Compare commits

...
This repository has been archived on 2023-10-28. You can view files and clone it, but cannot push or open issues or pull requests.

190 Commits

Author SHA1 Message Date
surtur f40ebcfc64
makefile: bump kaniko to a cgroupv2-aware revision
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-20 15:28:45 +02:00
surtur 1605479fda
chore: bump traefik to version 2.5.0
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-20 15:09:59 +02:00
surtur 621e1fda0e
chore: bump traefik to version 2.5.0-rc5
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-04 03:03:21 +02:00
surtur 8bc33aedd9
chore: bump traefik to version 2.5.0-rc2
All checks were successful
continuous-integration/drone/push Build is passing
2021-06-29 23:16:39 +02:00
surtur ec9e53f632
chore: bump traefik to version 2.4.9
All checks were successful
continuous-integration/drone/push Build is passing
2021-06-24 00:31:50 +02:00
surtur b3755bfd56
ci: use linux-amd64 stable tag
All checks were successful
continuous-integration/drone/push Build is passing
the tag's also moving but efforts are made to only push working code
there so it should do in terms of stability
2021-05-02 04:08:18 +02:00
surtur fe658e0daf
ci: switch kaniko to immawanderer/drone-kaniko
All checks were successful
continuous-integration/drone/push Build is passing
2021-04-21 13:41:05 +02:00
surtur fe60a2fac4
ci: pin hadolint version
All checks were successful
continuous-integration/drone/push Build is passing
Since we were using the infamous latest tag, the version of hadolint
used changed on us transparently (1.x.x--> 2.x.x) that had breaking
changes (apparently), causing the builds to fail (without the sources
changing in any way) --> see https://drone.dotya.ml/wanderer/pwt-0x01-ng/292/1/5
I guess now we know why version pinning is called a *best practice*...
2021-03-29 12:42:51 +02:00
surtur c2e0d20db9
chore: bump traefik to v2.4.7
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-03-23 18:56:49 +01:00
surtur 935c85c2e2
add .editorconfig
All checks were successful
continuous-integration/drone/push Build is passing
as per #14
close #14 (#14)
2021-03-02 15:45:14 +01:00
surtur 487c031566
chore: bump drone-kaniko version to 0.8.1
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-26 18:31:41 +01:00
surtur 4fd4146b1d
feat: unprivileged prod container w/ nobody+alpine
All checks were successful
continuous-integration/drone/push Build is passing
* let restore run as root in base container
* switch runtime container to alpine (was using debian before)
* chown stuff as nobody:nobody and become nobody to run the app
* as a consequence, we're no longer allowed to bind to :80 so the port
  has been changed to :8081. that also needed to be reflected in the
  compose file for traefik to know where to route traffic
* ASPNETCORE_ENVIRONMENT env var properly set to Production
2021-02-23 15:19:16 +01:00
surtur c2e3016f4c
makefile: {build,compose build} w/ buildkit
* faster builds with buildkit
  ref: https://www.docker.com/blog/faster-builds-in-compose-thanks-to-buildkit-support/
* use $(dcmd) variable at all times

* [skip ci]
2021-02-23 15:06:55 +01:00
surtur c9c69ce4e6
make prod image tag match the compose image name 2021-02-23 14:58:36 +01:00
surtur e8c6464e5f
enable traefik checking /health endpoint
* [skip ci]
2021-02-22 18:47:44 +01:00
surtur 5809918907
add healthcheck endpoint at /health
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-22 15:28:01 +01:00
surtur 80554d9da9
add traefik sticky session cookie
All checks were successful
continuous-integration/drone/push Build is passing
this helps traefik route clients to the backend servers they established
connections with initially, keeping the sessions alive properly

ref: https://doc.traefik.io/traefik/routing/services/#sticky-sessions
ref: https://stackoverflow.com/a/64711374
2021-02-22 13:39:03 +01:00
surtur 7abdd05701
add systemd service file and nginx config snippet
* setup considers deploying on dotya.ml but this can easily be changed
* [skip ci]
2021-02-22 12:54:30 +01:00
surtur 98e194f926
Makefile: add compose-prod-related targets 2021-02-22 12:54:29 +01:00
surtur 5c65ea289b
add production compose file
* secrets are stored in env files, examples were added
* add .*.env to .{docker,git}ignore to prevent anybody from mistakenly
  committing their env files (unless it's forced)

* [skip ci]
2021-02-22 12:54:29 +01:00
surtur 4cde01394f
add SimilarProducts into product's Details view
All checks were successful
continuous-integration/drone/push Build is passing
merge:
* feature-details-similar:
  add SimilarProducts partial view (6a7ec20)
  refactor: pass on List<Product> that has prod data (22f297c)
  update ProductsController to include similar prods (0590cd2)
2021-02-21 04:15:29 +01:00
surtur 6a7ec20275
add SimilarProducts partial view
All checks were successful
continuous-integration/drone/push Build is passing
* shows up to 3 similar products
* include it in product Detail
2021-02-21 04:10:56 +01:00
surtur 22f297c081
refactor: pass on List<Product> that has prod data
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-21 03:24:41 +01:00
surtur 0590cd2a69
update ProductsController to include similar prods
All checks were successful
continuous-integration/drone/push Build is passing
...and refactor a little
2021-02-21 02:54:27 +01:00
surtur 61a7f569cc
chore: cleanup in views
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-18 20:23:28 +01:00
surtur 73fb03ffc6
add #if DEBUG RazorRuntimeCompilation
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-18 20:01:18 +01:00
surtur 0fbf239593
fix SimilarProduct behaviour
All checks were successful
continuous-integration/drone/push Build is passing
* solves similar product uniqueness issues
* solves issue with checkboxes for similar products not showing as selected

fixes #15
fixes #16
2021-02-18 19:39:55 +01:00
surtur 4c6efbc830
chore: add db connstring (dev) to launchSettings
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-16 16:34:10 +01:00
surtur 0dfead8388
deprecate Product in favour of SimilarProduct
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-16 16:30:45 +01:00
surtur 7db10ab6c0
add SimilarProduct model class
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-16 16:29:19 +01:00
surtur d69b8c7cc6
chore: similar products [wip]
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-14 22:50:52 +01:00
surtur 7b536ba8f5
chore: add Similar parameter to Product model
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-14 22:40:05 +01:00
surtur 604d01a33a
fix: bring prod images from prod table
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-14 18:20:35 +01:00
surtur 35452d57dd
chore: add similar product functionality [wip]
All checks were successful
continuous-integration/drone/push Build is passing
attempt to get similar product from the view in the controller
2021-02-13 22:02:53 +01:00
surtur 574990a7bf
add similar products logic to admin view
All checks were successful
continuous-integration/drone/push Build is passing
essentially making this available for product edits. edited product
itself cannot be selected as similar.

note: checkbox input logic is still missing in the controller
2021-02-13 19:44:25 +01:00
surtur 2d8b31b6ff
rm: SimilarConf
All checks were successful
continuous-integration/drone/push Build is passing
since we don't plan to use {Created,Updated} for the Similar table
2021-02-13 17:40:39 +01:00
surtur aa0196d168
chore: add "Similar" to mock product init
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 17:37:23 +01:00
surtur bf7f93c1df
add: not-mapped list "Similar' to product model
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 17:36:03 +01:00
surtur 27a0f33744
chore: rm unused using in Similar.cs
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-13 17:34:04 +01:00
surtur 3be55f23be
chore: product edit-create view intent fixes
* skip ci
2021-02-12 22:56:57 +01:00
surtur dc7d505937
add: 'Similar' model and db conf
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 22:50:03 +01:00
surtur 8b04b59ec3
chore: reindent with tabs
[skip ci]
2021-02-12 22:18:48 +01:00
surtur 6a154f2f90
mv: js to site.js
All checks were successful
continuous-integration/drone/push Build is passing
site.js is already included in _Layout
2021-02-12 22:05:55 +01:00
surtur 93c7687b06
consolidate Stylezbro to stylez.css
[skip ci]
2021-02-12 21:52:30 +01:00
surtur 5cd55341da
chore: moar inline to stylez.css
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 21:47:51 +01:00
surtur f2841fe0b3
displaying role-based navbar items simplified 2021-02-12 21:29:03 +01:00
surtur 0ee01339b7
move {product,carousel} inline style to stylez.css
[skip ci]
2021-02-12 21:14:37 +01:00
surtur 404ddaeaf9
chore: reindent ultimate_script using tabs 2021-02-12 21:02:43 +01:00
surtur 77ada7f3cc
chore: move my orders inline styles to a css file
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 21:00:46 +01:00
surtur 1022a267de
refactor: view order details using summary-details
All checks were successful
continuous-integration/drone/push Build is passing
...like a pro and rm Details.js
2021-02-12 20:40:09 +01:00
surtur be3b7a1b93
chore: update product detail view [skip ci]
* button style update
* rm unused using
* add a space after the dash before the product name
2021-02-12 20:13:32 +01:00
surtur ab67773917
feat: use proper SELinux context for bind mounts
this change solves the issue I recently had after setting SELinux to
Enforcing mode and the containers suddenly could not access files
from the bind mounts anymore. the solution is to mount volumes with
either z (preferable here) or Z to have them automatically relabelled

 If you volume mount a image with -v /SOURCE:/DESTINATION:z docker will
 automatically relabel the content for you to s0. If you volume mount with
 a Z, then the label will be specific to the container, and not be able to
 be shared between containers.

ref: https://www.projectatomic.io/blog/2015/06/using-volumes-with-docker-can-cause-problems-with-selinux/

pertains:
* Makefile (volume args for kaniko)
* docker-compose.yml ($PWD to /src mount and a db volume)

[skip ci]
2021-02-12 19:26:28 +01:00
surtur c19a2963ec
chore: rm unnecessary using
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-12 18:41:37 +01:00
surtur facf2c1260
feat: dynamically generate product details
All checks were successful
continuous-integration/drone/push Build is passing
* details for each product are now grabbed from the db
* functionality to "add product to order" (fake cart) has been added
* added Product.js, which facilitates calling the endpoint to add
  product "to order"

* as per #11
* close #11
2021-02-12 13:29:30 +01:00
surtur ac45c43174
chore: switch to UltimateViewModel for Home
All checks were successful
continuous-integration/drone/push Build is passing
* as per #11
2021-02-12 13:22:29 +01:00
surtur a626fb50a6
chore: make ProductsController return a Product
All checks were successful
continuous-integration/drone/push Build is passing
* in the Detail action method
* as per #11
2021-02-12 13:14:09 +01:00
surtur fb407848dc
fix: properly pass username to the view as uname
All checks were successful
continuous-integration/drone/push Build is passing
* using ViewData to pass a username string sourced from the User object

* as per #11
2021-02-12 13:03:51 +01:00
surtur 26ca0798a8
refactor: CustomerOrderNotCartController
All checks were successful
continuous-integration/drone/push Build is passing
* as per #11
2021-02-12 12:58:40 +01:00
surtur 3ac4659680
add: UltimateViewModel (as per #11)
All checks were successful
continuous-integration/drone/push Build is passing
* consolidates {Carousel,Product}ViewModel in a single ViewModel since
  we're going to need both carousels and products in the index
2021-02-12 11:45:56 +01:00
surtur 21bfef1e8c
cleanup: remove unused SessionExtensions methods
All checks were successful
continuous-integration/drone/push Build is passing
* edit _Layout accordingly

as per #11
2021-02-12 11:43:48 +01:00
surtur 13b0ffffeb
refactor: use fully qualified class name in FK ref
All checks were successful
continuous-integration/drone/push Build is passing
as per #11
2021-02-12 11:32:58 +01:00
surtur d46ff68121
add: {UsersController,views} and update _Layout
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 12:59:34 +01:00
surtur 06b3b39805
chore: add User_id to ViewData in OrdersController
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 12:18:44 +01:00
surtur 4eab6f8dc1
add: CustomerOrderViewModel
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 12:11:12 +01:00
surtur 4b42658671
feat: add Customer area
All checks were successful
continuous-integration/drone/push Build is passing
* controller
* views
* SessionExtensions class
2021-02-10 12:01:59 +01:00
surtur 9bed70352e
{User_id,Price_total} in Order {controller,views}
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 11:38:55 +01:00
surtur 649cf813ba
chore: prepare for using session in Startup
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 11:21:04 +01:00
surtur d3671871d2
chore: update Order model
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 11:08:00 +01:00
surtur 9a916a1e5b
chore: input --> textarea
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 03:39:54 +01:00
surtur fac834c4dc
feat: use logger
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 03:26:37 +01:00
surtur 5e9f283c26
add validation to {login,register} and refactor
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 03:03:46 +01:00
surtur 72d4d71415
fix: identity application service missed values
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 03:02:12 +01:00
surtur 8eb0429637
chore: print ModelState err for AccountController
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 03:01:07 +01:00
surtur a4c21dc643
chore: use literal min_passwd_length values
All checks were successful
continuous-integration/drone/push Build is passing
needs const values
2021-02-10 02:59:21 +01:00
surtur 8b4e8a45fb
fix: cover null input on UniqueCharsAttr
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 02:27:07 +01:00
surtur 3de6d80f37
cleanup: remove unused usings throughout
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 00:34:56 +01:00
surtur a2eff0f0cb
feat: protect controllers with authorization
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-10 00:34:56 +01:00
surtur 0cef320b1c
chore: edit visibility of certain navbar items
All checks were successful
continuous-integration/drone/push Build is passing
TODO: handle proper authentication in the controllers
2021-02-09 22:31:15 +01:00
surtur 2342aaaf7a
fix: redirect on logout proper
All checks were successful
continuous-integration/drone/push Build is passing
follow-up of {e915495,f9f3f3a035}
2021-02-09 22:29:17 +01:00
surtur 8851dfb957
chore: update _Layout taking in regard user logins
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 21:55:08 +01:00
surtur a6296cb24b
add: {Login,Register} controller POST actions
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 21:00:06 +01:00
surtur f9f3f3a035
chore: follow-up of e915495 (redirect on logout)
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 20:50:34 +01:00
surtur 633412ea0e
typo fix: use Roles enum
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 20:31:15 +01:00
surtur f74bb2d5f3
add: {carousel,product}s webroot image folders
* [skip ci]
2021-02-09 20:06:19 +01:00
surtur db7fe47646
chore: add jp{,e}g and png to .gitignore 2021-02-09 20:04:39 +01:00
surtur e915495456
add: redirect to homepage on logout
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 18:44:19 +01:00
surtur f5f8c2046c
add: controller constructor
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 18:15:06 +01:00
surtur 9bbe1aabd9
chore: crlf to lf 2021-02-09 18:14:14 +01:00
surtur da67434379
chore: a file rename
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 17:59:12 +01:00
surtur 15faafe3d7
chore: register identity service
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-09 17:04:22 +01:00
surtur 714f2073ce
add: SecurityIdentityApplicationService
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-08 23:15:26 +01:00
surtur 344da21484
add: config values
All checks were successful
continuous-integration/drone/push Build is passing
* lockout_on_failure boolean default to true
* remember_me_feature boolean, default to false
2021-02-08 23:13:12 +01:00
surtur e89f450e13
add: {username,password} in user identity model
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-08 23:11:46 +01:00
surtur eff8d1f087
chore: change logout method type in interface
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-08 23:10:46 +01:00
surtur e07fe18c23
chore: use role in identity application service
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-08 22:48:00 +01:00
surtur c18a502192
add: ISecurityApplicationInterface
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 22:26:26 +01:00
surtur 704a9052b9
rename: pluginscache --> pkgcache 2021-02-07 22:13:15 +01:00
surtur e79fbd5d30
optim: let restore have its own step and use cache
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 21:38:41 +01:00
surtur 0a5eea2a5e
use UniqueCharsAttr for {Login,Register}ViewModel
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-07 20:50:08 +01:00
surtur 345fc8d5e2
add: unique characters attribute
All checks were successful
continuous-integration/drone/push Build is passing
familiarly known as UniqueCharsAttr
2021-02-07 20:47:55 +01:00
surtur 4d8c44ce38
add: conf value for min unique chars in passwds 2021-02-07 20:47:55 +01:00
surtur 90553d9ae9
chore: use min_passwd_length with attribute
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 21:07:01 +01:00
surtur f7ab994ddb
chore: use min_passwd_length config value
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 21:04:23 +01:00
surtur 46f0247c89
add: config class
All checks were successful
continuous-integration/drone/push Build is passing
* will store app configuration parameters
2021-02-06 21:03:42 +01:00
surtur bd6ce61990
chore: edit filetype attr usage
All checks were successful
continuous-integration/drone/push Build is passing
* allow on {field, property,parameter}
* allow attr to be used only once per item
2021-02-06 20:31:41 +01:00
surtur 820da633b3
chore: rm {email,name} from registration form
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 20:22:46 +01:00
surtur 9aab10c765
[skip ci] chore: rm forgotten comment 2021-02-06 15:14:38 +01:00
surtur 4f6b0e3942
fix: image src not getting properly registered
All checks were successful
continuous-integration/drone/push Build is passing
image src for {carousel,product} was missing on {create,edit} even
though the images themselves were uploaded

fixes #9
2021-02-06 15:09:23 +01:00
surtur 4af0984fa9
chore: move manager creation after admin creation
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 14:50:38 +01:00
surtur ad427cae41
re-enable printing of role creation debug info
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 14:48:52 +01:00
surtur 69b9f101aa
chore: add registration view
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 14:46:08 +01:00
surtur 35b4abedec
chore: view errors, should any occur
All checks were successful
continuous-integration/drone/push Build is passing
2021-02-06 14:44:49 +01:00
surtur 9cd1188714
chore: fix role creation on app startup
All checks were successful
continuous-integration/drone/push Build is passing
fixes #8
kudos to @FunTomOftheBlueScreen@utb.cz
2021-01-28 17:18:15 +01:00
surtur 24a830b523
merge: feature-add-identity
commit 47c8a3e8e27fb8a71e702232c681d588fa152bc0
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:58:07 2021 +0100

    chore: register and login on the right

commit 3e32cd61c2436123887d78f4ae396577b378e53c
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:43:07 2021 +0100

    chore: add {Login,Register}ViewModel

commit f82f12a5328f1500c3a58dccb3728dc86d1cc92c
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:30:36 2021 +0100

    chore: libs and pkgs version bump

commit 202b06505c94817c7bc60b432a9320efce80afac
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:29:40 2021 +0100

    chore: local post-kaniko convenience chown

    * [skip ci]

commit 7e4c4ccbbf4f7290d47c4c682f09ee508b34ae1e
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:29:15 2021 +0100

    chore: add Manager on app start

commit 62e9d567c07794b821eb86e7dcff4ecbb6bbd47e
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 14:12:24 2021 +0100

    chore: add Login page

    * include FontAwesome (atm from CDN, will fetch it later and bundle it
      here)

commit 3c899a2d024d8b74c7e28dfd4b86e1332d964129
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 13:33:32 2021 +0100

    fix: use proper service provider

commit 88a6e0f45eae7d71cf352d01a693429246effd98
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 12:26:41 2021 +0100

    chore: do creation async + s/FirstName/Name/

commit eaed0ec13c0dd3824802a698ac9ed30bffc52de9
Author: surtur <a_mirre@utb.cz>
Date:   Thu Jan 28 10:55:23 2021 +0100

    fix: err creating admin

    * cause of silly passwd requirements - require {upper,lower}case and
      non-alphanumeric characters is implicitly set to true

commit f55f8517f8f8516192b5d156c71596e7fcc55ad2
Author: surtur <a_mirre@utb.cz>
Date:   Wed Jan 27 23:11:06 2021 +0100

    feat: table renames+call role-creating methods

    * no "AspNet" prefix
    * actually call Ensure{Admin,Roles}Created on app start

commit 20d3458f281ec9478dc9b0fab3795556dcd0a78f
Author: surtur <a_mirre@utb.cz>
Date:   Wed Jan 27 22:05:11 2021 +0100

    fix typo: DbContext --> DBContext

commit f972ac83e7197813bf4c9b4d9bb44d640b5dd071
Author: surtur <a_mirre@utb.cz>
Date:   Wed Jan 27 20:55:22 2021 +0100

    chore: add login and usr mgmt logic

    * add login and registration views
    * add methods to create admin user on app start

commit b65a6d21f8dc5227a8da2d5040348e269c2f3711
Author: surtur <a_mirre@utb.cz>
Date:   Wed Jan 27 16:02:33 2021 +0100

    chore: add auth options

    * FIXME dev fun settings need to be replaced with proper settings

commit 8f84939c22ec96e0a143350b9b9ba3d738dfc149
Author: surtur <a_mirre@utb.cz>
Date:   Wed Jan 27 14:54:57 2021 +0100

    chore: adding identity roles (batch 1)

    * preparing Startup and DBContext to work with roles and auth
    * added Roles enum
    * added User model
    * added Microsoft.AspNetCore.Identity.EntityFrameworkCore v3.1.10 nuget

    * note: auth is not ready and working yet, this is batch one of the
      preparations
2021-01-28 15:27:25 +01:00
surtur d2353d3851
feat: generalise && simplify MegaUpload
All checks were successful
continuous-integration/drone/push Build is passing
* ...+ related edits in {Products,Carousel}Controller
2021-01-26 23:13:34 +01:00
surtur d8cecc6194
chore: rm bogus reviews and repurpose button
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 22:00:15 +01:00
surtur 8cd0f66e6d
chore: add Product Detail boilerplate
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 21:39:27 +01:00
surtur 8c64353e1e
chore: add the product Details route to Startup.cs
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 21:14:17 +01:00
surtur 2d2a7f4745
chore: add main page product boilerplate
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 21:13:33 +01:00
surtur 880de828cb
chore: rm landing page .net promo code
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 20:41:51 +01:00
surtur 9dcd80ac58
chore: add style section dev/prod separator
All checks were successful
continuous-integration/drone/push Build is passing
* contains the same stylesheet and no bundling pipeline is in place atm
2021-01-26 18:49:19 +01:00
surtur bb189cac7f
feat: add hadolint Dockerfile linting
All checks were successful
continuous-integration/drone/push Build is passing
commit 5acb4e2ba773d312c6b5159011ef415af53f8f71
Author: surtur <a_mirre@utb.cz>
Date:   Tue Jan 26 16:28:10 2021 +0100

    chore: rework ci pipeline logic

    * run {debug,release} builds after clone, then lint Dockerfile{,.dev}
      and finally run kaniko builds ({debug,release}) in parallel

commit 1e16f72eb4957b14c7fb316282d4cefae0811871
Author: surtur <a_mirre@utb.cz>
Date:   Tue Jan 26 16:19:50 2021 +0100

    feat: add hadolint Dockerfile linting

    to conform the linter and best practices:
    * add a FROM alias
    * quote variables (even though they're single-word and known in
      advance, might actually change it to ignore the warning)
2021-01-26 17:35:44 +01:00
surtur 4c78f0e417
chore: proper column count (bootstrap 12)
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 16:07:01 +01:00
surtur a5ebd88141
chore: edit table style
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 15:41:52 +01:00
wanderer bd42a0f845
merge: 'add custom attribute for file mime type validation' (#6)
All checks were successful
continuous-integration/drone/push Build is passing
from feature-custom-attr-validation into master

6f6ebaa cleanup: rm redundant ids
9e90f3e chore: reword attr err msg + add help link
7644066 chore: add Product validation
cf223da chore: more carousel validation goodies
b19c4a7 fix: typo and missing using
484d9a2 chore: add image validation

Reviewed-on: #6
2021-01-26 15:24:06 +01:00
surtur 6f6ebaa6fa
cleanup: rm redundant ids
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2021-01-26 15:08:12 +01:00
surtur 9e90f3ee28
chore: reword attr err msg + add help link
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-26 14:57:41 +01:00
surtur 7644066138
chore: add Product validation
All checks were successful
continuous-integration/drone/push Build is passing
as per #5

commit d0b491d1dc289dedf242a974b1b42e021272234b
Author: surtur <a_mirre@utb.cz>
Date:   Tue Jan 26 14:35:05 2021 +0100

    chore: add proper Product validation

    * image file type checking with custom attr

commit 81a5fe36c2aef355aa71a6208acea096dd35d70e
Author: surtur <a_mirre@utb.cz>
Date:   Mon Jan 25 22:29:48 2021 +0100

    chore: add client-side carousel edit validation

commit 5cddb4bd9b55a514d9aa6f79f291678c88d33773
Author: surtur <a_mirre@utb.cz>
Date:   Mon Jan 25 22:27:38 2021 +0100

    chore: add carousel image file type checking

    * since image is not required, so isn't ImageSrc
2021-01-26 14:40:58 +01:00
surtur cf223daf81
chore: more carousel validation goodies
All checks were successful
continuous-integration/drone/push Build is passing
commit 81a5fe36c2aef355aa71a6208acea096dd35d70e
Author: surtur <a_mirre@utb.cz>
Date:   Mon Jan 25 22:29:48 2021 +0100

    chore: add client-side carousel edit validation

commit 5cddb4bd9b55a514d9aa6f79f291678c88d33773
Author: surtur <a_mirre@utb.cz>
Date:   Mon Jan 25 22:27:38 2021 +0100

    chore: add carousel image file type checking

    * since image is not required, so isn't ImageSrc
2021-01-25 22:32:34 +01:00
surtur b19c4a7c00
fix: typo and missing using
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-25 16:30:37 +01:00
surtur 484d9a2db5
chore: add image validation 2021-01-24 19:31:56 +01:00
surtur ff301397ff
chore: make Created attr read-only
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-23 14:44:15 +01:00
surtur be090f643f
chore: add server-side validation as per #5
All checks were successful
continuous-integration/drone/push Build is passing
* for {Carouse,Product}
2021-01-23 14:43:42 +01:00
surtur a863e396e9
chore: add Carousel,Product client-side validation
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-23 14:31:52 +01:00
surtur fa9880d242
chore: defer validation js loading 2021-01-23 14:26:57 +01:00
surtur 0741deaff9
chore: change compose targets
* update Makefile and README
* [skip ci]
2021-01-23 14:25:24 +01:00
surtur d660c59cf7
chore: add response compression
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-23 14:24:02 +01:00
surtur d9a31187ad
chore: update {About,Contact} pages
All checks were successful
continuous-integration/drone/push Build is passing
* rm generic stuff, replace it with our goodness
* use emoji!
2021-01-22 20:55:08 +01:00
surtur 902250c471
chore: take into account 2020
* update footer partial view
2021-01-22 20:51:52 +01:00
surtur ed3557de11
chore: update README.md [skip ci]
close #4
2021-01-21 22:02:13 +01:00
surtur 41b2056cca
chore: rm commented using
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-21 21:19:30 +01:00
surtur 4ba9949cc7
chore: Index --> Order Index 2021-01-21 21:19:02 +01:00
surtur 031ed0a2c0
chore: don't show db-bound fields
All checks were successful
continuous-integration/drone/push Build is passing
* this still needs to be dealt with server-side, but at least don't
  present the user a form in which they could edit created/updated
  fields
2021-01-21 21:14:49 +01:00
surtur 3bb964e6f2
update README.md: rm dev branch reference
All checks were successful
continuous-integration/drone/push Build is passing
2021-01-03 11:03:38 +01:00
surtur 1442a2a664
chore: bind the new."attr" as we're using postgres
All checks were successful
continuous-integration/drone/push Build is passing
closes #2
  closes #3
  accompanied by create_triggers script that creates triggers in the
  subject columns to update the "Updated" value just before the column
  update occurs
2021-01-03 02:38:26 +01:00
surtur 8dda15cfac
chore: rework dbinit slightly
All checks were successful
continuous-integration/drone/push Build is passing
* fill the tables only if there's nothing in them already
2020-12-31 11:28:56 +01:00
surtur 0779d07d84
fix: model validation failing on required attr
All checks were successful
continuous-integration/drone/push Build is passing
* fixes #2
2020-12-31 11:25:11 +01:00
surtur 6949461bed
merge: feature-admin-views-spaces-to-tabs
All checks were successful
continuous-integration/drone/push Build is passing
commit 43ceea436cf04ed259d5e094e3e003da630d3dce
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:57:48 2020 +0100

    chore: spaces to tabs in admin views

commit 0bc3345368826a6c6edae974ca4cb69f32134553
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:41:12 2020 +0100

    chore: always update the updated timestamp

    * instruct db to update the updated timestamp both on create and edit,
      fixing a little bug that was preventing creation of items of any kind
      (since this behaviour has been used throughout the entity configs) due
      to the updated timestamp not being set (while db knows it's a not-null)

commit 34664fc9f3876605001b4c5294d3350a136f2997
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:18:05 2020 +0100

    chore: edit the scaffolded views to use our layout

commit 6816006c09b5a3d79c489e3c27d7081b83e6195e
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:14:17 2020 +0100

    chore: update Makefile "run" target+added targets

    * pgdba - start a dev db container in background
    * pgdbz - stop the dev db image
    * docker run db img args are configured via variables as seen fit
    * run is now more of a "watch run"

commit f1d87345ac2d7d9ce854a6c8bb37731ff0f8805c
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:12:41 2020 +0100

    chore: add {OrderItem,Order} links to navbar

commit c214cf4fe6625ad312275432a8883a01e37f9cf0
Author: surtur <a_mirre@utb.cz>
Date:   Wed Dec 30 13:37:34 2020 +0100

    chore: add netcore-scaffolded controllers+views

    * Templates folder of the vs.web.codegenerators pkg (v3.1.4) had to be
      temporarily copied to the project directory for the scaffolding to
      work; unnecessary template folders had to be removed prior to running
      scaffold command
    * restored pkgs and clean built, then scaffolded using (var set for
      trace output of the command):
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrdersController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.Order -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrderItemController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.OrderItem -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
2020-12-31 02:11:14 +01:00
surtur 1445207ca3
merge: feature-update-timestamp-onaddorupdate
All checks were successful
continuous-integration/drone/push Build is passing
commit 0bc3345368826a6c6edae974ca4cb69f32134553
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:41:12 2020 +0100

    chore: always update the updated timestamp

    * instruct db to update the updated timestamp both on create and edit,
      fixing a little bug that was preventing creation of items of any kind
      (since this behaviour has been used throughout the entity configs) due
      to the updated timestamp not being set (while db knows it's a not-null)

commit 34664fc9f3876605001b4c5294d3350a136f2997
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:18:05 2020 +0100

    chore: edit the scaffolded views to use our layout

commit 6816006c09b5a3d79c489e3c27d7081b83e6195e
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:14:17 2020 +0100

    chore: update Makefile "run" target+added targets

    * pgdba - start a dev db container in background
    * pgdbz - stop the dev db image
    * docker run db img args are configured via variables as seen fit
    * run is now more of a "watch run"

commit f1d87345ac2d7d9ce854a6c8bb37731ff0f8805c
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:12:41 2020 +0100

    chore: add {OrderItem,Order} links to navbar

commit c214cf4fe6625ad312275432a8883a01e37f9cf0
Author: surtur <a_mirre@utb.cz>
Date:   Wed Dec 30 13:37:34 2020 +0100

    chore: add netcore-scaffolded controllers+views

    * Templates folder of the vs.web.codegenerators pkg (v3.1.4) had to be
      temporarily copied to the project directory for the scaffolding to
      work; unnecessary template folders had to be removed prior to running
      scaffold command
    * restored pkgs and clean built, then scaffolded using (var set for
      trace output of the command):
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrdersController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.Order -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrderItemController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.OrderItem -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
2020-12-31 02:03:34 +01:00
surtur df9f0b36c4
merge: bring in feature-netcore-scaffolding-pain
All checks were successful
continuous-integration/drone/push Build is passing
commit 34664fc9f3876605001b4c5294d3350a136f2997
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:18:05 2020 +0100

    chore: edit the scaffolded views to use our layout

commit 6816006c09b5a3d79c489e3c27d7081b83e6195e
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:14:17 2020 +0100

    chore: update Makefile "run" target+added targets

    * pgdba - start a dev db container in background
    * pgdbz - stop the dev db image
    * docker run db img args are configured via variables as seen fit
    * run is now more of a "watch run"

commit f1d87345ac2d7d9ce854a6c8bb37731ff0f8805c
Author: surtur <a_mirre@utb.cz>
Date:   Thu Dec 31 01:12:41 2020 +0100

    chore: add {OrderItem,Order} links to navbar

commit c214cf4fe6625ad312275432a8883a01e37f9cf0
Author: surtur <a_mirre@utb.cz>
Date:   Wed Dec 30 13:37:34 2020 +0100

    chore: add netcore-scaffolded controllers+views

    * Templates folder of the vs.web.codegenerators pkg (v3.1.4) had to be
      temporarily copied to the project directory for the scaffolding to
      work; unnecessary template folders had to be removed prior to running
      scaffold command
    * restored pkgs and clean built, then scaffolded using (var set for
      trace output of the command):
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrdersController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.Order -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
      make restore clean build; codegen_trace=1 dotnet-aspnet-codegenerator controller -p "." -actions --force -name OrderItemController -dc pwt_0x01_ng.Models.Database.DBContext -async -m pwt_0x01_ng.Models.OrderItem -namespace pwt_0x01_ng.Areas.Admin.Controllers -outDir Areas/Admin/Controllers --no-build
2020-12-31 02:01:45 +01:00
surtur 421a8f5f62
merge: bring in feature-add-shared-base-entity
All checks were successful
continuous-integration/drone/push Build is passing
commit 85cbab22a8d58acf53e32bc122329b50170de1a0
Author: surtur <a_mirre@utb.cz>
Date:   Wed Dec 30 00:53:27 2020 +0100

    chore: update proj file to include Migrations dir

commit 71bcd9fa486b761b83ad488d51e9fde5b5d8565f
Author: surtur <a_mirre@utb.cz>
Date:   Tue Dec 29 23:28:17 2020 +0100

    chore: prevent deletion of products used in orders

commit d9ddee09d78c8e50dbf4f5bcbe07b80294de62e8
Author: surtur <a_mirre@utb.cz>
Date:   Tue Dec 29 19:45:40 2020 +0100

    chore: add db conf for {Product,Carousel,Order}

    * csproj pkg cleanup

commit 66c9c1cb520373b91723e2f4ef8d4bc76e05d8a9
Author: surtur <a_mirre@utb.cz>
Date:   Tue Dec 29 17:48:09 2020 +0100

    chore: create a shared base entity
2020-12-30 03:40:50 +01:00
surtur 37cfb08acb
chore: update footer w/ project and author details
All checks were successful
continuous-integration/drone/push Build is passing
* link to source at https://git.dotya.ml/wanderer/pwt-0x01-ng
2020-12-29 02:26:36 +01:00
surtur cbbcc36286
chore: move footer to a partial view
All checks were successful
continuous-integration/drone/push Build is passing
* also generate the year next to copyright dynamically
2020-12-29 02:17:50 +01:00
surtur 0afdebe155
chore: defer script loading (performance)
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-29 02:02:00 +01:00
surtur 5c9546b9be
feat: add product CRUD functionality
All checks were successful
continuous-integration/drone/push Build is passing
* overload of MegaUpload has been added so that it could be used with
  Product as well as Carousel
* confirmation script appropriately edited to now also serve Product
2020-12-29 01:25:13 +01:00
surtur 2258ca0f6a
chore: add error handling for 40{3,4} and 500
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-28 22:28:41 +01:00
surtur b35b3d6ce7
refactor: spaces --> tabs
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-28 14:50:19 +01:00
surtur 4b6e560e6d
chore: replace legacy routing code with proper 3.1
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-28 14:36:36 +01:00
surtur f0535f7797
chore: conditional razor runtime compilation
All checks were successful
continuous-integration/drone/push Build is passing
* enabled in dev mode
2020-12-27 16:22:27 +01:00
surtur dfeb6156e9
chore: add stuff to the list of the ignored
* [skip ci]
2020-12-27 05:37:43 +01:00
surtur 9cf87e8a22
chore: follow-up of 1bb9a07
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 05:35:02 +01:00
surtur 9ef5f543f7
chore: only inlude the id (hidden) when editing
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 05:30:37 +01:00
surtur e8b4625165
chore: setting this never gets old
* env var to disable dotnet calling home
* [skip ci]
2020-12-27 05:29:33 +01:00
surtur 1bb9a07bfa
chore: dbfake --> real db in controllers
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 05:28:27 +01:00
surtur a0cab2d46f
chore: rename namespace matching rest of the proj
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 04:52:04 +01:00
surtur 2875223652
chore: update csproj file
All checks were successful
continuous-integration/drone/push Build is passing
* rm folder trailing slashes
* include pkg ref for razor to solve a curious error occuring on a system
  with 5.0 installed along 3.1 while targeting 3.1
2020-12-27 04:48:08 +01:00
surtur 5da01fb3b2
chore: update .dockerignore for smaller images
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-27 04:46:38 +01:00
surtur fe2e054f0b
chore: postgre --> postgres
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-26 19:28:41 +01:00
surtur 83379ec335
chore: revert 61714e3d9e
All checks were successful
continuous-integration/drone/pr Build is passing
* didn't work out so well, builds fine but refuses to run, stashing
  the idea for another time
2020-12-26 18:42:34 +01:00
surtur 61714e3d9e
chore: use async for db ops
All checks were successful
continuous-integration/drone/pr Build is passing
2020-12-26 18:00:15 +01:00
surtur 5a81c93395
chore: include carousels folder 2020-12-26 17:59:32 +01:00
surtur b0669f65c0
chore: target 3.1.* + connected simplification
All checks were successful
continuous-integration/drone/pr Build is passing
* global.json sdk value will now match 3.1.whatever
* rm the now-redundant sed both from the ci pipeline and Dockerfile*
* limit ci pipeline runs to single build for an event. e.g. on a pr,
  run a single build instead of both a pr build and a push build
* edit Makefile to force --no-cache builds
2020-12-26 17:26:11 +01:00
surtur 4fd5b9f4de
chore: turn off the stupid banner for now 2020-12-26 17:05:31 +01:00
surtur 779c349ff6
chore: correct db creds format + mount local dir
* ./ instead of $PWD means that the mounted folder is not where the
  command is run from but rather the folder local to the compose file
* use proper db creds format
2020-12-26 17:02:24 +01:00
surtur 82d075dd01
feat: switch to rootless runs + refactor
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
* build as root but run the container as UID/GID 1000
* db expose only on localhost
* docker-compose mount $PWD for easy local debugging
2020-12-15 22:47:08 +01:00
surtur 1489069c5c
chore: update Makefile - add dcdevrun target
All checks were successful
continuous-integration/drone/pr Build is passing
* [skip ci]
2020-12-15 15:28:11 +01:00
surtur 67561303a4
feat: move to framework version 3.1
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
* lots of pertaining changes
* again solving global.json version mismatch with sed
* correctly specify db creds via dc env
2020-12-15 15:04:28 +01:00
surtur e5b871c275
chore: adding postgre support [wip - batch 1]
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-06 12:23:51 +01:00
surtur 8a628a890b
chore: change build output paths + publish
All checks were successful
continuous-integration/drone/push Build is passing
* ./bin/ is in .gitignore so output build stuff there
* dotnet build --> dotnet publish for release build
2020-12-06 00:01:33 +01:00
surtur b10dc1078b
chore: Makefile:add test target; update .drone.yml
* test target to test build all the goodies
* rm dotnet clean from the build pipeline to match what is being built
  inside docker
* [skip ci]
2020-12-05 22:34:45 +01:00
surtur 60586f4d68
refactor: optimize Dockerfiles for quicker builds
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-05 22:28:19 +01:00
surtur 6af240c922
chore: add meaningful aria-describedby text 2020-12-05 16:58:24 +01:00
surtur de466c8422
fix: typo - pruneargs contained a dot (.)
[skip ci]
2020-12-02 03:43:34 +01:00
surtur 76235b7ef8
chore: add prune target and refactor kaniko stuff
[skip ci]
2020-12-02 03:39:58 +01:00
surtur 6b3601ee0b
refactor: clean up Makefile vars
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-02 03:31:56 +01:00
surtur 37e93a9d95
chore: specify .PHONY targets [skip ci] 2020-12-02 03:15:10 +01:00
surtur 8b60e35627
chore: print out the target commands 2020-12-02 03:13:19 +01:00
surtur 9d845e246f
chore: rm kaniko-testing remnant call
All checks were successful
continuous-integration/drone/push Build is passing
2020-12-02 03:12:26 +01:00
surtur e64d137b2a
merge: bring in kaniko builds
commit 3a155798a9
Author: surtur <a_mirre@utb.cz>
Date:   Wed Dec 2 03:08:12 2020 +0100

    rm: get rid of unused launch settings

    [skip ci]

commit 3f775a110b
Author: surtur <a_mirre@utb.cz>
Date:   Sun Nov 29 21:08:11 2020 +0100

    fix: clean build after restore

    * this fixes occasional situation when NuGets have not been restored yet
      but the clean build already running required them, which resulted a
      failed build

commit 42c6dfd3ba
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 20:05:30 2020 +0100

    chore: update make kaniko

    * skip ci

commit 1cc3df6619
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 18:58:48 2020 +0100

    chore: rm debug ls call

    skip ci

commit befe7880fc
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 18:58:27 2020 +0100

    revert: set kaniko context back to "."

    skip ci

commit 15d7de1996
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 18:58:06 2020 +0100

    chore: add make kaniko

    * skip ci

commit 5ed341f4ae
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 17:13:27 2020 +0100

    refactor: merge pipelines back

commit 732cd7d717
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 17:12:39 2020 +0100

    chore: don't wait and build

commit 11a9ab07ab
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 17:11:08 2020 +0100

    revert: kaniko don't give a **** about relpaths

commit a581457326
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 16:23:28 2020 +0100

    debug: see where we are

commit 1ef9a6831f
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 15:58:50 2020 +0100

    chore: try /src workspace for kaniko

commit 11d796f0dd
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 15:30:21 2020 +0100

    chore: add /src context for kaniko

commit 4b58c3f584
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 15:22:18 2020 +0100

    fix: rel paths are nicer to kaniko

commit 63601f7f47
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 15:16:57 2020 +0100

    chore: run wherever

commit 54fb45ec65
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 15:06:42 2020 +0100

    refactor: effectively revert 907a470 and 63bde0a

commit eece1e3dba
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 14:52:23 2020 +0100

    chore: move dockerbuilds to a separate pipeline

    * and run them on the main runner

commit 907a4702d8
Author: surtur <a_mirre@utb.cz>
Date:   Fri Nov 27 14:47:03 2020 +0100

    chore: give kaniko relative paths in Dockerfile
2020-12-02 03:09:34 +01:00
surtur 5aa52dd687
chore: try out fresher drone-kaniko image
All checks were successful
continuous-integration/drone/push Build is passing
2020-11-27 19:00:06 +01:00
128 changed files with 4499 additions and 663 deletions

13
.dockerignore Normal file
View File

@ -0,0 +1,13 @@
docker-compose.yml
.dockerignore
.gitignore
bin/
obj/
.git
.vs
.vscode
.aspnet
.dotnet
.nuget
.local
.*.env

View File

@ -7,35 +7,75 @@ platform:
os: linux
arch: amd64
trigger:
ref:
- refs/heads/master
- refs/heads/feature-*
- refs/pull/*/head
- refs/tags/*
steps:
- name: debug
- name: restore
pull: always
image: mcr.microsoft.com/dotnet/core/sdk:2.1-alpine
image: mcr.microsoft.com/dotnet/core/sdk:3.1-alpine
volumes:
- name: pkgcache
path: /root/.nuget/packages
depends_on: [clone]
commands:
- dotnet restore
- name: debug
pull: always
image: mcr.microsoft.com/dotnet/core/sdk:3.1-alpine
volumes:
- name: pkgcache
path: /root/.nuget/packages
depends_on: [restore]
commands:
- dotnet build .
- name: release
pull: always
image: mcr.microsoft.com/dotnet/core/sdk:2.1-alpine
depends_on:
- debug
image: mcr.microsoft.com/dotnet/core/sdk:3.1-alpine
volumes:
- name: pkgcache
path: /root/.nuget/packages
depends_on: [restore]
commands:
- dotnet clean
- dotnet restore
- dotnet publish -c Release
- dotnet publish -c Release -o out
- name: hadolint release
image: hadolint/hadolint:v1.23.0-8-gb01c5a9-alpine
depends_on: [clone]
commands:
- hadolint --version
- hadolint Dockerfile
- name: hadolint debug
image: hadolint/hadolint:v1.23.0-8-gb01c5a9-alpine
depends_on: [clone]
commands:
- hadolint --version
- hadolint Dockerfile.dev
- name: docker-release-build
pull: always
image: banzaicloud/drone-kaniko
image: immawanderer/drone-kaniko:linux-amd64
depends_on: [release, hadolint release]
settings:
dockerfile: Dockerfile
context: .
- name: docker-debug-build
pull: always
image: banzaicloud/drone-kaniko
image: immawanderer/drone-kaniko:linux-amd64
depends_on: [debug, hadolint debug]
settings:
dockerfile: Dockerfile.dev
context: .
volumes:
- name: pkgcache
temp: {}

30
.editorconfig Normal file
View File

@ -0,0 +1,30 @@
[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4
[{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}]
end_of_line = lf
indent_style = space
indent_size = 2
[{*.yaml,*.yml}]
end_of_line = lf
indent_style = space
indent_size = 2
[*.js.map]
end_of_line = lf
indent_style = space
indent_size = 2
[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,hlsl,hlsli,hlslinc,master,nuspec,paml,razor,resw,resx,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}]
end_of_line = lf
indent_style = tab
indent_size = 4
tab_width = 4

3
.example-db.env Normal file
View File

@ -0,0 +1,3 @@
POSTGRES_USER=postgres
POSTGRES_PASSWORD=a6a204ca41effac89a0696ec8c652ba8b6b668129d55b5085eb4453ccb214343481bd0a61b889f1e2ec2ad58d267edf623898892b6cd042f93ad17610871da58
POSTGRES_INITDB_ARGS="--data-checksums"

3
.example-release_app.env Normal file
View File

@ -0,0 +1,3 @@
DOTNET_CLI_TELEMETRY_OPTOUT=true
ASPNETCORE_ENVIRONMENT=Production
DB_CONNECTION_STRING="User ID=postgres;Password=a6a204ca41effac89a0696ec8c652ba8b6b668129d55b5085eb4453ccb214343481bd0a61b889f1e2ec2ad58d267edf623898892b6cd042f93ad17610871da58;Server=db;Port=5432;Database=pwt;Integrated Security=true;Pooling=true;"

14
.gitignore vendored
View File

@ -4,3 +4,17 @@ obj/
riderModule.iml
/_ReSharper.Caches/
*.swp
.dotnet/
.aspnet/
.nuget/
.local/
.idea/
.vscode/
.vs/
.ash_history
*.jpg
*.jpeg
*.png
.*.env

3
.hadolint.yaml Normal file
View File

@ -0,0 +1,3 @@
ignored:
# ad "SC2039 In POSIX sh, UID is undefined." - it's a var defined by us
- SC2039

View File

@ -1,98 +1,125 @@
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authorization;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Dbfake;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Areas.Admin.Controllers
{
[Area("Admin")]
public class CarouselController : Controller
{
IHostingEnvironment hosting_env;
private IList<Carousel> Carousels = Dbfake.Carousels;
[Area("Admin")]
[Authorize(Roles = nameof(Roles.Admin) + "," + nameof(Roles.Manager))]
public class CarouselController : Controller
{
IWebHostEnvironment hosting_env;
readonly DBContext dbctx;
public CarouselController(IHostingEnvironment hosting_env){
this.hosting_env = hosting_env;
}
public CarouselController(DBContext dbctx, IWebHostEnvironment hosting_env){
this.dbctx = dbctx;
this.hosting_env = hosting_env;
}
// GET
public IActionResult Select()
{
CarouselViewModel carousel = new CarouselViewModel();
carousel.Carousels = Carousels;
return View(carousel);
}
// GET
public async Task <IActionResult> Select()
{
CarouselViewModel carousel = new CarouselViewModel();
carousel.Carousels = await dbctx.Carousel.ToListAsync();
return View(carousel);
}
public IActionResult Create()
{
return View();
}
public IActionResult Create()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Carousel carousel)
{
carousel.ImageSrc = string.Empty;
MegaUpload mega_upload = new MegaUpload(hosting_env);
await mega_upload.DoMegaUpload(carousel);
[HttpPost]
public async Task<IActionResult> Create(Carousel carousel)
{
if (ModelState.IsValid) {
MegaUpload mega_upload = new MegaUpload(hosting_env.WebRootPath, "carousels", "image");
if ((carousel.ImageSrc = await mega_upload.DoMegaUpload(carousel.Image)) != string.Empty){/* all good here */}
Carousels.Add(carousel);
return RedirectToAction(nameof(Select));
}
dbctx.Carousel.Add(carousel);
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
} else {
string errors = "";
for (int i = 0; i < ModelState.Values.Count(); i++){
for (int j = 0; j < ModelState.Values.ElementAt(i).Errors.Count(); j++){
errors += "\n " + ModelState.Values.ElementAt(i).Errors.ElementAt(j).ToString();
}
}
ViewData["Message"] = $"error creating Carousel => \n{errors}" + errors;
return View(carousel);
}
}
public IActionResult Edit(int id)
{
Carousel carousel_item = Carousels.Where(c_item => c_item.id == id).FirstOrDefault();
if (carousel_item != null)
{
return View(carousel_item);
}
else
{
return NotFound();
}
}
public IActionResult Edit(int id)
{
Carousel carousel_item = dbctx.Carousel.Where(c_item => c_item.id == id).FirstOrDefault();
if (carousel_item != null)
{
ViewData["ImageSrc"] = new SelectList(dbctx.Carousel, "ImageSrc", carousel_item.ImageSrc);
return View(carousel_item);
}
else
{
return NotFound();
}
}
[HttpPost]
public async Task<IActionResult> Edit(Carousel carousel)
{
Carousel carousel_item = Carousels.Where(c_item => c_item.id == carousel.id).FirstOrDefault();
[HttpPost]
public async Task<IActionResult> Edit(Carousel carousel)
{
Carousel carousel_item = dbctx.Carousel.Where(c_item => c_item.id == carousel.id).FirstOrDefault();
if (carousel_item != null)
{
carousel_item.id = carousel.id;
carousel_item.DataTarget = carousel.DataTarget;
carousel_item.ImageAlt = carousel.ImageAlt;
carousel_item.CarouselContent = carousel.CarouselContent;
if (carousel_item != null)
{
if (ModelState.IsValid) {
carousel_item.id = carousel.id;
carousel_item.DataTarget = carousel.DataTarget;
carousel_item.ImageAlt = carousel.ImageAlt;
carousel_item.CarouselContent = carousel.CarouselContent;
MegaUpload mega_upload = new MegaUpload(hosting_env);
if (await mega_upload.DoMegaUpload(carousel)){
carousel_item.ImageSrc = carousel.ImageSrc;
}
return RedirectToAction(nameof(Select));
}
else
{
return NotFound();
}
}
MegaUpload mega_upload = new MegaUpload(hosting_env.WebRootPath, "carousels", "image");
if ((carousel.ImageSrc = await mega_upload.DoMegaUpload(carousel.Image)) != string.Empty){
carousel_item.ImageSrc = carousel.ImageSrc;
}
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
}
string errors = "";
for (int i = 0; i < ModelState.Values.Count(); i++){
for (int j = 0; j < ModelState.Values.ElementAt(i).Errors.Count(); j++){
errors += "\n " + ModelState.Values.ElementAt(i).Errors.ElementAt(j).ToString();
}
}
ViewData["Message"] = "error editing Carousel => \n" + errors;
return View(carousel_item);
}
else
{
return NotFound();
}
}
public IActionResult Delete(int id)
{
Carousel carousel_item = Carousels.Where(c_item => c_item.id == id).FirstOrDefault();
if (carousel_item != null)
{
Carousels.Remove(carousel_item);
return RedirectToAction(nameof(Select));
}
else
{
return NotFound();
}
}
}
public async Task <IActionResult> Delete(int id)
{
Carousel carousel_item = dbctx.Carousel.Where(c_item => c_item.id == id).FirstOrDefault();
if (carousel_item != null)
{
dbctx.Carousel.Remove(carousel_item);
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
}
else
{
return NotFound();
}
}
}
}

View File

@ -0,0 +1,168 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Areas.Admin.Controllers
{
[Area("Admin")]
[Authorize(Roles = nameof(Roles.Admin))]
public class OrderItemController : Controller
{
private readonly DBContext _context;
public OrderItemController(DBContext context)
{
_context = context;
}
// GET: Admin/OrderItem
public async Task<IActionResult> Index()
{
var dBContext = _context.OrderItem.Include(o => o.Order).Include(o => o.Product);
return View(await dBContext.ToListAsync());
}
// GET: Admin/OrderItem/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var orderItem = await _context.OrderItem
.Include(o => o.Order)
.Include(o => o.Product)
.FirstOrDefaultAsync(m => m.id == id);
if (orderItem == null)
{
return NotFound();
}
return View(orderItem);
}
// GET: Admin/OrderItem/Create
public IActionResult Create()
{
ViewData["Order_id"] = new SelectList(_context.Order, "id", "Order_Number");
ViewData["Product_id"] = new SelectList(_context.Product, "id", "Description");
return View();
}
// POST: Admin/OrderItem/Create
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Order_id,Product_id,Amount,Price,id,Created,Updated")] OrderItem orderItem)
{
if (ModelState.IsValid)
{
_context.Add(orderItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
ViewData["Order_id"] = new SelectList(_context.Order, "id", "Order_Number", orderItem.Order_id);
ViewData["Product_id"] = new SelectList(_context.Product, "id", "Description", orderItem.Product_id);
return View(orderItem);
}
// GET: Admin/OrderItem/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var orderItem = await _context.OrderItem.FindAsync(id);
if (orderItem == null)
{
return NotFound();
}
ViewData["Order_id"] = new SelectList(_context.Order, "id", "Order_Number", orderItem.Order_id);
ViewData["Product_id"] = new SelectList(_context.Product, "id", "Description", orderItem.Product_id);
return View(orderItem);
}
// POST: Admin/OrderItem/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Order_id,Product_id,Amount,Price,id,new.Created,new.Updated")] OrderItem orderItem)
{
if (id != orderItem.id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(orderItem);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrderItemExists(orderItem.id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["Order_id"] = new SelectList(_context.Order, "id", "Order_Number", orderItem.Order_id);
ViewData["Product_id"] = new SelectList(_context.Product, "id", "Description", orderItem.Product_id);
return View(orderItem);
}
// GET: Admin/OrderItem/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var orderItem = await _context.OrderItem
.Include(o => o.Order)
.Include(o => o.Product)
.FirstOrDefaultAsync(m => m.id == id);
if (orderItem == null)
{
return NotFound();
}
return View(orderItem);
}
// POST: Admin/OrderItem/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var orderItem = await _context.OrderItem.FindAsync(id);
_context.OrderItem.Remove(orderItem);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool OrderItemExists(int id)
{
return _context.OrderItem.Any(e => e.id == id);
}
}
}

View File

@ -0,0 +1,158 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Rendering;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Areas.Admin.Controllers
{
[Area("Admin")]
[Authorize(Roles = nameof(Roles.Admin) + "," + nameof(Roles.Manager))]
public class OrdersController : Controller
{
private readonly DBContext _context;
public OrdersController(DBContext context)
{
_context = context;
}
// GET: Admin/Orders
public async Task<IActionResult> Index()
{
return View(await _context.Order.ToListAsync());
}
// GET: Admin/Orders/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var order = await _context.Order
.FirstOrDefaultAsync(m => m.id == id);
if (order == null)
{
return NotFound();
}
ViewData["User_id"] = new SelectList(_context.Users, "Id", "Id", order.User_id);
return View(order);
}
// GET: Admin/Orders/Create
public IActionResult Create()
{
return View();
}
// POST: Admin/Orders/Create
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Order_Number,id,User_id,Price_total,Created,Updated")] Order order)
{
if (ModelState.IsValid)
{
_context.Add(order);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(order);
}
// GET: Admin/Orders/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var order = await _context.Order.FindAsync(id);
if (order == null)
{
return NotFound();
}
ViewData["User_id"] = new SelectList(_context.Users, "Id", "Id", order.User_id);
return View(order);
}
// POST: Admin/Orders/Edit/5
// To protect from overposting attacks, enable the specific properties you want to bind to, for
// more details, see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Order_Number,id,User_id,Price_total,new.Created,new.Updated")] Order order)
{
if (id != order.id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(order);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!OrderExists(order.id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
ViewData["User_id"] = new SelectList(_context.Users, "Id", "Id", order.User_id);
return View(order);
}
// GET: Admin/Orders/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var order = await _context.Order
.FirstOrDefaultAsync(m => m.id == id);
if (order == null)
{
return NotFound();
}
return View(order);
}
// POST: Admin/Orders/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var order = await _context.Order.FindAsync(id);
_context.Order.Remove(order);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool OrderExists(int id)
{
return _context.Order.Any(e => e.id == id);
}
}
}

View File

@ -0,0 +1,172 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Authorization;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
using System.Collections.Generic;
namespace pwt_0x01_ng.Areas.Admin.Controllers
{
[Area("Admin")]
[Authorize(Roles = nameof(Roles.Admin) + "," + nameof(Roles.Manager))]
public class ProductController : Controller
{
IWebHostEnvironment hosting_env;
readonly DBContext dbctx;
public ProductController(DBContext dbctx, IWebHostEnvironment hosting_env){
this.dbctx = dbctx;
this.hosting_env = hosting_env;
}
public async Task <IActionResult> Select()
{
ProductViewModel product = new ProductViewModel();
product.Products = await dbctx.Product.ToListAsync();
return View(product);
}
public async Task<IActionResult> Create()
{
ProductViewModel product = new ProductViewModel();
product.Products = await dbctx.Product.ToListAsync();
ViewData["prods"] = product.Products;
return View();
}
[HttpPost]
public async Task<IActionResult> Create(Product product)
{
if (ModelState.IsValid) {
MegaUpload mega_upload = new MegaUpload(hosting_env.WebRootPath, "products", "image");
if ((product.ImageSrc = await mega_upload.DoMegaUpload(product.Image)) != string.Empty){/* all good here */};
dbctx.Product.Add(product);
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
} else {
ViewData["Message"] = "error creating Product";
return View(product);
}
}
public async Task<IActionResult> Edit(int id)
{
ProductViewModel product = new ProductViewModel();
product.Products = await dbctx.Product.ToListAsync();
ViewData["prods"] = product.Products;
Product item = dbctx.Product.Where(p_item => p_item.id == id).FirstOrDefault();
if (item != null)
{
IList<Similar> s = await dbctx.Similar.Where(p_item => p_item.prod_id == id).ToListAsync();
IList<SimilarProduct> similar = new List<SimilarProduct>();
foreach (var prod in product.Products) {
SimilarProduct sp = new SimilarProduct();
sp.id = prod.id;
sp.Selected = prod.Selected;
similar.Add(sp);
}
foreach (var s_item in s)
{
var prodpls = await dbctx.Product.Where(p_item => p_item.id == s_item.similar_prod_id).FirstOrDefaultAsync();
if(prodpls != null){
for (int i = 0; i < similar.Count; i++)
{
if(similar[i].id == prodpls.id){
similar[i].Selected = true;
}
}
}
}
item.Similar = similar;
ViewData["ImageSrc"] = new SelectList(dbctx.Product, "ImageSrc", item.ImageSrc);
return View(item);
}
else
{
return NotFound();
}
}
[HttpPost]
public async Task<IActionResult> Edit(Product product)
{
Product item = dbctx.Product.Where(p_item => p_item.id == product.id).FirstOrDefault();
if (item != null)
{
if (ModelState.IsValid) {
item.id = product.id;
item.Name = product.Name;
item.Price = product.Price;
item.Description = product.Description;
item.ImageAlt = product.ImageAlt;
MegaUpload mega_upload = new MegaUpload(hosting_env.WebRootPath, "products", "image");
if ((product.ImageSrc = await mega_upload.DoMegaUpload(product.Image)) != string.Empty){
item.ImageSrc = product.ImageSrc;
}
if (product.Similar != null) {
IList<SimilarProduct> similar = new List<SimilarProduct>();
foreach (var prod in product.Similar) {
SimilarProduct sp = new SimilarProduct();
sp.id = prod.id;
sp.Selected = prod.Selected;
similar.Add(sp);
}
if(similar.Count > 0){
foreach (var prod in similar)
{
Similar s = dbctx.Similar.Where(p_item => p_item.prod_id == item.id && p_item.similar_prod_id == prod.id).FirstOrDefault();
if(prod.Selected){
if (s == null) {
s = new Similar();
s.prod_id = item.id;
s.similar_prod_id = prod.id;
dbctx.Similar.Add(s);
}
} else {
if (s != null) {
dbctx.Similar.Remove(s);
}
}
}
}
}
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
}
var errors = ModelState.Values.SelectMany(v => v.Errors);
ViewData["Message"] = "error editing Product";
return View(item);
}
else
{
return NotFound();
}
}
public async Task <IActionResult> Delete(int id)
{
Product item = dbctx.Product.Where(p_item => p_item.id == id).FirstOrDefault();
if (item != null)
{
dbctx.Product.Remove(item);
await dbctx.SaveChangesAsync();
return RedirectToAction(nameof(Select));
}
else
{
return NotFound();
}
}
}
}

View File

@ -0,0 +1,153 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
using Microsoft.AspNetCore.Authorization;
namespace pwt_0x01_ng.Areas.Admin.Controllers
{
[Area("Admin")]
[Authorize(Roles = nameof(Roles.Admin))]
public class UsersController : Controller
{
private readonly DBContext _context;
public UsersController(DBContext context)
{
_context = context;
}
// GET: Admin/Users
public async Task<IActionResult> Index()
{
return View(await _context.Users.ToListAsync());
}
// GET: Admin/Users/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users
.FirstOrDefaultAsync(m => m.Id == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// GET: Admin/Users/Create
public IActionResult Create()
{
return View();
}
// POST: Admin/Users/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("Name,LastName,Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount")] User user)
{
if (ModelState.IsValid)
{
_context.Add(user);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
return View(user);
}
// GET: Admin/Users/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users.FindAsync(id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: Admin/Users/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("Name,LastName,Id,UserName,NormalizedUserName,Email,NormalizedEmail,EmailConfirmed,PasswordHash,SecurityStamp,ConcurrencyStamp,TwoFactorEnabled,LockoutEnd,LockoutEnabled,AccessFailedCount")] User user)
{
if (id != user.Id)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(user);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!UserExists(user.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction(nameof(Index));
}
return View(user);
}
// GET: Admin/Users/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var user = await _context.Users
.FirstOrDefaultAsync(m => m.Id == id);
if (user == null)
{
return NotFound();
}
return View(user);
}
// POST: Admin/Users/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var user = await _context.Users.FindAsync(id);
_context.Users.Remove(user);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
private bool UserExists(int id)
{
return _context.Users.Any(e => e.Id == id);
}
}
}

View File

@ -1,6 +1,7 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model Carousel
@{
ViewData["Title"] = "Carousel Creation";
ViewData["Title"] = "Carousel Creation";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
@ -9,3 +10,8 @@
@{ViewBag.Action = "Create";}
@await Html.PartialAsync("PartialForm_EditCreate", Model)
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
}

View File

@ -1,6 +1,7 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model Carousel
@{
ViewData["Title"] = "Carousel Editing";
ViewData["Title"] = "Carousel Editing";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
@ -8,4 +9,9 @@
<p>Edit carousel item.</p>
@{ViewBag.Action = "Edit";}
<partial name="PartialForm_EditCreate" model="Model">
<partial name="PartialForm_EditCreate" model="Model"/>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
}

View File

@ -1,36 +1,41 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model Carousel
@{
if((!String.IsNullOrEmpty(ViewBag.Action))||(!String.IsNullOrWhiteSpace(ViewBag.Action)))
{
<div class="row">
<form asp-action="@ViewBag.Action" enctype="multipart/form-data" method="post">
<div class="form-group col-sm-4">
<div class="form-group">
<label asp-for="@Model.id"></label>
<input asp-for="@Model.id" class="form-control" id="inputCarousel" aria-describedby="carouselHelp" placeholder="id">
</div>
<div class="form-group">
<label asp-for="@Model.DataTarget"></label>
<input asp-for="@Model.DataTarget" class="form-control" id="inputCarousel" aria-describedby="carouselHelp" placeholder="Data Target">
</div>
<div class="form-group">
<label asp-for="@Model.Image"></label>
<input asp-for="@Model.Image" accept="image/*" class="form-inline" id="inputCarousel" aria-describedby="carouselHelp">
</div>
<div class="form-group">
<label asp-for="@Model.ImageAlt"></label>
<input asp-for="@Model.ImageAlt" class="form-control" id="inputCarousel" aria-describedby="carouselHelp" placeholder="Image alt">
</div>
<div class="form-group">
<label asp-for="@Model.CarouselContent"></label>
<input asp-for="@Model.CarouselContent" class="form-control" id="inputCarousel" aria-describedby="carouselHelp" placeholder="Image description">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
}
else {
<h2>Error: Action is not set</h2>
}
if((!String.IsNullOrEmpty(ViewBag.Action))||(!String.IsNullOrWhiteSpace(ViewBag.Action)))
{
<div class="row">
<form asp-action="@ViewBag.Action" enctype="multipart/form-data" method="post">
<div class="form-group col-sm-4">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@{if (@ViewBag.Action == nameof(CarouselController.Edit)){
<input type="hidden" asp-for="@Model.id" class="form-control" aria-describedby="carousel id edit">
}}
<div class="form-group">
<label asp-for="@Model.DataTarget"></label>
<input asp-for="@Model.DataTarget" class="form-control" aria-describedby="css selector id of the image" placeholder="Data Target">
<span asp-validation-for="@Model.DataTarget" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.Image"></label>
<input id="file" asp-for="@Model.Image" accept="image/*" class="form-inline" aria-describedby="the image">
<span asp-validation-for="@Model.Image" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.ImageAlt"></label>
<input asp-for="@Model.ImageAlt" class="form-control" aria-describedby="image alt text" placeholder="Image alt">
<span asp-validation-for="@Model.ImageAlt" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.CarouselContent"></label>
<textarea asp-for="@Model.CarouselContent" class="form-control" aria-describedby="image description" placeholder="Image description"></textarea>
<span asp-validation-for="@Model.CarouselContent" class="text-danger"></span>
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
}
else {
<h2>Error: Action is not set</h2>
}
}

View File

@ -1,6 +1,6 @@
@model CarouselViewModel
@{
ViewData["Title"] = "Carousel Select";
ViewData["Title"] = "Carousel Select";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
@ -11,40 +11,35 @@
<br/><br/>
@{
if (Model?.Carousels != null && Model.Carousels.Count > 0) {
<table style="width:100%">
<tr>
<th class="col-sm-1">@Html.DisplayNameFor(model => model.Carousels[0].id)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Carousels[0].DataTarget)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Carousels[0].ImageSrc)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Carousels[0].ImageAlt)</th>
<th class="col-sm-4">@Html.DisplayNameFor(model => model.Carousels[0].CarouselContent)</th>
<th class="col-sm-1">Edit</th>
<th class="col-sm-1">Delete</th>
</tr>
@{
foreach (var carousel_item in Model.Carousels)
{
<tr>
<td class="col-sm-1">@carousel_item.id</td>
<td class="col-sm-2">@carousel_item.DataTarget</td>
<td class="col-sm-2">@carousel_item.ImageSrc</td>
<td class="col-sm-2">@carousel_item.ImageAlt</td>
<td class="col-sm-4">@carousel_item.CarouselContent</td>
<td class="col-sm-2"><a asp-action="Edit" asp-route-id="@carousel_item.id">Edit</a></td>
<td class="col-sm-2"><a asp-action="Delete" asp-route-id="@carousel_item.id" onclick="return c_item_deletion_confirmation();">Delete</a></td>
</tr>
}
}
</table>
}
else
{
<h2>Carousel is empty</h2>
}
}
@section Scripts
{
<script src="~/js/ultimate_script.js"></script>
if (Model?.Carousels != null && Model.Carousels.Count > 0) {
<table id="carousel_table" class="table table-responsive table-striped table-bordered">
<tr>
<th class="col-sm-1">@Html.DisplayNameFor(model => model.Carousels[0].id)</th>
<th class="col-sm-1">@Html.DisplayNameFor(model => model.Carousels[0].DataTarget)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Carousels[0].ImageSrc)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Carousels[0].ImageAlt)</th>
<th class="col-sm-4">@Html.DisplayNameFor(model => model.Carousels[0].CarouselContent)</th>
<th class="col-sm-1">Edit</th>
<th class="col-sm-1">Delete</th>
</tr>
@{
foreach (var carousel_item in Model.Carousels)
{
<tr>
<td class="col-sm-1">@carousel_item.id</td>
<td class="col-sm-1">@carousel_item.DataTarget</td>
<td class="col-sm-2">@carousel_item.ImageSrc</td>
<td class="col-sm-2">@carousel_item.ImageAlt</td>
<td class="col-sm-4">@carousel_item.CarouselContent</td>
<td class="col-sm-1"><a asp-action="Edit" asp-route-id="@carousel_item.id">Edit</a></td>
<td class="col-sm-1"><a asp-action="Delete" asp-route-id="@carousel_item.id" onclick="return item_deletion_confirmation();">Delete</a></td>
</tr>
}
}
</table>
}
else
{
<h2>Carousel is empty</h2>
}
}

View File

@ -0,0 +1,42 @@
@model pwt_0x01_ng.Models.OrderItem
@{
ViewData["Title"] = "Create";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h4>OrderItem</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Order_id" class="control-label"></label>
<select asp-for="Order_id" class ="form-control" asp-items="ViewBag.Order_id"></select>
</div>
<div class="form-group">
<label asp-for="Product_id" class="control-label"></label>
<select asp-for="Product_id" class ="form-control" asp-items="ViewBag.Product_id"></select>
</div>
<div class="form-group">
<label asp-for="Amount" class="control-label"></label>
<input asp-for="Amount" class="form-control" />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,57 @@
@model pwt_0x01_ng.Models.OrderItem
@{
ViewData["Title"] = "Delete";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>OrderItem</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Amount)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Amount)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Order)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Order.Order_Number)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Product)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Product.Description)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Created)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Created)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Updated)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Updated)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>

View File

@ -0,0 +1,54 @@
@model pwt_0x01_ng.Models.OrderItem
@{
ViewData["Title"] = "Details";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<div>
<h4>OrderItem</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Amount)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Amount)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Order)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Order.Order_Number)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Product)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Product.Description)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Created)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Created)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Updated)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Updated)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,45 @@
@model pwt_0x01_ng.Models.OrderItem
@{
ViewData["Title"] = "Edit";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h4>OrderItem</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Order_id" class="control-label"></label>
<select asp-for="Order_id" class="form-control" asp-items="ViewBag.Order_id"></select>
<span asp-validation-for="Order_id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Product_id" class="control-label"></label>
<select asp-for="Product_id" class="form-control" asp-items="ViewBag.Product_id"></select>
<span asp-validation-for="Product_id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Amount" class="control-label"></label>
<input asp-for="Amount" class="form-control" />
<span asp-validation-for="Amount" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price" class="control-label"></label>
<input asp-for="Price" class="form-control" />
<span asp-validation-for="Price" class="text-danger"></span>
</div>
<input type="hidden" asp-for="id" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,65 @@
@model IEnumerable<pwt_0x01_ng.Models.OrderItem>
@{
ViewData["Title"] = "OrderItem";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table table-responsive table-striped table-bordered">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Amount)
</th>
<th>
@Html.DisplayNameFor(model => model.Price)
</th>
<th>
@Html.DisplayNameFor(model => model.Order)
</th>
<th>
@Html.DisplayNameFor(model => model.Product)
</th>
<th>
@Html.DisplayNameFor(model => model.Created)
</th>
<th>
@Html.DisplayNameFor(model => model.Updated)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Amount)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price)
</td>
<td>
@Html.DisplayFor(modelItem => item.Order.Order_Number)
</td>
<td>
@Html.DisplayFor(modelItem => item.Product.Description)
</td>
<td>
@Html.DisplayFor(modelItem => item.Created)
</td>
<td>
@Html.DisplayFor(modelItem => item.Updated)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

View File

@ -0,0 +1,34 @@
@model pwt_0x01_ng.Models.Order
@{
ViewData["Title"] = "Create";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h4>Order</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Order_Number" class="control-label"></label>
<input asp-for="Order_Number" class="form-control" />
<span asp-validation-for="Order_Number" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="User_id" class="control-label"></label>
<input asp-for="User_id" class="form-control" />
<span asp-validation-for="User_id" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,51 @@
@model pwt_0x01_ng.Models.Order
@{
ViewData["Title"] = "Delete";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Order</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Order_Number)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Order_Number)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.User_id)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.User_id)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price_total)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price_total)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Created)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Created)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Updated)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Updated)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="id" />
<input type="submit" value="Delete" class="btn btn-danger" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>

View File

@ -0,0 +1,48 @@
@model pwt_0x01_ng.Models.Order
@{
ViewData["Title"] = "Details";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<div>
<h4>Order</h4>
<hr />
<dl class="row">
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Order_Number)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Order_Number)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.User_id)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.User_id)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Price_total)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Price_total)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Created)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Created)
</dd>
<dt class = "col-sm-2">
@Html.DisplayNameFor(model => model.Updated)
</dt>
<dd class = "col-sm-10">
@Html.DisplayFor(model => model.Updated)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,40 @@
@model pwt_0x01_ng.Models.Order
@{
ViewData["Title"] = "Edit";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h4>Order</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Order_Number" class="control-label"></label>
<input asp-for="Order_Number" class="form-control" />
<span asp-validation-for="Order_Number" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="User_id" class="control-label"></label>
<input asp-for="User_id" class="form-control" />
<span asp-validation-for="User_id" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Price_total" class="control-label"></label>
<input asp-for="Price_total" class="form-control" />
<span asp-validation-for="Price_total" class="text-danger"></span>
</div>
<input type="hidden" asp-for="id" />
<div class="form-group">
<input type="submit" value="Save" class="btn btn-primary" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,58 @@
@model IEnumerable<pwt_0x01_ng.Models.Order>
@{
ViewData["Title"] = "Order Index";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table table-responsive table-striped table-bordered">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Order_Number)
</th>
<th>
@Html.DisplayNameFor(model => model.User_id)
</th>
<th>
@Html.DisplayNameFor(model => model.Price_total)
</th>
<th>
@Html.DisplayNameFor(model => model.Created)
</th>
<th>
@Html.DisplayNameFor(model => model.Updated)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Order_Number)
</td>
<td>
@Html.DisplayFor(modelItem => item.User_id)
</td>
<td>
@Html.DisplayFor(modelItem => item.Price_total)
</td>
<td>
@Html.DisplayFor(modelItem => item.Created)
</td>
<td>
@Html.DisplayFor(modelItem => item.Updated)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

View File

@ -0,0 +1,16 @@
@model Product
@{
ViewData["Title"] = "Product - Create";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Create new item.</p>
@{ViewBag.Action = "Create";}
@await Html.PartialAsync("edit-create_part", Model)
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
}

View File

@ -0,0 +1,16 @@
@model Product
@{
ViewData["Title"] = "Product - Edit";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Edit product.</p>
@{ViewBag.Action = "Edit";}
<partial name="edit-create_part" model="Model"/>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
<script defer src="~/js/validation/file_type.js"></script>
}

View File

@ -0,0 +1,46 @@
@model ProductViewModel
@{
ViewData["Title"] = "Products";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>All product data.</p>
<a asp-action="Create">Create new item</a>
<br/><br/>
@{
if (Model?.Products != null && Model.Products.Count > 0) {
<table id="product_table" class="table table-responsive table-striped table-bordered">
<tr>
<th class="col-sm">@Html.DisplayNameFor(model => model.Products[0].id)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Products[0].Name)</th>
<th class="col-sm-1">@Html.DisplayNameFor(model => model.Products[0].Price)</th>
<th class="col-sm-3">@Html.DisplayNameFor(model => model.Products[0].Description)</th>
<th class="col-sm-3">@Html.DisplayNameFor(model => model.Products[0].ImageSrc)</th>
<th class="col-sm-2">@Html.DisplayNameFor(model => model.Products[0].ImageAlt)</th>
<th class="col-sm">Edit</th>
<th class="col-sm">Delete</th>
</tr>
@{
foreach (var item in Model.Products)
{
<tr>
<td class="col-sm">@item.id</td>
<td class="col-sm-2">@item.Name</td>
<td class="col-sm-1">@item.Price</td>
<td class="col-sm-3">@item.Description</td>
<td class="col-sm-3">@item.ImageSrc</td>
<td class="col-sm-2">@item.ImageAlt</td>
<td class="col-sm"><a asp-action="Edit" asp-route-id="@item.id">Edit</a></td>
<td class="col-sm"><a asp-action="Delete" asp-route-id="@item.id" onclick="return item_deletion_confirmation();">Delete</a></td>
</tr>
}
}
</table>
}
else
{
<h2>No products to show</h2>
}
}

View File

@ -0,0 +1,79 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model Product
@{
IList<Product> products;
IList<SimilarProduct> similar;
products = (IList<Product>)ViewData["prods"];
similar = new List<SimilarProduct>();
if((!String.IsNullOrEmpty(ViewBag.Action))||(!String.IsNullOrWhiteSpace(ViewBag.Action)))
{
<div class="row">
<form asp-action="@ViewBag.Action" enctype="multipart/form-data" method="post">
<div class="form-group col-sm-4">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
@{
if (@ViewBag.Action == nameof(ProductController.Edit)){
<input type="hidden" asp-for="@Model.id" class="form-control">
}
}
<div class="form-group">
<label asp-for="@Model.Name"></label>
<input asp-for="@Model.Name" class="form-control" aria-describedby="product name" placeholder="product name">
<span asp-validation-for="@Model.Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.Price"></label>
<input asp-for="@Model.Price" class="form-control" aria-describedby="product price" placeholder="price">
<span asp-validation-for="@Model.Price" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.Description"></label>
<input asp-for="@Model.Description" class="form-control" aria-describedby="product description" placeholder="description">
<span asp-validation-for="@Model.Description" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.Image"></label>
<input id="file" asp-for="@Model.Image" accept="image/*" class="form-inline" aria-describedby="product image">
<span asp-validation-for="@Model.Image" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="@Model.ImageAlt"></label>
<input asp-for="@Model.ImageAlt" class="form-control" aria-describedby="product image alt text" placeholder="Image alt">
<span asp-validation-for="@Model.ImageAlt" class="text-danger"></span>
</div>
@{
if(@Model != null && products != null && products.Count > 0){
<div class="form-group form-check">
<div>
<label>similar products</label>
</div>
@if(@Model.Similar != null){
similar = @Model.Similar;
}
@for(int i = 0; i < products.Count; i++){
@if(products.ElementAt(i).id != @Model.id){
<label class="form-check-label checkbox-inline">@products.ElementAt(i).Name</label>
@if(similar[i].Selected){
<input asp-for="@Model.Similar[i].Selected" type="checkbox" checked>
<input asp-for="@Model.Similar[i].id" type="hidden">
} else {
<input asp-for="@Model.Similar[i].Selected" type="checkbox">
<input asp-for="@Model.Similar[i].id" type="hidden">
}
} else {
<input asp-for="@Model.Similar[i].Selected" type="checkbox" hidden>
<input asp-for="@Model.Similar[i].id" type="hidden">
}
}
</div>
}
}
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</form>
</div>
}
else {
<h2>Error: Action is not set</h2>
}
}

View File

@ -0,0 +1,104 @@
@model pwt_0x01_ng.Models.Identity.User
@{
ViewData["Title"] = "Create";
}
<h2>Create</h2>
<h4>User</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Create">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NormalizedUserName" class="control-label"></label>
<input asp-for="NormalizedUserName" class="form-control" />
<span asp-validation-for="NormalizedUserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NormalizedEmail" class="control-label"></label>
<input asp-for="NormalizedEmail" class="form-control" />
<span asp-validation-for="NormalizedEmail" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="EmailConfirmed" /> @Html.DisplayNameFor(model => model.EmailConfirmed)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="PasswordHash" class="control-label"></label>
<input asp-for="PasswordHash" class="form-control" />
<span asp-validation-for="PasswordHash" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SecurityStamp" class="control-label"></label>
<input asp-for="SecurityStamp" class="form-control" />
<span asp-validation-for="SecurityStamp" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConcurrencyStamp" class="control-label"></label>
<input asp-for="ConcurrencyStamp" class="form-control" />
<span asp-validation-for="ConcurrencyStamp" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="TwoFactorEnabled" /> @Html.DisplayNameFor(model => model.TwoFactorEnabled)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="LockoutEnd" class="control-label"></label>
<input asp-for="LockoutEnd" class="form-control" />
<span asp-validation-for="LockoutEnd" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="LockoutEnabled" /> @Html.DisplayNameFor(model => model.LockoutEnabled)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="AccessFailedCount" class="control-label"></label>
<input asp-for="AccessFailedCount" class="form-control" />
<span asp-validation-for="AccessFailedCount" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -0,0 +1,105 @@
@model pwt_0x01_ng.Models.Identity.User
@{
ViewData["Title"] = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>User</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.UserName)
</dt>
<dd>
@Html.DisplayFor(model => model.UserName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.NormalizedUserName)
</dt>
<dd>
@Html.DisplayFor(model => model.NormalizedUserName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.NormalizedEmail)
</dt>
<dd>
@Html.DisplayFor(model => model.NormalizedEmail)
</dd>
<dt>
@Html.DisplayNameFor(model => model.EmailConfirmed)
</dt>
<dd>
@Html.DisplayFor(model => model.EmailConfirmed)
</dd>
<dt>
@Html.DisplayNameFor(model => model.PasswordHash)
</dt>
<dd>
@Html.DisplayFor(model => model.PasswordHash)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SecurityStamp)
</dt>
<dd>
@Html.DisplayFor(model => model.SecurityStamp)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ConcurrencyStamp)
</dt>
<dd>
@Html.DisplayFor(model => model.ConcurrencyStamp)
</dd>
<dt>
@Html.DisplayNameFor(model => model.TwoFactorEnabled)
</dt>
<dd>
@Html.DisplayFor(model => model.TwoFactorEnabled)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LockoutEnd)
</dt>
<dd>
@Html.DisplayFor(model => model.LockoutEnd)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LockoutEnabled)
</dt>
<dd>
@Html.DisplayFor(model => model.LockoutEnabled)
</dd>
<dt>
@Html.DisplayNameFor(model => model.AccessFailedCount)
</dt>
<dd>
@Html.DisplayFor(model => model.AccessFailedCount)
</dd>
</dl>
<form asp-action="Delete">
<input type="hidden" asp-for="Id" />
<input type="submit" value="Delete" class="btn btn-default" /> |
<a asp-action="Index">Back to List</a>
</form>
</div>

View File

@ -0,0 +1,102 @@
@model pwt_0x01_ng.Models.Identity.User
@{
ViewData["Title"] = "Details";
}
<h2>Details</h2>
<div>
<h4>User</h4>
<hr />
<dl class="dl-horizontal">
<dt>
@Html.DisplayNameFor(model => model.Name)
</dt>
<dd>
@Html.DisplayFor(model => model.Name)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
@Html.DisplayFor(model => model.LastName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.UserName)
</dt>
<dd>
@Html.DisplayFor(model => model.UserName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.NormalizedUserName)
</dt>
<dd>
@Html.DisplayFor(model => model.NormalizedUserName)
</dd>
<dt>
@Html.DisplayNameFor(model => model.Email)
</dt>
<dd>
@Html.DisplayFor(model => model.Email)
</dd>
<dt>
@Html.DisplayNameFor(model => model.NormalizedEmail)
</dt>
<dd>
@Html.DisplayFor(model => model.NormalizedEmail)
</dd>
<dt>
@Html.DisplayNameFor(model => model.EmailConfirmed)
</dt>
<dd>
@Html.DisplayFor(model => model.EmailConfirmed)
</dd>
<dt>
@Html.DisplayNameFor(model => model.PasswordHash)
</dt>
<dd>
@Html.DisplayFor(model => model.PasswordHash)
</dd>
<dt>
@Html.DisplayNameFor(model => model.SecurityStamp)
</dt>
<dd>
@Html.DisplayFor(model => model.SecurityStamp)
</dd>
<dt>
@Html.DisplayNameFor(model => model.ConcurrencyStamp)
</dt>
<dd>
@Html.DisplayFor(model => model.ConcurrencyStamp)
</dd>>
<dt>
@Html.DisplayNameFor(model => model.TwoFactorEnabled)
</dt>
<dd>
@Html.DisplayFor(model => model.TwoFactorEnabled)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LockoutEnd)
</dt>
<dd>
@Html.DisplayFor(model => model.LockoutEnd)
</dd>
<dt>
@Html.DisplayNameFor(model => model.LockoutEnabled)
</dt>
<dd>
@Html.DisplayFor(model => model.LockoutEnabled)
</dd>
<dt>
@Html.DisplayNameFor(model => model.AccessFailedCount)
</dt>
<dd>
@Html.DisplayFor(model => model.AccessFailedCount)
</dd>
</dl>
</div>
<div>
<a asp-action="Edit" asp-route-id="@Model.Id">Edit</a> |
<a asp-action="Index">Back to List</a>
</div>

View File

@ -0,0 +1,117 @@
@model pwt_0x01_ng.Models.Identity.User
@{
ViewData["Title"] = "Edit";
}
<h2>Edit</h2>
<h4>User</h4>
<hr />
<div class="row">
<div class="col-md-4">
<form asp-action="Edit">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<div class="form-group">
<label asp-for="Name" class="control-label"></label>
<input asp-for="Name" class="form-control" />
<span asp-validation-for="Name" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="LastName" class="control-label"></label>
<input asp-for="LastName" class="form-control" />
<span asp-validation-for="LastName" class="text-danger"></span>
</div>
<input type="hidden" asp-for="Id" />
<div class="form-group">
<label asp-for="UserName" class="control-label"></label>
<input asp-for="UserName" class="form-control" />
<span asp-validation-for="UserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NormalizedUserName" class="control-label"></label>
<input asp-for="NormalizedUserName" class="form-control" />
<span asp-validation-for="NormalizedUserName" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Email" class="control-label"></label>
<input asp-for="Email" class="form-control" />
<span asp-validation-for="Email" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="NormalizedEmail" class="control-label"></label>
<input asp-for="NormalizedEmail" class="form-control" />
<span asp-validation-for="NormalizedEmail" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="EmailConfirmed" /> @Html.DisplayNameFor(model => model.EmailConfirmed)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="PasswordHash" class="control-label"></label>
<input asp-for="PasswordHash" class="form-control" />
<span asp-validation-for="PasswordHash" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="SecurityStamp" class="control-label"></label>
<input asp-for="SecurityStamp" class="form-control" />
<span asp-validation-for="SecurityStamp" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="ConcurrencyStamp" class="control-label"></label>
<input asp-for="ConcurrencyStamp" class="form-control" />
<span asp-validation-for="ConcurrencyStamp" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="PhoneNumber" class="control-label"></label>
<input asp-for="PhoneNumber" class="form-control" />
<span asp-validation-for="PhoneNumber" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="PhoneNumberConfirmed" /> @Html.DisplayNameFor(model => model.PhoneNumberConfirmed)
</label>
</div>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="TwoFactorEnabled" /> @Html.DisplayNameFor(model => model.TwoFactorEnabled)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="LockoutEnd" class="control-label"></label>
<input asp-for="LockoutEnd" class="form-control" />
<span asp-validation-for="LockoutEnd" class="text-danger"></span>
</div>
<div class="form-group">
<div class="checkbox">
<label>
<input asp-for="LockoutEnabled" /> @Html.DisplayNameFor(model => model.LockoutEnabled)
</label>
</div>
</div>
<div class="form-group">
<label asp-for="AccessFailedCount" class="control-label"></label>
<input asp-for="AccessFailedCount" class="form-control" />
<span asp-validation-for="AccessFailedCount" class="text-danger"></span>
</div>
<div class="form-group">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</form>
</div>
</div>
<div>
<a asp-action="Index">Back to List</a>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -0,0 +1,113 @@
@model IEnumerable<pwt_0x01_ng.Models.Identity.User>
@{
ViewData["Title"] = "Index";
}
<h2>Index</h2>
<p>
<a asp-action="Create">Create New</a>
</p>
<table class="table table-responsive table-striped table-bordered">
<thead>
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.LastName)
</th>
<th>
@Html.DisplayNameFor(model => model.UserName)
</th>
<th>
@Html.DisplayNameFor(model => model.NormalizedUserName)
</th>
<th>
@Html.DisplayNameFor(model => model.Email)
</th>
<th>
@Html.DisplayNameFor(model => model.NormalizedEmail)
</th>
<th>
@Html.DisplayNameFor(model => model.EmailConfirmed)
</th>
<th>
@Html.DisplayNameFor(model => model.PasswordHash)
</th>
<th>
@Html.DisplayNameFor(model => model.SecurityStamp)
</th>
<th>
@Html.DisplayNameFor(model => model.ConcurrencyStamp)
</th>
<th>
@Html.DisplayNameFor(model => model.TwoFactorEnabled)
</th>
<th>
@Html.DisplayNameFor(model => model.LockoutEnd)
</th>
<th>
@Html.DisplayNameFor(model => model.LockoutEnabled)
</th>
<th>
@Html.DisplayNameFor(model => model.AccessFailedCount)
</th>
<th></th>
</tr>
</thead>
<tbody>
@foreach (var item in Model) {
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
@Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
@Html.DisplayFor(modelItem => item.NormalizedUserName)
</td>
<td>
@Html.DisplayFor(modelItem => item.Email)
</td>
<td>
@Html.DisplayFor(modelItem => item.NormalizedEmail)
</td>
<td>
@Html.DisplayFor(modelItem => item.EmailConfirmed)
</td>
<td>
@Html.DisplayFor(modelItem => item.PasswordHash)
</td>
<td>
@Html.DisplayFor(modelItem => item.SecurityStamp)
</td>
<td>
@Html.DisplayFor(modelItem => item.ConcurrencyStamp)
</td>
<td>
@Html.DisplayFor(modelItem => item.TwoFactorEnabled)
</td>
<td>
@Html.DisplayFor(modelItem => item.LockoutEnd)
</td>
<td>
@Html.DisplayFor(modelItem => item.LockoutEnabled)
</td>
<td>
@Html.DisplayFor(modelItem => item.AccessFailedCount)
</td>
<td>
<a asp-action="Edit" asp-route-id="@item.Id">Edit</a> |
<a asp-action="Details" asp-route-id="@item.Id">Details</a> |
<a asp-action="Delete" asp-route-id="@item.Id">Delete</a>
</td>
</tr>
}
</tbody>
</table>

View File

@ -1,3 +1,3 @@
@{
Layout = "_Layout";
Layout = "_Layout";
}

View File

@ -0,0 +1,118 @@
using pwt_0x01_ng.Controllers;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.ApplicationServices;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace pwt_0x01_ng.Areas.Customer.Controllers
{
[Area("Customer")]
[Authorize(Roles = nameof(Roles.Customer))]
public class CustomerOrderNotCartController : Controller
{
const string s_price_total = "Price_total";
const string s_order_items = "OrderItems";
ISecurityApplicationService isas;
DBContext ctx;
public CustomerOrderNotCartController(ISecurityApplicationService isas, DBContext ctx)
{
this.isas = isas;
this.ctx = ctx;
}
[HttpPost]
public int AddOrderItemsToSession(int? Product_id)
{
int price_total = 0;
if (HttpContext.Session.IsAvailable)
{
price_total = HttpContext.Session.GetInt32(s_price_total).GetValueOrDefault();
}
Product product = ctx.Product.Where(prod => prod.id == Product_id).FirstOrDefault();
if (product != null)
{
OrderItem order_item = new OrderItem()
{
Product_id = product.id,
Product = product,
Amount = 1,
Price = product.Price
};
if (HttpContext.Session.IsAvailable)
{
List<OrderItem> orderItems = HttpContext.Session.GetObject<List<OrderItem>>(s_order_items);
OrderItem orderItemInSession = null;
if (orderItems != null){
orderItemInSession = orderItems.Find(oi => oi.Product_id == order_item.Product_id);
}
else
orderItems = new List<OrderItem>();
if (orderItemInSession != null)
{
++orderItemInSession.Amount;
orderItemInSession.Price += order_item.Product.Price;
}
else
{
orderItems.Add(order_item);
}
HttpContext.Session.SetObject(s_order_items, orderItems);
price_total += order_item.Product.Price;
HttpContext.Session.SetInt32(s_price_total, price_total);
}
}
return price_total;
}
public async Task<IActionResult> ApproveOrderInSession()
{
if (HttpContext.Session.IsAvailable)
{
int price_total = 0;
List<OrderItem> order_items = HttpContext.Session.GetObject<List<OrderItem>>(s_order_items);
if (order_items != null)
{
foreach (OrderItem order_item in order_items)
{
price_total += order_item.Product.Price * order_item.Amount;
order_item.Product = null;
}
User currentUser = await isas.gimme_current_user(User);
Order order = new Order()
{
Order_Number = Convert.ToBase64String(Guid.NewGuid().ToByteArray()),
Price_total = price_total,
OrderItems = order_items,
User_id = currentUser.Id
};
await ctx.AddAsync(order);
await ctx.SaveChangesAsync();
HttpContext.Session.Remove(s_order_items);
HttpContext.Session.Remove(s_price_total);
return RedirectToAction(nameof(CustomerOrdersController.Index), nameof(CustomerOrdersController).Replace("Controller", ""), new { Area = nameof(Customer) });
}
}
return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Replace("Controller", ""), new { Area = "" });
}
}
}

View File

@ -0,0 +1,49 @@
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.ApplicationServices;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.Identity;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace pwt_0x01_ng.Areas.Customer.Controllers
{
[Area("Customer")]
[Authorize(Roles = nameof(Roles.Customer))]
public class CustomerOrdersController : Controller
{
ISecurityApplicationService isas;
DBContext ctx;
public CustomerOrdersController(ISecurityApplicationService isas, DBContext ctx)
{
this.isas = isas;
this.ctx = ctx;
}
public async Task<IActionResult> Index()
{
if (User.Identity.IsAuthenticated)
{
User currentUser = await isas.gimme_current_user(User);
if (currentUser != null)
{
IList<Order> userOrders = await this.ctx.Order
.Where(or => or.User_id == currentUser.Id)
.Include(o => o.OrderItems)
.ThenInclude(oi => oi.Product)
.ToListAsync();
string uname = currentUser.username;
ViewData["uname"] = uname;
return View(userOrders);
}
}
return NotFound();
}
}
}

View File

@ -0,0 +1,62 @@
@model IList<Order>;
@{
ViewData["Title"] = "My Orders";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
@{
if (Model != null && Model != null && Model.Count > 0)
{
foreach (var item in Model)
{
<table id="myorders_table" class="table table-responsive table-striped table-bordered">
<tr>
<th class="col-sm-1">@nameof(Order.id)</th>
<th class="col-sm-2">@nameof(Order.Order_Number)</th>
<th class="col-sm-2">@nameof(Order.Price_total)</th>
<th class="col-sm-2">@nameof(Order.Created)</th>
<th class="col-sm-2">@nameof(Order.Updated)</th>
<th class="col-sm-3">username</th>
</tr>
<tr>
<td class="col-sm-1">@item.id</td>
<td class="col-sm-2">@item.Order_Number</td>
<td class="col-sm-2">@item.Price_total</td>
<td class="col-sm-2">@item.Created</td>
<td class="col-sm-2">@item.Updated</td>
<td class="col-sm-3">@ViewData["uname"]</td>
</tr>
</table>
<details id="details_summary">
<summary class="btn-link">View Details</summary>
<div>
<h4>Order Items</h4>
<table id="order_details_table" class="table table-responsive table-bordered">
<tr>
<th class="col-sm-3">@nameof(Product.Name)</th>
<th class="col-sm-1">@nameof(OrderItem.Amount)</th>
<th class="col-sm-1">@nameof(OrderItem.Price)</th>
</tr>
@{
foreach (var order_items_item in item.OrderItems)
{
<tr>
<td class="col-sm-3">@order_items_item.Product.Name</td>
<td class="col-sm-1">@order_items_item.Amount</td>
<td class="col-sm-1">@order_items_item.Price</td>
</tr>
}
}
</table>
</div>
</details>
}
}
else
{
<h2>Orders are empty!</h2>
}
}

View File

@ -0,0 +1,3 @@
@using pwt_0x01_ng
@using pwt_0x01_ng.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -0,0 +1,76 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using pwt_0x01_ng.Models.ApplicationServices;
using pwt_0x01_ng.Controllers;
using pwt_0x01_ng.Models;
namespace pwt_0x01_ng.Areas.Security.Controllers
{
[Area("Security")]
[AllowAnonymous]
public class AccountController : Controller
{
private ISecurityApplicationService isas;
public AccountController(ISecurityApplicationService isas){
this.isas = isas;
}
public IActionResult Login()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Login(LoginViewModel lvm)
{
lvm.login_failed = false;
if(ModelState.IsValid){
bool login_success = await isas.login(lvm);
if(login_success){
return RedirectToAction(nameof(HomeController.Index), nameof(HomeController).Replace("Controller", String.Empty), new {area = ""});
} else {
lvm.login_failed = true;
}
}
return View(lvm);
}
public IActionResult Logout()
{
isas.logout();
return RedirectToRoute(new { action = nameof(HomeController.Index), controller = nameof(HomeController).Replace("Controller", String.Empty), area="" });
}
public IActionResult Register()
{
return View();
}
[HttpPost]
public async Task<IActionResult> Register(RegisterViewModel rvm)
{
string[] errors = null;
if(ModelState.IsValid){
errors = await isas.register(rvm, Models.Identity.Roles.Customer);
if(errors == null){
var lvm = new LoginViewModel(){username = rvm.username, password = rvm.password, login_failed = false};
return await Login(lvm);
}
Console.WriteLine("error registering user {" + rvm.username + "," + rvm.email + "," + rvm.password + "}.");
}
string err = "";
for (int i = 0; i < ModelState.Values.Count(); i++){
for (int j = 0; j < ModelState.Values.ElementAt(i).Errors.Count(); j++){
err += "\n " + ModelState.Values.ElementAt(i).Errors.ElementAt(j).ToString();
}
}
Console.WriteLine("ModelState errors: " + err);
ViewData["Message"] = "error registering an account => \n" + err;
return View();
}
}
}

View File

@ -0,0 +1,57 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model LoginViewModel
@{
ViewData["Title"] = "Login";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<div class="container">
<div class="login">
<h3>Login or <a href="/Security/Account/Register">Sign up</a></h3>
<div class="row row-xs-offset-1 row-sm-offset-2">
<div class="col-xs-2 col-sm-2">
<a href="#" class="btn btn-lg btn-block btn-facebook" data-toggle="tooltip" data-placement="top" title="Facebook">
<i class="fa fa-facebook fa-1x"></i>
</a>
</div>
<div class="col-xs-2 col-sm-2">
<a href="#" class="btn btn-lg btn-block btn-twitter" data-toggle="tooltip" data-placement="top" title="Twitter">
<i class="fa fa-twitter fa-1x"></i>
</a>
</div>
<div class="col-xs-2 col-sm-2">
<a href="#" class="btn btn-lg btn-block btn-github" data-toggle="tooltip" data-placement="top" title="GitHub">
<i class="fa fa-github fa-1x"></i>
</a>
</div>
</div>
<br>
<div class="row row-sm-offset-3">
<div class="col-xs-12 col-sm-6"><hr></div>
</div>
<div class="row row-sm-offset-3">
<div class="form-group col-xs-12 col-sm-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form class="loginForm" action="" enctype="multipart/form-data" method="post">
<span asp-validation-for="@Model.username" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-user"></span></span>
<input asp-for="@Model.username" type="text" class="form-control" name="username" placeholder="Username">
</div>
<br>
<span asp-validation-for="@Model.password" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-key"></span></span>
<input asp-for="@Model.password" type="password" class="form-control" name="password" placeholder="Password">
</div>
<hr>
<button class="btn btn-lg btn-outline-primary" type="submit"><i class="fa fa-sign-in"></i> Login</button>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -0,0 +1,48 @@
@using pwt_0x01_ng.Areas.Admin.Controllers;
@model RegisterViewModel
@{
ViewData["Title"] = "Register";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<div class="container">
<div class="row">
<br>
<div class="row row-sm-offset-3">
<div class="form-group col-xs-12 col-sm-6">
<div asp-validation-summary="ModelOnly" class="text-danger"></div>
<form class="signupForm" action="" enctype="multipart/form-data" method="post">
<span asp-validation-for="@Model.username" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-user"></span></span>
<input asp-for="@Model.username" type="text" class="form-control" name="username" placeholder="Username">
</div>
<br>
<span asp-validation-for="@Model.email" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-envelope"></span></span>
<input asp-for="@Model.email" type="text" class="form-control" name="email" placeholder="Email">
</div>
<hr>
<span asp-validation-for="@Model.password" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-key"></span></span>
<input asp-for="@Model.password" type="password" class="form-control" name="password" placeholder="Password">
</div>
<br>
<span asp-validation-for="@Model.repeated_password" class="text-danger"></span>
<div class="input-group">
<span class="input-group-addon"><span class="fa fa-key"></span></span>
<input asp-for="@Model.repeated_password" type="password" class="form-control" name="repeated_password" placeholder="Repeated password">
</div>
<hr>
<button class="btn btn-lg btn-outline-primary" type="submit"><i class="fa fa-sign-in"></i> Register</button>
</form>
</div>
</div>
</div>
</div>
@section Scripts {
@{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}

View File

@ -0,0 +1,3 @@
@using pwt_0x01_ng
@using pwt_0x01_ng.Models
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

View File

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

View File

@ -1,48 +1,73 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Dbfake;
using pwt_0x01_ng.Models.Database;
namespace pwt_0x01_ng.Controllers
{
public class HomeController : Controller
{
private IList<Carousel> carousels = Dbfake.Carousels;
public class HomeController : Controller
{
readonly DBContext dbctx;
readonly ILogger<HomeController> logger;
public HomeController(DBContext dbctx, ILogger<HomeController> logger){
this.dbctx = dbctx;
this.logger = logger;
}
public IActionResult Index()
{
CarouselViewModel carousel = new CarouselViewModel();
carousel.Carousels = carousels;
return View(carousel);
}
public async Task <IActionResult> Index()
{
UltimateViewModel uvm = new UltimateViewModel();
uvm.Carousels = await dbctx.Carousel.ToListAsync();
uvm.Products = await dbctx.Product.ToListAsync();
return View(uvm);
}
public IActionResult About()
{
ViewData["Message"] = "Your application description page.";
public IActionResult About()
{
ViewData["Message"] = "Best project ever";
return View();
}
return View();
}
public IActionResult Contact()
{
ViewData["Message"] = "Your contact page.";
public IActionResult Contact()
{
ViewData["Message"] = "Where you can find us";
logger.LogInformation("yay - somebody looking for our contact info");
return View();
}
return View();
}
public IActionResult Privacy()
{
return View();
}
public IActionResult Privacy()
{
return View();
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
}
}
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier});
}
[Route("/Home/HandleError/{code:int}")]
public IActionResult HandleError(int code)
{
ViewData["ErrorMessage"] = $"Error {code}";
switch(code) {
case 403:
return View("~/Views/Shared/403.cshtml");
case 404:
return View("~/Views/Shared/404.cshtml");
case 500:
return View("~/Views/Shared/500.cshtml");
default:
return View("~/Views/Shared/unhandled_err_code.cshtml");
}
}
}
}

View File

@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using pwt_0x01_ng.Models;
using pwt_0x01_ng.Models.Database;
namespace pwt_0x01_ng.Controllers
{
public class ProductsController : Controller
{
readonly DBContext dbctx;
public ProductsController(DBContext dbctx){
this.dbctx = dbctx;
}
public async Task<IActionResult> Detail(int id){
Product p = await dbctx.Product.Where(p_item => p_item.id == id).FirstOrDefaultAsync();
if (p == null) return NotFound();
IList<Similar> s = await dbctx.Similar.Where(p_item => p_item.prod_id == id).ToListAsync();
IList<Product> similar = new List<Product>();
if (s != null) {
foreach (var s_item in s) {
var prod = await dbctx.Product.Where(p_item => p_item.id == s_item.similar_prod_id).FirstOrDefaultAsync();
if (prod != null) {
similar.Add(prod);
}
}
}
if (similar.Count > 0) {
ViewData["similar"] = similar;
}
return View(p);
}
}
}

99
Deployment/pwt.conf Normal file
View File

@ -0,0 +1,99 @@
upstream pwt {
server 127.0.0.1:8001;
}
server {
return 301 https://pwt.dotya.ml$request_uri;
listen 80;
listen [::]:80;
server_name pwt.dotya.ml;
return 404;
add_header Referrer-Policy "no-referrer, origin-when-cross-origin";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Robots-Tag none;
add_header X-Real-IP $remote_addr;
add_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Forwarded-Proto $scheme;
}
server {
server_name pwt.dotya.ml;
access_log /var/log/nginx/pwt.dotya.ml.access.log;
error_log /var/log/nginx/pwt.dotya.ml.error.log;
expires $expires;
etag on;
brotli on;
brotli_static on;
brotli_types *;
if ($http_user_agent ~* SemrushBot|morfeus) {
return 302 your-script-kiddie-box;
}
error_page 302 @blackhole;
location @blackhole {
add_header MESSAGE "YOU ALL SUCK D*CK";
add_header Retry-after "your script dies";
return 200;
}
location / {
proxy_pass http://pwt;
}
location = /robots.txt {
allow all;
add_header Content-Type "text/plain; charset=utf-8";
add_header X-Robots-Tag "none";
return 200 "User-agent: *\nDisallow: /";
}
add_header Content-Security-Policy "default-src 'none'; manifest-src 'self'; font-src 'self' https: blob:; img-src 'self'; script-src 'self' https: 'sha256-BU6NaT/mFkOb6wlKw9aAUV4i5MFr/Z/IFLIYAVFkmxQ=' 'sha256-V1j096ud9m6h5KncKkiAplx22jz14i0avalMtHLMFPs=' 'sha256-ggCJb9MSbkFha0PX0DCP2JxSZ4Vyz95IZKhP9nHXES8='; style-src 'self' https:; object-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self';";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Feature-Policy "geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; fullscreen 'self'; payment 'none';";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Robots-Tag none;
add_header X-Real-IP $remote_addr;
add_header X-Forwarded-For $proxy_add_x_forwarded_for;
add_header X-Forwarded-Proto $scheme;
listen [::]:443 ssl http2;
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/pwt.dotya.ml/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pwt.dotya.ml/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
if ($host = www.pwt.dotya.ml) {
return 301 https://pwt.dotya.ml$request_uri;
}
listen 80;
listen [::]:80;
server_name www.pwt.dotya.ml;
return 404;
add_header Referrer-Policy "no-referrer, origin-when-cross-origin, strict-origin-when-cross-origin";
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Robots-Tag none;
add_header X-Real-IP $remote_addr;
add_header X-Forwarded-For $remote_addr;
listen [::]:443 ssl http2;
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/pwt.dotya.ml/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pwt.dotya.ml/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}

12
Deployment/pwt.service Normal file
View File

@ -0,0 +1,12 @@
[Unit]
Description=pwt
After=nginx.service docker.service
[Service]
Restart=on-failure
ExecStart=/usr/bin/docker-compose -f /etc/pwt/pwt-0x01-ng/docker-compose.prod.yml up --remove-orphans --build --scale netcoreultimateapp-prod=2
ExecStop=/usr/bin/docker-compose -f /etc/pwt/pwt-0x01-ng/docker-compose.prod.yml stop
[Install]
WantedBy=multi-user.target

View File

@ -1,15 +1,17 @@
FROM mcr.microsoft.com/dotnet/core/sdk:2.1-alpine
COPY . /src
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS base
WORKDIR /src
RUN dotnet --version && dotnet --info
RUN dotnet clean
COPY *.csproj ./
RUN dotnet restore
RUN dotnet build -c Release
FROM mcr.microsoft.com/dotnet/aspnet:2.1
COPY . ./
RUN dotnet publish -c Release -o bin/out
COPY --from=0 /src/bin/Release/netcoreapp2.1/publish/ App/
FROM mcr.microsoft.com/dotnet/aspnet:3.1-alpine
WORKDIR /App
ENV ASPNETCORE_ENVIRONMENT=Release
COPY --from=base /src/bin/out/ .
RUN chown -R nobody:nobody ./
USER nobody
ENV ASPNETCORE_ENVIRONMENT Production
ENV ASPNETCORE_URLS http://*:8081
ENTRYPOINT ["dotnet", "pwt-0x01-ng.dll"]

View File

@ -1,13 +1,17 @@
FROM mcr.microsoft.com/dotnet/core/sdk:2.1-alpine
COPY . /src
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine
ENV UID 1000
ENV GID 1000
ENV UNAME unpriv
RUN adduser -D -u ${UID} -g ${GID} -H ${UNAME} -h /src
WORKDIR /src
RUN dotnet --version && dotnet --info
RUN dotnet clean
COPY *.csproj ./
RUN dotnet restore
RUN dotnet build -c Debug
COPY . ./
RUN dotnet build -c Debug -o bin/out
RUN chown -R "${UID}":"${GID}" ./ /root/
USER ${UNAME}
ENV ASPNETCORE_ENVIRONMENT=Development

View File

@ -1,10 +1,32 @@
dtag = netcoreultimapp
dc = docker-compose
dccmd = COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose
dtag = netcoreultimateapp-prod
dtagdev = netcoreultimapp-dev
dfile = Dockerfile
dfiledev = $(dfile).dev
lport = 8000
lportdev = 8001
CC = dotnet
dcmd = DOCKER_BUILDKIT=1 docker
pruneargs = system prune -af
dcmdrun = $(dcmd) run --rm
wdir = /src
kanikoimg = gcr.io/kaniko-project/executor@sha256:6ecc43ae139ad8cfa11604b592aaedddcabff8cef469eda303f1fb5afe5e3034
dargskaniko = -w=$(wdir) -v $$(pwd):$(wdir):z $(kanikoimg)
kanikoargs = -c=$(wdir) --use-new-run --snapshotMode=redo --no-push
krelease = $(dcmdrun) $(dargskaniko) -f=$(dfile) $(kanikoargs)
kdebug = $(dcmdrun) $(dargskaniko) -f=$(dfiledev) $(kanikoargs)
postkanikochown = sudo chown -R $$USER:$$USER ./
pgdbcapdrop = --cap-drop NET_ADMIN --cap-drop SYS_ADMIN
pgdbenv = -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=679968312e029a806c1905c40ec331aa199a1eb86bd0b9eb04057933e449bdc9ef8ef292a39b68cafa5689c901a17266 -e POSTGRES_INITDB_ARGS="--data-checksums"
pgdbname = pgdb
pgdbports = -p 127.0.0.1:5433:5432
pgdbvol = -v pgdbdata:/var/lib/postgresql/data
pgdbargs = run -d $(pgdbcapdrop) $(pgdbenv) --name $(pgdbname) $(pgdbports) $(pgdbvol) --restart unless-stopped
pgdbimg = postgres:13.1-alpine
zenv = DB_CONNECTION_STRING=$$(cat appsettings.Development.json | jq .db.Postgres | sed -e 's/5432/5433/' -e 's/=db/=localhost/' -e 's/"//g')
.PHONY: dev dockerbuild dockerdevbuild dockerrun dockerdevrun dockertest dockerdev kaniko clean prune pgdba pgdbz test dcdevb dcprodbuild dcdevup dcprodup
dev: restore build run
@ -15,38 +37,70 @@ build:
$(CC) build .
run:
$(CC) run .
$(zenv) $(CC) watch run .
releasebuild: clean restore
releasebuild: restore clean
$(CC) publish -c Release
dockerbuild:
@docker build \
$(dcmd) build \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
-t $(dtag) -f $(dfile) .
-t $(dtag) -f $(dfile) --no-cache .
dockerdevbuild:
@docker build \
$(dcmd) build \
--build-arg VCS_REF=`git rev-parse --short HEAD` \
--build-arg BUILD_DATE=`date -u +"%Y-%m-%dT%H:%M:%SZ"` \
-t $(dtagdev) -f $(dfiledev) .
-t $(dtagdev) -f $(dfiledev) --no-cache .
dockerrun:
@echo ======================
@echo local port: $(lport)
@echo ======================
docker run --rm -p $(lport):80 $(dtag)
$(dcmdrun) -p $(lport):80 $(dtag)
dockerdevrun:
@echo ======================
@echo local dev port: $(lportdev)
@echo ======================
docker run --rm -p $(lportdev):5000 $(dtagdev)
$(dcmdrun) -p $(lportdev):5000 $(dtagdev)
dcdevb:
$(dccmd) -f $(dc).yml build --no-cache --pull --progress tty
dcprodbuild:
$(dccmd) -f $(dc).prod.yml build --no-cache --pull --progress tty
dcdevup:
@echo ======================
@echo local dev port: $(lportdev)
@echo ======================
$(dccmd) -f $(dc).yml up --remove-orphans
dcprodup:
$(dccmd) -f $(dc).prod.yml up --remove-orphans --scale netcoreultimateapp-prod=2
kaniko:
$(krelease)
$(postkanikochown)
$(kdebug)
$(postkanikochown)
dockerdev: dockerdevbuild dockerdevrun
dockertest: dockerdevbuild dockerbuild
test: releasebuild build dockertest kaniko
clean:
$(CC) clean
prune:
$(dcmd) $(pruneargs)
pgdba:
$(dcmd) $(pgdbargs) $(pgdbimg)
pgdbz:
$(dcmd) stop $(pgdbname)

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Models.ApplicationServices
{
public interface ISecurityApplicationService
{
Task<string[]> register(RegisterViewModel rvm, Roles role);
Task<bool> login(LoginViewModel lvm);
Task logout();
Task<User> find_user_by_username(string username);
Task<IList<string>> gimme_user_roles(User usr);
Task<User> gimme_current_user(ClaimsPrincipal claims);
}
}

View File

@ -0,0 +1,66 @@
using System.Linq;
using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Models.ApplicationServices
{
public class SecurityIdentityApplicationService : ISecurityApplicationService
{
UserManager<User> um;
SignInManager<User> sim;
public SecurityIdentityApplicationService(UserManager<User> um, SignInManager<User> sim){
this.um = um;
this.sim = sim;
}
public async Task<string[]> register(RegisterViewModel rvm, Roles role){
User usr = new User(){username = rvm.username, UserName = rvm.username, Name = rvm.username, LastName = "", password = rvm.password, Email = rvm.email, EmailConfirmed = true};
string[] errors = null;
var res = await um.CreateAsync(usr, rvm.password);
if(res.Succeeded){
var roleres = await um.AddToRoleAsync(usr, role.ToString());
if(!roleres.Succeeded){
int c = 0;
foreach (var err in res.Errors)
{
res.Errors.Append(res.Errors.ElementAt(c));
c++;
}
}
}
if(res.Errors != null && res.Errors.Count() > 0){
errors = new string[res.Errors.Count()];
int c = 0;
foreach (var err in res.Errors)
{
errors[c] = res.Errors.ElementAt(c).Description;
}
c++;
}
return errors;
}
public async Task<bool> login(LoginViewModel lvm){
var result = await sim.PasswordSignInAsync(lvm.username, lvm.password, config.remember_me_feature, config.lockout_on_failure);
return result.Succeeded;
}
public Task logout(){
return sim.SignOutAsync();
}
public Task<User> find_user_by_username(string username){
return um.FindByNameAsync(username);
}
public Task<IList<string>> gimme_user_roles(User usr){
return um.GetRolesAsync(usr);
}
public Task<User> gimme_current_user(ClaimsPrincipal claims){
return um.GetUserAsync(claims);
}
}
}

View File

@ -1,14 +1,24 @@
using Microsoft.AspNetCore.Http;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models
{
public class Carousel
{
public int id { get; set; }
public string DataTarget { get; set; }
public IFormFile Image { get; set; }
public string ImageSrc { get; set; }
public string ImageAlt { get; set; }
public string CarouselContent { get; set; }
}
[Table("Carousel")]
public class Carousel : Entity
{
[Required]
public string DataTarget { get; set; }
[NotMapped]
[FileTypeAttr("image")]
public IFormFile Image { get; set; }
[StringLength(255)]
public string ImageSrc { get; set; }
[Required]
[StringLength(50, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 1)]
public string ImageAlt { get; set; }
[Required]
public string CarouselContent { get; set; }
}
}

View File

@ -2,8 +2,8 @@ using System.Collections.Generic;
namespace pwt_0x01_ng.Models
{
public class CarouselViewModel
{
public IList<Carousel> Carousels { get; set; }
}
public class CarouselViewModel
{
public IList<Carousel> Carousels { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models
{
public class CustomerOrderViewModel
{
IList<Order> Orders { get; set; }
}
}

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models.Database
{
public static class CarouselHelper
{
public static IList<Carousel> GenerateCarousel()
{
IList<Carousel> carousels = new List<Carousel>()
{
new Carousel() { DataTarget = "#myCarousel", ImageSrc = "/images/banner1.svg", ImageAlt = "ASP.NET", CarouselContent = "Learn how to build ASP.NET apps that can run anywhere.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525028&clcid=0x409\">Learn More</a>"},
new Carousel() { DataTarget = "#myCarousel", ImageSrc = "/images/banner2.svg", ImageAlt = "ASP.NET", CarouselContent = "There are powerful new features in Visual Studio for building modern web apps.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525030&clcid=0x409\">Learn More</a>"},
new Carousel() { DataTarget = "#myCarousel", ImageSrc = "/images/banner3.svg", ImageAlt = "ASP.NET", CarouselContent = "Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525027&clcid=0x409\">Learn More</a>"},
new Carousel() { DataTarget = "#myCarousel", ImageSrc = "/images/ms_loves_linux.jpeg", ImageAlt = "msloveslinux", CarouselContent = "ms loves linux"}
};
return carousels;
}
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace pwt_0x01_ng.Models.Database.Conf
{
public class CarouselConf : IEntityTypeConfiguration<Carousel>
{
public void Configure(EntityTypeBuilder<Carousel> builder)
{
builder.Property(nameof(Carousel.Created)).ValueGeneratedOnAdd().HasDefaultValueSql("NOW()");
builder.Property(nameof(Carousel.Updated)).ValueGeneratedOnAddOrUpdate().HasDefaultValueSql("NOW()");
}
}
}

View File

@ -0,0 +1,15 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace pwt_0x01_ng.Models.Database.Conf
{
public class OrderConf : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasMany(order => order.OrderItems).WithOne(item => item.Order).IsRequired().HasForeignKey(item => item.Order_id).OnDelete(DeleteBehavior.Restrict);
builder.Property(nameof(Order.Created)).ValueGeneratedOnAdd().HasDefaultValueSql("NOW()");
builder.Property(nameof(Order.Updated)).ValueGeneratedOnAddOrUpdate().HasDefaultValueSql("NOW()");
}
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace pwt_0x01_ng.Models.Database.Conf
{
public class OrderItemConf : IEntityTypeConfiguration<OrderItem>
{
public void Configure(EntityTypeBuilder<OrderItem> builder)
{
builder.Property(nameof(OrderItem.Created)).ValueGeneratedOnAdd().HasDefaultValueSql("NOW()");
builder.Property(nameof(OrderItem.Updated)).ValueGeneratedOnAddOrUpdate().HasDefaultValueSql("NOW()");
}
}
}

View File

@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace pwt_0x01_ng.Models.Database.Conf
{
public class ProductConf : IEntityTypeConfiguration<Product>
{
public void Configure(EntityTypeBuilder<Product> builder)
{
builder.Property(nameof(Product.Created)).ValueGeneratedOnAdd().HasDefaultValueSql("NOW()");
builder.Property(nameof(Product.Updated)).ValueGeneratedOnAddOrUpdate().HasDefaultValueSql("NOW()");
}
}
}

View File

@ -0,0 +1,40 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using pwt_0x01_ng.Models.Identity;
using pwt_0x01_ng.Models.Database.Conf;
namespace pwt_0x01_ng.Models.Database
{
public class DBContext : IdentityDbContext<User, Role, int>
{
public DBContext(DbContextOptions<DBContext> options) : base(options)
{
}
public DbSet<Carousel> Carousel { get; set; }
public DbSet<Product> Product { get; set; }
public DbSet<Order> Order { get; set; }
public DbSet<OrderItem> OrderItem { get; set; }
public DbSet<Similar> Similar { get; set; }
protected override void OnModelCreating(ModelBuilder model_builder)
{
base.OnModelCreating(model_builder);
this.ApplyConfiguration(model_builder);
foreach(var entity_type in model_builder.Model.GetEntityTypes()){
entity_type.SetTableName(entity_type.GetTableName().Replace("AspNet", string.Empty));
}
}
protected virtual void ApplyConfiguration(ModelBuilder model_builder)
{
model_builder.ApplyConfiguration(new CarouselConf());
model_builder.ApplyConfiguration(new ProductConf());
model_builder.ApplyConfiguration(new OrderConf());
model_builder.ApplyConfiguration(new OrderItemConf());
}
}
}

121
Models/Database/DBInit.cs Normal file
View File

@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Identity;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Models.Database
{
public static class DBInit
{
public static void Init(DBContext dbContext)
{
dbContext.Database.EnsureCreated();
if(dbContext.Carousel.Count() == 0){
IList<Carousel> carousels = CarouselHelper.GenerateCarousel();
foreach (var c in carousels)
{
dbContext.Carousel.Add(c);
}
dbContext.SaveChanges();
}
if(dbContext.Product.Count() == 0){
IList<Product> products = ProductHelper.GenerateProduct();
foreach (var p in products)
{
dbContext.Product.Add(p);
}
dbContext.SaveChanges();
}
}
public static async Task EnsureRolesCreated(IServiceProvider sp){
using (var services = sp.CreateScope()){
RoleManager<Role> role_manager = services.ServiceProvider.GetRequiredService<RoleManager<Role>>();
string[] rolespls = Enum.GetNames(typeof(Roles));
foreach (var role in rolespls){
Console.WriteLine(role + " => " + role.GetType());
await role_manager.CreateAsync(new Role(role));
}
}
}
public static async Task EnsureManagerCreated(IServiceProvider sp){
using (var services = sp.CreateScope()){
UserManager<User> usr_manager = services.ServiceProvider.GetRequiredService<UserManager<User>>();
User manager = new User(){
UserName = "manager",
Email = "manager@manager.com",
Name = "manager",
LastName = "",
EmailConfirmed = true
};
var super_secure_password = "123"; /* TODO - rm this */
User manager_in_db = await usr_manager.FindByNameAsync(manager.UserName);
if (manager_in_db == null){
IdentityResult ir = new IdentityResult();
try {
ir = await usr_manager.CreateAsync(manager, super_secure_password);
} catch (Exception e){
Debug.WriteLine(e);
}
if(ir.Succeeded){
string[] rolespls = Enum.GetNames(typeof(Roles));
foreach (var role in rolespls){
if(role != Roles.Admin.ToString()){
await usr_manager.AddToRoleAsync(manager, role);
}
}
} else if (ir.Errors != null && ir.Errors.Count() >0){
foreach (var err in ir.Errors){
Debug.WriteLine("Error during manager role creation" + err.Code + " => " + err.Description);
}
}
}
}
}
public static async Task EnsureAdminCreated(IServiceProvider sp){
using (var services = sp.CreateScope()){
UserManager<User> usr_manager = services.ServiceProvider.GetRequiredService<UserManager<User>>();
User admin = new User(){
UserName = "admin",
Email = "admin@admin.com",
Name = "admin",
LastName = "",
EmailConfirmed = true
};
var super_secure_password = "123"; /* TODO - rm this */
User admin_in_db = await usr_manager.FindByNameAsync(admin.UserName);
if (admin_in_db == null){
IdentityResult ir = new IdentityResult();
try {
ir = await usr_manager.CreateAsync(admin, super_secure_password);
} catch (Exception e){
Debug.WriteLine(e);
}
if(ir.Succeeded){
string[] rolespls = Enum.GetNames(typeof(Roles));
foreach (var role in rolespls){
await usr_manager.AddToRoleAsync(admin, role);
}
} else if (ir.Errors != null && ir.Errors.Count() >0){
foreach (var err in ir.Errors){
Debug.WriteLine("Error during admin role creation" + err.Code + " => " + err.Description);
}
}
}
}
}
}
}

View File

@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models.Database
{
public static class ProductHelper
{
public static IList<Product> GenerateProduct()
{
IList<Product> products = new List<Product>()
{
new Product() { Name="a", Price=0, Description="aaaa", ImageSrc = "/images/banner1.svg", ImageAlt = "ASP.NET", Similar = {}},
new Product() { Name="b", Price=1, Description="bbbb", ImageSrc = "/images/banner2.svg", ImageAlt = "ASP.NET", Similar = {}},
new Product() { Name="c", Price=2, Description="cccc", ImageSrc = "/images/banner3.svg", ImageAlt = "ASP.NET", Similar = {}},
new Product() { Name="d", Price=3, Description="dddd", ImageSrc = "/images/ms_loves_linux.jpeg", ImageAlt = "msloveslinux", Similar = {}}
};
return products;
}
}
}

View File

@ -1,20 +0,0 @@
using System.Collections.Generic;
using Microsoft.IdentityModel.Tokens;
namespace pwt_0x01_ng.Models.Dbfake
{
public static class CarouselHelper
{
public static IList<Carousel> GenerateCarousel()
{
IList<Carousel> carousels = new List<Carousel>()
{
new Carousel() { id = 0, DataTarget = "#myCarousel", ImageSrc = "/images/banner1.svg", ImageAlt = "ASP.NET", CarouselContent = "Learn how to build ASP.NET apps that can run anywhere.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525028&clcid=0x409\">Learn More</a>"},
new Carousel() { id = 1, DataTarget = "#myCarousel", ImageSrc = "/images/banner2.svg", ImageAlt = "ASP.NET", CarouselContent = "There are powerful new features in Visual Studio for building modern web apps.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525030&clcid=0x409\">Learn More</a>"},
new Carousel() { id = 2, DataTarget = "#myCarousel", ImageSrc = "/images/banner3.svg", ImageAlt = "ASP.NET", CarouselContent = "Learn how Microsoft's Azure cloud platform allows you to build, deploy, and scale web apps.<a class=\"btn btn-default\" href=\"https://go.microsoft.com/fwlink/?LinkID=525027&clcid=0x409\">Learn More</a>"},
new Carousel() { id = 3, DataTarget = "#myCarousel", ImageSrc = "/images/ms_loves_linux.jpeg", ImageAlt = "msloveslinux", CarouselContent = "ms loves linux"}
};
return carousels;
}
}
}

View File

@ -1,15 +0,0 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models.Dbfake
{
public static class Dbfake
{
/* singe db table simulation*/
public static IList<Carousel> Carousels { get; set; }
static Dbfake()
{
Carousels = CarouselHelper.GenerateCarousel();
}
}
}

17
Models/Entity.cs Normal file
View File

@ -0,0 +1,17 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace pwt_0x01_ng.Models
{
public abstract class Entity
{
[Key]
[Required]
public int id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Created { get; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Updated { get; }
}
}

View File

@ -1,11 +1,9 @@
using System;
namespace pwt_0x01_ng.Models
{
public class ErrorViewModel
{
public string RequestId { get; set; }
public class ErrorViewModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
}
}

9
Models/Identity/Role.cs Normal file
View File

@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Identity;
namespace pwt_0x01_ng.Models.Identity
{
public class Role : IdentityRole<int>
{
public Role(string name) : base(name){}
}
}

10
Models/Identity/Roles.cs Normal file
View File

@ -0,0 +1,10 @@
namespace pwt_0x01_ng.Models.Identity
{
public enum Roles
{
Admin,
Manager,
Customer
}
}

12
Models/Identity/User.cs Normal file
View File

@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Identity;
namespace pwt_0x01_ng.Models.Identity
{
public class User : IdentityUser<int>
{
public virtual string username {get;set;}
public virtual string password {get;set;}
public virtual string Name {get; set;}
public virtual string LastName {get; set;}
}
}

15
Models/LoginViewModel.cs Normal file
View File

@ -0,0 +1,15 @@
using System.ComponentModel.DataAnnotations;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models
{
public class LoginViewModel
{
[Required]
public string username {get; set;}
[Required]
[UniqueCharsAttr]
public string password {get; set;}
public bool login_failed {get; set;}
}
}

View File

@ -1,38 +1,40 @@
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
namespace pwt_0x01_ng.Models
{
public class MegaUpload
{
IHostingEnvironment hosting_env;
public class MegaUpload
{
string webroot_path;
string folder_name;
string file_type;
public MegaUpload(IHostingEnvironment hosting_env){
this.hosting_env = hosting_env;
}
public async Task<bool> DoMegaUpload(Carousel carousel)
{
bool great_success = false;
var img = carousel.Image;
if(img != null && img.ContentType.ToLower().Contains("image") && img.Length > 0 && img.Length < 2000000){
var fname = Path.GetFileNameWithoutExtension(img.FileName);
var fext = Path.GetExtension(img.FileName);
/* in case such file already exists - wip */
/* var fname_rand = Path.GetRandomFileName()+Path.GetFileNameWithoutExtension(img.FileName); */
var f_relative = Path.Combine("images","carousels", fname + fext);
var file_path = Path.Combine(hosting_env.WebRootPath, f_relative);
public MegaUpload(string webroot_path, string folder_name, string file_type){
this.webroot_path = webroot_path;
this.folder_name = folder_name;
this.file_type = file_type;
}
public async Task<string> DoMegaUpload(IFormFile iformfile)
{
string return_file_path = String.Empty;
var img = iformfile;
if(img != null && img.ContentType.ToLower().Contains(file_type) && img.Length > 0 && img.Length < 2000000){
var fname = Path.GetFileNameWithoutExtension(img.FileName);
var fext = Path.GetExtension(img.FileName);
/* in case such file already exists - wip */
/* var fname_rand = Path.GetRandomFileName()+Path.GetFileNameWithoutExtension(img.FileName); */
var f_relative = Path.Combine("images",folder_name, fname + fext);
var file_path = Path.Combine(webroot_path, f_relative);
using (var stream = new FileStream(file_path, FileMode.Create)){
await img.CopyToAsync(stream);
}
carousel.ImageSrc = $"/{f_relative}";
great_success = true;
}
return great_success;
}
}
using (var stream = new FileStream(file_path, FileMode.Create)){
await img.CopyToAsync(stream);
}
return_file_path = $"/{f_relative}";
/* great_success = true; */
}
return return_file_path;
}
}
}

22
Models/Order.cs Normal file
View File

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using pwt_0x01_ng.Models.Identity;
namespace pwt_0x01_ng.Models
{
[Table(nameof(Order))]
public class Order : Entity
{
[Required]
public string Order_Number { get; set; }
public IList<OrderItem> OrderItems { get; set; }
[ForeignKey(nameof(Identity.User))]
[Required]
public int User_id { get; set; }
[NotMapped]
public User usr { get; set; }
[Required]
public decimal Price_total { get; set; }
}
}

22
Models/OrderItem.cs Normal file
View File

@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace pwt_0x01_ng.Models
{
[Table(nameof(OrderItem))]
public class OrderItem : Entity
{
[ForeignKey(nameof(Order))]
[Required]
public int Order_id { get; set; }
[ForeignKey(nameof(Product))]
[Required]
public int Product_id { get; set; }
[Required]
public int Amount { get; set; }
[Required]
public decimal Price { get; set; }
public Order Order { get; set; }
public Product Product {get; set; }
}
}

31
Models/Product.cs Normal file
View File

@ -0,0 +1,31 @@
using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models
{
[Table("Product")]
public class Product : Entity
{
[Required]
public string Name { get; set; }
[Required]
public int Price { get; set; }
[Required]
public string Description { get; set; }
[FileTypeAttr("image")]
[NotMapped]
public IFormFile Image { get; set; }
[StringLength(255)]
public string ImageSrc { get; set; }
[Required]
[StringLength(50, ErrorMessage = "{0} length must be between {2} and {1}.", MinimumLength = 1)]
public string ImageAlt { get; set; }
[NotMapped]
public IList<SimilarProduct> Similar { get; set; }
[NotMapped]
public bool Selected { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models
{
public class ProductViewModel
{
public IList<Product> Products { get; set; }
}
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
using pwt_0x01_ng.Models.Validation;
namespace pwt_0x01_ng.Models
{
public class RegisterViewModel
{
[Required]
public string username {get; set;}
[Required]
[UniqueCharsAttr]
[RegularExpression(@"^.{2,}$", ErrorMessage = "Minimum password length is 2 characters.")]
public string password {get; set;}
[Required]
[Compare(nameof(password), ErrorMessage = "Passwords don't match.")]
public string repeated_password {get; set;}
[Required]
public string email {get; set;}
}
}

View File

@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
public static class SessionExtensions
{
public static T GetObject<T>(this ISession session, string key)
{
var data = session.GetString(key);
if (data == null)
{
return default(T);
}
return JsonConvert.DeserializeObject<T>(data);
}
public static void SetObject(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
}

18
Models/Similar.cs Normal file
View File

@ -0,0 +1,18 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace pwt_0x01_ng.Models
{
[Table(nameof(Similar))]
public class Similar
{
[Required]
[Key]
public int id { get; set; }
[Required]
public int prod_id { get; set; }
[ForeignKey(nameof(Product))]
[Required]
public int similar_prod_id { get; set; }
}
}

8
Models/SimilarProduct.cs Normal file
View File

@ -0,0 +1,8 @@
namespace pwt_0x01_ng.Models
{
public class SimilarProduct
{
public int id { get; set; }
public bool Selected { get; set; }
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models
{
public class SimilarViewModel
{
public IList<Similar> Similar { get; set; }
}
}

View File

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace pwt_0x01_ng.Models
{
public class UltimateViewModel
{
public IList<Carousel> Carousels { get; set; }
public IList<Product> Products { get; set; }
}
}

View File

@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace pwt_0x01_ng.Models.Validation
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
public class FileTypeAttr : ValidationAttribute, IClientModelValidator
{
private readonly string type;
public FileTypeAttr(string type){
this.type = type.ToLower();
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if (value == null) {
/* img is optional as of now */
return ValidationResult.Success;
} else if (value is IFormFile iff) {
if(iff.ContentType.ToLower().Contains(type)) {
return ValidationResult.Success;
} else {
return new ValidationResult(GetErrorMessage(validationContext.MemberName), new List<string> { validationContext.MemberName });
}
}
throw new NotImplementedException($"Attribute {nameof(FileTypeAttr)} not implemented for object {value.GetType()}.");
}
protected string GetErrorMessage(string member_name) => $"make sure the {member_name} you picked really is of type <code>{type}/*</code>. <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types\" title=\"help\" target=\"_blank\" rel=\"noopener noreferer\"><em>help</em></a>";
public void AddValidation(ClientModelValidationContext ctx){
MergeAttribute(ctx.Attributes, "data-val", "true");
MergeAttribute(ctx.Attributes, "data-val-content", GetErrorMessage("file"));
MergeAttribute(ctx.Attributes, "data-val-content-type", type);
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value){
if (attributes.ContainsKey(key)){
return false;
}
attributes.Add(key, value);
return true;
}
}
}

View File

@ -0,0 +1,45 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
namespace pwt_0x01_ng.Models.Validation
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false)]
public class UniqueCharsAttr : ValidationAttribute, IClientModelValidator
{
public UniqueCharsAttr(){
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext) {
if(value == null){return new ValidationResult(GetErrorMessage(), new List<string> { validationContext.MemberName });}
if (value is string phrase) {
int count = phrase.Distinct().Count();
if(count >= config.min_passwd_unique_chars) {
return ValidationResult.Success;
} else {
return new ValidationResult(GetErrorMessage(count, validationContext.MemberName), new List<string> { validationContext.MemberName });
}
}
throw new NotImplementedException($"Attribute {nameof(UniqueCharsAttr)} not implemented for object {value.GetType()}.");
}
protected string GetErrorMessage(int chars, string member_name) => $"not enough unique characters - provided: {chars}, wanted: {config.min_passwd_unique_chars}, problematic field: {member_name}";
protected string GetErrorMessage() => $"not enough unique characters. wanted: {config.min_passwd_unique_chars}";
public void AddValidation(ClientModelValidationContext ctx){
MergeAttribute(ctx.Attributes, "data-val", "true");
MergeAttribute(ctx.Attributes, "data-val-uniquechars", GetErrorMessage());
}
private bool MergeAttribute(IDictionary<string, string> attributes, string key, string value){
if (attributes.ContainsKey(key)){
return false;
}
attributes.Add(key, value);
return true;
}
}
}

View File

@ -1,24 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Threading.Tasks;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using pwt_0x01_ng.Models.Database;
namespace pwt_0x01_ng
{
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public class Program
{
public static async Task Main(string[] args)
{
IWebHost webHost = CreateWebHostBuilder(args).Build();
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>();
}
using (var scope = webHost.Services.CreateScope())
{
var serviceProvider = scope.ServiceProvider;
var dbctx = serviceProvider.GetRequiredService<DBContext>();
DBInit.Init(dbctx);
await DBInit.EnsureRolesCreated(serviceProvider);
await DBInit.EnsureAdminCreated(serviceProvider);
await DBInit.EnsureManagerCreated(serviceProvider);
}
webHost.Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.ConfigureLogging((ctx, logging) => {
logging.ClearProviders();
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
});
}
}

View File

@ -1,26 +1,13 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:47798",
"sslPort": 44311
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"pwt_0x01_ng": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation",
"DB_CONNECTION_STRING": "User ID=postgres;Password=679968312e029a806c1905c40ec331aa199a1eb86bd0b9eb04057933e449bdc9ef8ef292a39b68cafa5689c901a17266;Server=localhost;Port=5433;Database=pwt;Integrated Security=true;Pooling=true;"
}
}
}

View File

@ -3,7 +3,23 @@
this repo holds *sawce* for PWT .netcore mvc project 0x01-ng
### how to run this
> run the following commands from the solution folder
- Makefile (you need `make` for this) --> see the [[Makefile]]
- direct `dotnet` (and/or `docker`) commands
#### useful Makefile targets
- `restore` --> runs `dotnet restore .`
- `clean` --> clean builds the project
- `build` --> builds the project
- `dockerdevbuild` --> clean-builds a container image from [[Dockerfile.dev]] (have a look in there for details)
- `dcdevb` --> compose clean build of a debug (dev) version, pulling db
- `dcdevup` --> runs the above (see [[docker-compose.yml]])
> you need to have created a db beforehand to run these
- `run` --> runs `$CURRENT_ENV dotnet watch run .`
- `dev` --> runs `restore build run` - this is kind of a convenience target
#### run using dotnet
> run the `dotnet` commands from the solution folder
> note that a preconfigured db is required for any kind of running the project (consider taking a look at [useful makefile targets](#useful-makefile-targets))
on the first run, restore stuff
```sh

View File

@ -0,0 +1,59 @@
DROP TRIGGER IF EXISTS trigger_updated ON "Order";
CREATE OR REPLACE FUNCTION updatedattr() RETURNS trigger
AS $func$
BEGIN
NEW."Updated" := now();
NEW."Created" := OLD."Created";
RAISE NOTICE 'Order updated ''%'' on %', OLD."Order_Number", NEW."Updated";
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_updated
BEFORE UPDATE ON "Order"
FOR EACH ROW
EXECUTE PROCEDURE updatedattr();
DROP TRIGGER IF EXISTS trigger_updated ON "Product";
CREATE OR REPLACE FUNCTION updatedattr() RETURNS trigger
AS $func$
BEGIN
NEW."Updated" := now();
NEW."Created" := OLD."Created";
RAISE NOTICE 'Product updated ''%'' on %', OLD."id", NEW."Updated";
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_updated
BEFORE UPDATE ON "Product"
FOR EACH ROW
EXECUTE PROCEDURE updatedattr();
DROP TRIGGER IF EXISTS trigger_updated ON "OrderItem";
CREATE OR REPLACE FUNCTION updatedattr() RETURNS trigger
AS $func$
BEGIN
NEW."Updated" := now();
NEW."Created" := OLD."Created";
RAISE NOTICE 'OrderItem updated ''%'' on %', OLD."id", NEW."Updated";
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_updated
BEFORE UPDATE ON "OrderItem"
FOR EACH ROW
EXECUTE PROCEDURE updatedattr();
DROP TRIGGER IF EXISTS trigger_updated ON "Carousel";
CREATE OR REPLACE FUNCTION updatedattr() RETURNS trigger
AS $func$
BEGIN
NEW."Updated" := now();
NEW."Created" := OLD."Created";
RAISE NOTICE 'Carousel updated ''%'' on %', OLD."id", NEW."Updated";
RETURN NEW;
END;
$func$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_updated
BEFORE UPDATE ON "Carousel"
FOR EACH ROW
EXECUTE PROCEDURE updatedattr();

View File

@ -1,68 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.CookiePolicy;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using pwt_0x01_ng.Models.Identity;
using pwt_0x01_ng.Models.Database;
using pwt_0x01_ng.Models.ApplicationServices;
using Microsoft.EntityFrameworkCore;
namespace pwt_0x01_ng
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
options.HttpOnly = HttpOnlyPolicy.Always;
options.Secure = CookieSecurePolicy.Always;
});
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddHealthChecks();
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => false;
options.MinimumSameSitePolicy = SameSiteMode.Lax;
options.HttpOnly = HttpOnlyPolicy.Always;
options.Secure = CookieSecurePolicy.Always;
});
services.AddControllersWithViews();
IMvcBuilder builder = services.AddRazorPages();
#if DEBUG
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT").Equals("Development")){
builder.AddRazorRuntimeCompilation();
}
#endif
var connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
services.AddDbContext<DBContext>(options =>
options.UseNpgsql(
connectionString
)
);
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
services.AddIdentity<User, Role>().AddEntityFrameworkStores<DBContext>().AddDefaultTokenProviders();
services.Configure<IdentityOptions>(o =>
{
/* dev fun settings */
o.Password.RequireDigit = false;
o.Password.RequireUppercase = false;
o.Password.RequireLowercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequiredLength = config.min_passwd_length;
o.Password.RequiredUniqueChars = 1;
o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);
o.Lockout.MaxFailedAccessAttempts = 3;
o.Lockout.AllowedForNewUsers = false;
o.User.RequireUniqueEmail = true;
/* FIXME
* o.Password.RequireDigit = true;
* o.Password.RequiredLength = 18;
* o.Password.RequiredUniqueChars = 10;
* o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
* o.Lockout.MaxFailedAccessAttempts = 3;
* o.Lockout.AllowedForNewUsers = true;
* o.User.RequireUniqueEmail = true;
*/
});
services.ConfigureApplicationCookie(o => {
o.Cookie.HttpOnly = true;
o.ExpireTimeSpan = TimeSpan.FromDays(2);
o.SlidingExpiration = true;
o.LoginPath = "/Security/Account/Login";
o.LogoutPath = "/Security/Account/Logout";
});
services.AddScoped<ISecurityApplicationService, SecurityIdentityApplicationService>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_3_0);
services.AddResponseCompression();
services.AddSession();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.EnvironmentName.Equals("Development"))
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStatusCodePages();
app.UseStatusCodePagesWithReExecute("/Home/HandleError/{0}");
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseSession();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllerRoute(
name: "areas",
pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}"
);
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Products}/{action=Details}/{id?}"
);
endpoints.MapHealthChecks("/health");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "areas",
template: "{area:exists}/{controller=Home}/{action=Index}/{id?}"
);
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
app.UseStaticFiles();
app.UseCookiePolicy();
}
}
}

View File

@ -1,7 +1,7 @@
@{
ViewData["Title"] = "About";
ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<p>Use this area to provide additional information.</p>
<p>not much here now</p>

View File

@ -1,17 +1,9 @@
@{
ViewData["Title"] = "Contact";
ViewData["Title"] = "Contact";
}
<h2>@ViewData["Title"]</h2>
<h3>@ViewData["Message"]</h3>
<h4>@ViewData["Message"]</h4>
<address>
One Microsoft Way<br/>
Redmond, WA 98052-6399<br/>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br/>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
Earth &#x1F30D;
</address>

View File

@ -1,107 +1,68 @@
@model CarouselViewModel
@model UltimateViewModel
@{
ViewData["Title"] = "Home Page";
ViewData["Title"] = "Home Page";
}
@section Styles {
<link rel="stylesheet" href="~/css/Stylezbro.css"/>
<environment include="Development">
<link rel="stylesheet" href="~/css/products.css"/>
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/css/products.css"/>
</environment>
}
@{
if (Model != null && Model.Carousels.Count > 0)
{
<div id="@Model.Carousels[0].DataTarget.Substring(1, Model.Carousels[0].DataTarget.Length - 1)" class="carousel slide" data-ride="carousel" data-interval="6000">
<ol class="carousel-indicators">
@{
for (int i = 0; i < Model.Carousels.Count; i++)
{
string classli = String.Empty;
if (i == 0)
{
classli = "class=\"active\"";
}
<li data-target="@Model.Carousels[i].DataTarget" data-slide-to="@i" @Html.Raw(classli)></li>
}
}
</ol>
<div class="carousel-inner " role="listbox">
@{
for (int i = 0; i < Model.Carousels.Count; i++)
{
string classitem = "item";
if (i == 0)
{
classitem = "item active";
}
<div class="@classitem maxHeightCarousel">
<img src="@Model.Carousels[i].ImageSrc" alt="@Model.Carousels[i].ImageAlt" class="img-responsive"/>
<div class="carousel-caption" role="option">
<p>
@Html.Raw(Model.Carousels[i].CarouselContent)
</p>
</div>
</div>
}
}
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
<div class="row">
<div class="col-md-3">
<h2>Application uses</h2>
<ul>
<li>Sample pages using ASP.NET Core MVC</li>
<li>Theming using <a href="https://go.microsoft.com/fwlink/?LinkID=398939">Bootstrap</a></li>
</ul>
</div>
<div class="col-md-3">
<h2>How to</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=398600">Add a Controller and View</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699315">Manage User Secrets using Secret Manager.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699316">Use logging to log a message.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699317">Add packages using NuGet.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699319">Target development, staging or production environment.</a></li>
</ul>
</div>
<div class="col-md-3">
<h2>Overview</h2>
<ul>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=518008">Conceptual overview of what is ASP.NET Core</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=699320">Fundamentals of ASP.NET Core such as Startup and middleware.</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398602">Working with Data</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkId=398603">Security</a></li>
<li><a href="https://go.microsoft.com/fwlink/?LinkID=699321">Client side development</a>
</li>
<li>
<a href="https://go.microsoft.com/fwlink/?LinkID=699322">Develop on different platforms</a>
</li>
<li>
<a href="https://go.microsoft.com/fwlink/?LinkID=699323">Read more on the documentation site</a>
</li>
</ul>
</div>
<div class="col-md-3">
<h2>Run &amp; Deploy</h2>
<ul>
<li>
<a href="https://go.microsoft.com/fwlink/?LinkID=517851">Run your app</a>
</li>
<li>
<a href="https://go.microsoft.com/fwlink/?LinkID=517853">Run tools such as EF migrations and more</a>
</li>
<li>
<a href="https://go.microsoft.com/fwlink/?LinkID=398609">Publish to Microsoft Azure Web Apps</a>
</li>
</ul>
</div>
</div>
}
}
<div id="carouselpls">
<div class="row">
@{
if (Model != null && Model.Carousels.Count > 0)
{
<div id="@Model.Carousels[0].DataTarget.Substring(1, Model.Carousels[0].DataTarget.Length - 1)" class="carousel slide" data-ride="carousel" data-interval="6000">
<ol class="carousel-indicators">
@{
for (int i = 0; i < Model.Carousels.Count; i++)
{
string classli = String.Empty;
if (i == 0)
{
classli = "class=\"active\"";
}
<li data-target="@Model.Carousels[i].DataTarget" data-slide-to="@i" @Html.Raw(classli)></li>
}
}
</ol>
<div class="carousel-inner " role="listbox">
@{
for (int i = 0; i < Model.Carousels.Count; i++)
{
string classitem = "item";
if (i == 0)
{
classitem = "item active";
}
<div class="@classitem maxHeightCarousel">
<img src="@Model.Carousels[i].ImageSrc" alt="@Model.Carousels[i].ImageAlt" class="img-responsive"/>
<div class="carousel-caption" role="option">
<p>
@Html.Raw(Model.Carousels[i].CarouselContent)
</p>
</div>
</div>
}
}
</div>
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
<span class="sr-only">Previous</span>
</a>
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
<span class="sr-only">Next</span>
</a>
</div>
}
}
</div>
</div>
<partial name="../Products/_partial_Products.cshtml"/>

View File

@ -1,5 +1,5 @@
@{
ViewData["Title"] = "Privacy Policy";
ViewData["Title"] = "Privacy Policy";
}
<h2>@ViewData["Title"]</h2>

View File

@ -0,0 +1,39 @@
@model pwt_0x01_ng.Models.Product
@{
ViewData["Title"] = "Detail - " + @Model.Name;
}
<h2>Detail</h2>
<h3>@ViewData["Message"]</h3>
<div class="container">
<div class="row">
<div class="col-lg-3">
<h1 class="my-4">@Model.Name</h1>
<div class="list-group">
<a href="#" class="list-group-item active">Category 1</a>
<a href="#" class="list-group-item">Category 2</a>
<a href="#" class="list-group-item">Category 3</a>
</div>
</div><!-- /.col-lg-3 -->
<div class="col-lg-9">
<div class="card mt-4">
<img class="card-img-top img-fluid img-responsive" src="@Model.ImageSrc" alt="@Model.ImageAlt">
<div class="card-body">
<h3 class="card-title">@Model.Name</h3>
<h4>@Model.Price €</h4>
<p class="card-text">@Model.Description</p>
</div>
</div><!-- /.card -->
<div class="card card-outline-secondary my-4">
<div class="card-body">
<hr>
<input type="button" id="add_to_order" class="btn btn-primary" value="Add to order" onclick="Buy(@Model.id,'@Url.Action("AddOrderItemsToSession", "CustomerOrderNotCart", new { Area = "Customer" })', '#price_total', '@System.Globalization.CultureInfo.CurrentCulture.Name')" />
</div>
</div><!-- /.card -->
<partial name="_partial_SimilarProducts"/>
</div><!-- /.col-lg-9 -->
</div>
</div>

View File

@ -0,0 +1,40 @@
<!-- Page Content -->
<div class="container">
<div class="row">
<div class="col-lg-3">
<h1 class="my-4">Products</h1>
<div class="list-group">
<a href="#" class="list-group-item">Category 1</a>
<a href="#" class="list-group-item">Category 2</a>
<a href="#" class="list-group-item">Category 3</a>
</div>
</div><!-- /.col-lg-3 -->
<div class="col-lg-9">
<div class="row">
@{
if (Model != null && Model.Products.Count > 0){
for(int i = 0; i < Model.Products.Count; i++){
<div id="prod_card" class="col-lg-4 col-md-6 mb-4">
<div class="card h-100">
<a asp-area="" asp-controller="Products" asp-action="Detail" asp-route-id="@Model.Products[i].id"><img class="card-img-top img-responsive" id="prod_list_img" src="@Model.Products[i].ImageSrc" alt="@Model.Products[i].ImageAlt"></a>
<div class="card-body">
<h4 class="card-title">
<a asp-area="" asp-controller="Products" asp-action="Detail" asp-route-id="@Model.Products[i].id">@Model.Products[i].Name</a>
</h4>
<h5>@Model.Products[i].Price €</h5>
<p class="card-text">@Model.Products[i].Description</p>
</div>
<div class="card-footer">
<small class="text-muted">&#9733; &#9733; &#9733; &#9733; &#9733;</small>
</div>
</div>
</div>
}
} else {
<h3 class="text-info"><code>we apologise, no products are available at the moment.</code></h3>
}
}
</div><!-- /.row -->
</div><!-- /.col-lg-9 -->
</div><!-- /.row -->
</div><!-- /.container -->

View File

@ -0,0 +1,35 @@
@model pwt_0x01_ng.Models.Product
@{
IList<Product> similar;
similar = (IList<Product>) ViewData["similar"];
}
<div class="container">
<div class="col-lg-9">
<div class="row">
@if (similar != null && similar.Count > 0) {
<div class="col-lg-12 col-md-12">
<div class="col-lg-4 col-md-4">
<hr>
<label>Similar products</label>
</div>
</div>
int count_till = 3;
if (similar.Count < 3) count_till = similar.Count;
@for (int i = 0; i < count_till; i++) {
<div id="prod_card" class="col-lg-4 col-md-6 mb-4">
<div class="card h-100">
<a asp-area="" asp-controller="Products" asp-action="Detail" asp-route-id="@similar[i].id"><img class="card-img-top img-responsive" id="prod_list_img" src="@similar[i].ImageSrc" alt="@similar[i].ImageAlt"></a>
<div class="card-body">
<label><a asp-area="" asp-controller="Products" asp-action="Detail" asp-route-id="@similar[i].id">@similar[i].Name</a></label>
<div class="card-text">@similar[i].Description</div>
</div>
<div class="card-footer">
<div class="text-muted">@similar[i].Price €</div>
</div>
</div>
</div>
}
}
</div>
</div>
</div>

7
Views/Shared/403.cshtml Normal file
View File

@ -0,0 +1,7 @@
@{
ViewData["Title"] = "403 Forbidden";
}
<partial name="_errdivtop_part"/>
<h2 class="text-secondary">You are not authorized to access the resource.</h2>
<partial name="_errdivbottom_part"/>

Some files were not shown because too many files have changed in this diff Show More