From 7e080eaef977a8e8357b0d12a6ae3d81e25816f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mat=C4=9Bj=20Kub=C3=AD=C4=8Dek?= Date: Thu, 16 Jul 2026 18:33:05 +0200 Subject: [PATCH] InitEshop+ddd+ca --- {Template => Eshop}/.dockerignore | 0 Eshop/.idea/.idea.Eshop/.idea/.gitignore | 15 + .../.idea/.idea.Eshop}/.idea/encodings.xml | 0 .../.idea/.idea.Eshop}/.idea/indexLayout.xml | 0 Eshop/.idea/.idea.Eshop/.idea/vcs.xml | 6 + .../.idea/.idea.Template/.idea/.gitignore | 0 .../.idea/.idea.Template/.idea/encodings.xml | 4 + .../.idea.Template/.idea/indexLayout.xml | 8 + .../.idea/.idea.Template/.idea/vcs.xml | 0 .../DataTransferObjects/SignInResultDto.cs | 9 + .../DataTransferObjects/UserDataExportDto.cs | 8 + .../Auth/DataTransferObjects/UserDto.cs | 3 + .../DataTransferObjects/UserProfileDto.cs | 3 + .../DataTransferObjects/UserWithRolesDto.cs | 6 + .../Auth/Interfaces/IAuthenticationService.cs | 12 + .../Auth/Interfaces/IUserAccountService.cs | 29 ++ .../Interfaces/IUserAdministrationService.cs | 18 ++ .../Auth/UpdateProfileRequest.cs | 7 + .../Eshop.Application/Common/ServiceResult.cs | 28 ++ .../Eshop.Application.csproj | 13 + Eshop/Eshop.Domain/Common/DomainException.cs | 6 + Eshop/Eshop.Domain/Eshop.Domain.csproj | 9 + .../Eshop.Infrastructure}/AppDbContext.cs | 4 +- .../Eshop.Infrastructure}/Data/DataSeeder.cs | 4 +- .../DependencyInjection.cs | 56 ++++ .../Eshop.Infrastructure.csproj | 27 +- .../AspNetIdentityAuthenticationService.cs | 30 ++ .../AspNetIdentityUserAccountService.cs | 112 +++++++ ...AspNetIdentityUserAdministrationService.cs | 66 +++++ .../Persistence}/UserEntity.cs | 2 +- .../Admin/Controllers/AdminBaseController.cs | 2 +- .../Admin/Controllers/DashboardController.cs | 2 +- .../Admin/Controllers/UsersController.cs | 52 ++++ .../Areas/Admin/Views/Dashboard/Index.cshtml | 0 .../Admin/Views/Shared/_AdminLayout.cshtml | 0 .../Areas/Admin/Views/Users/Details.cshtml | 15 +- .../Areas/Admin/Views/Users/Index.cshtml | 8 +- .../Areas/Admin/Views/_ViewImports.cshtml | 4 + .../Areas/Admin/Views/_ViewStart.cshtml | 0 Eshop/Eshop.Web/Controllers/AuthController.cs | 206 +++++++++++++ .../Eshop.Web}/Controllers/HomeController.cs | 4 +- .../Eshop.Web}/Dockerfile | 0 Eshop/Eshop.Web/Eshop.Web.csproj | 25 ++ .../Models/UserWithRolesViewModel.cs | 10 + Eshop/Eshop.Web/Program.cs | 52 ++++ .../Eshop.Web}/Properties/launchSettings.json | 0 .../ViewModels/AuthLoginViewModel.cs | 2 +- .../ViewModels/AuthRegisterViewModel.cs | 2 +- .../Eshop.Web}/ViewModels/ErrorViewModel.cs | 2 +- .../Eshop.Web}/ViewModels/ProfileViewModel.cs | 2 +- .../Eshop.Web}/Views/Auth/AccessDenied.cshtml | 0 .../Eshop.Web}/Views/Auth/Login.cshtml | 2 +- .../Eshop.Web}/Views/Auth/Profile.cshtml | 2 +- .../Eshop.Web}/Views/Auth/Register.cshtml | 2 +- .../Eshop.Web}/Views/Home/Index.cshtml | 0 .../Eshop.Web}/Views/Home/Privacy.cshtml | 0 .../Eshop.Web}/Views/Shared/Error.cshtml | 2 +- .../Eshop.Web}/Views/Shared/_Layout.cshtml | 0 .../Views/Shared/_Layout.cshtml.css | 0 .../Shared/_ValidationScriptsPartial.cshtml | 0 .../Eshop.Web}/Views/_ViewImports.cshtml | 4 +- .../Eshop.Web}/Views/_ViewStart.cshtml | 0 .../Eshop.Web}/appsettings.Development.json | 0 .../Eshop.Web}/appsettings.json | 0 .../Eshop.Web}/wwwroot/css/site.css | 0 .../Eshop.Web}/wwwroot/favicon.ico | Bin .../Eshop.Web}/wwwroot/js/site.js | 0 .../Eshop.Web}/wwwroot/lib/bootstrap/LICENSE | 0 .../lib/bootstrap/dist/css/bootstrap-grid.css | 0 .../bootstrap/dist/css/bootstrap-grid.css.map | 0 .../bootstrap/dist/css/bootstrap-grid.min.css | 0 .../dist/css/bootstrap-grid.min.css.map | 0 .../bootstrap/dist/css/bootstrap-grid.rtl.css | 0 .../dist/css/bootstrap-grid.rtl.css.map | 0 .../dist/css/bootstrap-grid.rtl.min.css | 0 .../dist/css/bootstrap-grid.rtl.min.css.map | 0 .../bootstrap/dist/css/bootstrap-reboot.css | 0 .../dist/css/bootstrap-reboot.css.map | 0 .../dist/css/bootstrap-reboot.min.css | 0 .../dist/css/bootstrap-reboot.min.css.map | 0 .../dist/css/bootstrap-reboot.rtl.css | 0 .../dist/css/bootstrap-reboot.rtl.css.map | 0 .../dist/css/bootstrap-reboot.rtl.min.css | 0 .../dist/css/bootstrap-reboot.rtl.min.css.map | 0 .../dist/css/bootstrap-utilities.css | 0 .../dist/css/bootstrap-utilities.css.map | 0 .../dist/css/bootstrap-utilities.min.css | 0 .../dist/css/bootstrap-utilities.min.css.map | 0 .../dist/css/bootstrap-utilities.rtl.css | 0 .../dist/css/bootstrap-utilities.rtl.css.map | 0 .../dist/css/bootstrap-utilities.rtl.min.css | 0 .../css/bootstrap-utilities.rtl.min.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.css | 0 .../lib/bootstrap/dist/css/bootstrap.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.min.css | 0 .../bootstrap/dist/css/bootstrap.min.css.map | 0 .../lib/bootstrap/dist/css/bootstrap.rtl.css | 0 .../bootstrap/dist/css/bootstrap.rtl.css.map | 0 .../bootstrap/dist/css/bootstrap.rtl.min.css | 0 .../dist/css/bootstrap.rtl.min.css.map | 0 .../lib/bootstrap/dist/js/bootstrap.bundle.js | 0 .../bootstrap/dist/js/bootstrap.bundle.js.map | 0 .../bootstrap/dist/js/bootstrap.bundle.min.js | 0 .../dist/js/bootstrap.bundle.min.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.esm.js | 0 .../bootstrap/dist/js/bootstrap.esm.js.map | 0 .../bootstrap/dist/js/bootstrap.esm.min.js | 0 .../dist/js/bootstrap.esm.min.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.js | 0 .../lib/bootstrap/dist/js/bootstrap.js.map | 0 .../lib/bootstrap/dist/js/bootstrap.min.js | 0 .../bootstrap/dist/js/bootstrap.min.js.map | 0 .../jquery-validation-unobtrusive/LICENSE.txt | 0 .../dist/jquery.validate.unobtrusive.js | 0 .../dist/jquery.validate.unobtrusive.min.js | 0 .../wwwroot/lib/jquery-validation/LICENSE.md | 0 .../dist/additional-methods.js | 0 .../dist/additional-methods.min.js | 0 .../jquery-validation/dist/jquery.validate.js | 0 .../dist/jquery.validate.min.js | 0 .../Eshop.Web}/wwwroot/lib/jquery/LICENSE.txt | 0 .../wwwroot/lib/jquery/dist/jquery.js | 0 .../wwwroot/lib/jquery/dist/jquery.min.js | 0 .../wwwroot/lib/jquery/dist/jquery.min.map | 0 .../wwwroot/lib/jquery/dist/jquery.slim.js | 0 .../lib/jquery/dist/jquery.slim.min.js | 0 .../lib/jquery/dist/jquery.slim.min.map | 0 Eshop/Eshop.slnx | 10 + Eshop/WebTests/AuthServiceTests.cs | 111 +++++++ .../WebTests/MSTestSettings.cs | 0 .../WebTests/TestClassExample.cs | 2 +- {Template => Eshop}/WebTests/WebTests.csproj | 12 +- {Template => Eshop}/compose.yaml | 0 Template/Template.slnx | 7 - .../Admin/Controllers/UsersController.cs | 57 ---- .../Areas/Admin/Views/_ViewImports.cshtml | 3 - .../TemplateWeb/Controllers/AuthController.cs | 240 --------------- ...0260501160159_SwitchToIdentity.Designer.cs | 277 ------------------ .../20260501160159_SwitchToIdentity.cs | 223 -------------- .../Migrations/AppDbContextModelSnapshot.cs | 274 ----------------- Template/TemplateWeb/Models/ServiceResult.cs | 18 -- .../Models/UserWithRolesViewModel.cs | 10 - Template/TemplateWeb/Program.cs | 85 ------ Template/WebTests/AuthServiceTests.cs | 97 ------ 144 files changed, 972 insertions(+), 1339 deletions(-) rename {Template => Eshop}/.dockerignore (100%) create mode 100644 Eshop/.idea/.idea.Eshop/.idea/.gitignore rename {Template/.idea/.idea.Template => Eshop/.idea/.idea.Eshop}/.idea/encodings.xml (100%) rename {Template/.idea/.idea.Template => Eshop/.idea/.idea.Eshop}/.idea/indexLayout.xml (100%) create mode 100644 Eshop/.idea/.idea.Eshop/.idea/vcs.xml rename {Template => Eshop}/.idea/.idea.Template/.idea/.gitignore (100%) create mode 100644 Eshop/.idea/.idea.Template/.idea/encodings.xml create mode 100644 Eshop/.idea/.idea.Template/.idea/indexLayout.xml rename {Template => Eshop}/.idea/.idea.Template/.idea/vcs.xml (100%) create mode 100644 Eshop/Eshop.Application/Auth/DataTransferObjects/SignInResultDto.cs create mode 100644 Eshop/Eshop.Application/Auth/DataTransferObjects/UserDataExportDto.cs create mode 100644 Eshop/Eshop.Application/Auth/DataTransferObjects/UserDto.cs create mode 100644 Eshop/Eshop.Application/Auth/DataTransferObjects/UserProfileDto.cs create mode 100644 Eshop/Eshop.Application/Auth/DataTransferObjects/UserWithRolesDto.cs create mode 100644 Eshop/Eshop.Application/Auth/Interfaces/IAuthenticationService.cs create mode 100644 Eshop/Eshop.Application/Auth/Interfaces/IUserAccountService.cs create mode 100644 Eshop/Eshop.Application/Auth/Interfaces/IUserAdministrationService.cs create mode 100644 Eshop/Eshop.Application/Auth/UpdateProfileRequest.cs create mode 100644 Eshop/Eshop.Application/Common/ServiceResult.cs create mode 100644 Eshop/Eshop.Application/Eshop.Application.csproj create mode 100644 Eshop/Eshop.Domain/Common/DomainException.cs create mode 100644 Eshop/Eshop.Domain/Eshop.Domain.csproj rename {Template/TemplateWeb/Data => Eshop/Eshop.Infrastructure}/AppDbContext.cs (78%) rename {Template/TemplateWeb => Eshop/Eshop.Infrastructure}/Data/DataSeeder.cs (92%) create mode 100644 Eshop/Eshop.Infrastructure/DependencyInjection.cs rename Template/TemplateWeb/TemplateWeb.csproj => Eshop/Eshop.Infrastructure/Eshop.Infrastructure.csproj (61%) create mode 100644 Eshop/Eshop.Infrastructure/Identity/AspNetIdentityAuthenticationService.cs create mode 100644 Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAccountService.cs create mode 100644 Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAdministrationService.cs rename {Template/TemplateWeb/Entities => Eshop/Eshop.Infrastructure/Persistence}/UserEntity.cs (65%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Controllers/AdminBaseController.cs (79%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Controllers/DashboardController.cs (77%) create mode 100644 Eshop/Eshop.Web/Areas/Admin/Controllers/UsersController.cs rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Views/Dashboard/Index.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Views/Shared/_AdminLayout.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Views/Users/Details.cshtml (85%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Views/Users/Index.cshtml (81%) create mode 100644 Eshop/Eshop.Web/Areas/Admin/Views/_ViewImports.cshtml rename {Template/TemplateWeb => Eshop/Eshop.Web}/Areas/Admin/Views/_ViewStart.cshtml (100%) create mode 100644 Eshop/Eshop.Web/Controllers/AuthController.cs rename {Template/TemplateWeb => Eshop/Eshop.Web}/Controllers/HomeController.cs (88%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Dockerfile (100%) create mode 100644 Eshop/Eshop.Web/Eshop.Web.csproj create mode 100644 Eshop/Eshop.Web/Models/UserWithRolesViewModel.cs create mode 100644 Eshop/Eshop.Web/Program.cs rename {Template/TemplateWeb => Eshop/Eshop.Web}/Properties/launchSettings.json (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/ViewModels/AuthLoginViewModel.cs (94%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/ViewModels/AuthRegisterViewModel.cs (95%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/ViewModels/ErrorViewModel.cs (81%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/ViewModels/ProfileViewModel.cs (96%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Auth/AccessDenied.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Auth/Login.cshtml (97%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Auth/Profile.cshtml (98%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Auth/Register.cshtml (97%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Home/Index.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Home/Privacy.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Shared/Error.cshtml (94%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Shared/_Layout.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Shared/_Layout.cshtml.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/Shared/_ValidationScriptsPartial.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/_ViewImports.cshtml (52%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/Views/_ViewStart.cshtml (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/appsettings.Development.json (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/appsettings.json (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/css/site.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/favicon.ico (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/js/site.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/LICENSE (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation/LICENSE.md (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation/dist/additional-methods.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation/dist/additional-methods.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation/dist/jquery.validate.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/LICENSE.txt (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.min.map (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.slim.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.slim.min.js (100%) rename {Template/TemplateWeb => Eshop/Eshop.Web}/wwwroot/lib/jquery/dist/jquery.slim.min.map (100%) create mode 100644 Eshop/Eshop.slnx create mode 100644 Eshop/WebTests/AuthServiceTests.cs rename {Template => Eshop}/WebTests/MSTestSettings.cs (100%) rename {Template => Eshop}/WebTests/TestClassExample.cs (97%) rename {Template => Eshop}/WebTests/WebTests.csproj (68%) rename {Template => Eshop}/compose.yaml (100%) delete mode 100644 Template/Template.slnx delete mode 100644 Template/TemplateWeb/Areas/Admin/Controllers/UsersController.cs delete mode 100644 Template/TemplateWeb/Areas/Admin/Views/_ViewImports.cshtml delete mode 100644 Template/TemplateWeb/Controllers/AuthController.cs delete mode 100644 Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.Designer.cs delete mode 100644 Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.cs delete mode 100644 Template/TemplateWeb/Migrations/AppDbContextModelSnapshot.cs delete mode 100644 Template/TemplateWeb/Models/ServiceResult.cs delete mode 100644 Template/TemplateWeb/Models/UserWithRolesViewModel.cs delete mode 100644 Template/TemplateWeb/Program.cs delete mode 100644 Template/WebTests/AuthServiceTests.cs diff --git a/Template/.dockerignore b/Eshop/.dockerignore similarity index 100% rename from Template/.dockerignore rename to Eshop/.dockerignore diff --git a/Eshop/.idea/.idea.Eshop/.idea/.gitignore b/Eshop/.idea/.idea.Eshop/.idea/.gitignore new file mode 100644 index 0000000..5544f3a --- /dev/null +++ b/Eshop/.idea/.idea.Eshop/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/.idea.Eshop.iml +/contentModel.xml +/modules.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/Template/.idea/.idea.Template/.idea/encodings.xml b/Eshop/.idea/.idea.Eshop/.idea/encodings.xml similarity index 100% rename from Template/.idea/.idea.Template/.idea/encodings.xml rename to Eshop/.idea/.idea.Eshop/.idea/encodings.xml diff --git a/Template/.idea/.idea.Template/.idea/indexLayout.xml b/Eshop/.idea/.idea.Eshop/.idea/indexLayout.xml similarity index 100% rename from Template/.idea/.idea.Template/.idea/indexLayout.xml rename to Eshop/.idea/.idea.Eshop/.idea/indexLayout.xml diff --git a/Eshop/.idea/.idea.Eshop/.idea/vcs.xml b/Eshop/.idea/.idea.Eshop/.idea/vcs.xml new file mode 100644 index 0000000..6c0b863 --- /dev/null +++ b/Eshop/.idea/.idea.Eshop/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Template/.idea/.idea.Template/.idea/.gitignore b/Eshop/.idea/.idea.Template/.idea/.gitignore similarity index 100% rename from Template/.idea/.idea.Template/.idea/.gitignore rename to Eshop/.idea/.idea.Template/.idea/.gitignore diff --git a/Eshop/.idea/.idea.Template/.idea/encodings.xml b/Eshop/.idea/.idea.Template/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/Eshop/.idea/.idea.Template/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Eshop/.idea/.idea.Template/.idea/indexLayout.xml b/Eshop/.idea/.idea.Template/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/Eshop/.idea/.idea.Template/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/Template/.idea/.idea.Template/.idea/vcs.xml b/Eshop/.idea/.idea.Template/.idea/vcs.xml similarity index 100% rename from Template/.idea/.idea.Template/.idea/vcs.xml rename to Eshop/.idea/.idea.Template/.idea/vcs.xml diff --git a/Eshop/Eshop.Application/Auth/DataTransferObjects/SignInResultDto.cs b/Eshop/Eshop.Application/Auth/DataTransferObjects/SignInResultDto.cs new file mode 100644 index 0000000..97be81f --- /dev/null +++ b/Eshop/Eshop.Application/Auth/DataTransferObjects/SignInResultDto.cs @@ -0,0 +1,9 @@ +namespace Eshop.Application.Auth; + +public sealed class SignInResultDto +{ + public bool Succeeded { get; private init; } + + public static SignInResultDto Success => new() { Succeeded = true }; + public static SignInResultDto Failure => new() { Succeeded = false }; +} diff --git a/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDataExportDto.cs b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDataExportDto.cs new file mode 100644 index 0000000..237f9de --- /dev/null +++ b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDataExportDto.cs @@ -0,0 +1,8 @@ +namespace Eshop.Application.Auth; + +public sealed record UserDataExportDto( + string Id, + string UserName, + string Email, + IReadOnlyList Roles, + DateTime ExportDate); diff --git a/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDto.cs b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDto.cs new file mode 100644 index 0000000..04c3375 --- /dev/null +++ b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserDto.cs @@ -0,0 +1,3 @@ +namespace Eshop.Application.Auth; + +public sealed record UserDto(string Id, string UserName, string Email); diff --git a/Eshop/Eshop.Application/Auth/DataTransferObjects/UserProfileDto.cs b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserProfileDto.cs new file mode 100644 index 0000000..2fc1067 --- /dev/null +++ b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserProfileDto.cs @@ -0,0 +1,3 @@ +namespace Eshop.Application.Auth; + +public sealed record UserProfileDto(string UserName, string Email); diff --git a/Eshop/Eshop.Application/Auth/DataTransferObjects/UserWithRolesDto.cs b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserWithRolesDto.cs new file mode 100644 index 0000000..1ce9ebf --- /dev/null +++ b/Eshop/Eshop.Application/Auth/DataTransferObjects/UserWithRolesDto.cs @@ -0,0 +1,6 @@ +namespace Eshop.Application.Auth; + +public sealed record UserWithRolesDto( + UserDto User, + IReadOnlyList Roles, + IReadOnlyList AllRoles); diff --git a/Eshop/Eshop.Application/Auth/Interfaces/IAuthenticationService.cs b/Eshop/Eshop.Application/Auth/Interfaces/IAuthenticationService.cs new file mode 100644 index 0000000..7122959 --- /dev/null +++ b/Eshop/Eshop.Application/Auth/Interfaces/IAuthenticationService.cs @@ -0,0 +1,12 @@ +namespace Eshop.Application.Auth; + +public interface IAuthenticationService +{ + Task SignInAsync( + string emailOrUsername, + string password, + bool rememberMe, + CancellationToken cancellationToken = default); + + Task SignOutAsync(CancellationToken cancellationToken = default); +} diff --git a/Eshop/Eshop.Application/Auth/Interfaces/IUserAccountService.cs b/Eshop/Eshop.Application/Auth/Interfaces/IUserAccountService.cs new file mode 100644 index 0000000..8848fe8 --- /dev/null +++ b/Eshop/Eshop.Application/Auth/Interfaces/IUserAccountService.cs @@ -0,0 +1,29 @@ +using Eshop.Application.Common; + +namespace Eshop.Application.Auth; + +public interface IUserAccountService +{ + Task RegisterAsync( + string userName, + string email, + string password, + CancellationToken cancellationToken = default); + + Task GetProfileAsync( + string userId, + CancellationToken cancellationToken = default); + + Task UpdateProfileAsync( + string userId, + UpdateProfileRequest request, + CancellationToken cancellationToken = default); + + Task DeleteAccountAsync( + string userId, + CancellationToken cancellationToken = default); + + Task ExportUserDataAsync( + string userId, + CancellationToken cancellationToken = default); +} diff --git a/Eshop/Eshop.Application/Auth/Interfaces/IUserAdministrationService.cs b/Eshop/Eshop.Application/Auth/Interfaces/IUserAdministrationService.cs new file mode 100644 index 0000000..369fdd0 --- /dev/null +++ b/Eshop/Eshop.Application/Auth/Interfaces/IUserAdministrationService.cs @@ -0,0 +1,18 @@ +using Eshop.Application.Common; + +namespace Eshop.Application.Auth; + +public interface IUserAdministrationService +{ + Task> GetAllUsersWithRolesAsync( + CancellationToken cancellationToken = default); + + Task GetUserWithRolesAsync( + string userId, + CancellationToken cancellationToken = default); + + Task UpdateUserRolesAsync( + string userId, + IReadOnlyList selectedRoles, + CancellationToken cancellationToken = default); +} diff --git a/Eshop/Eshop.Application/Auth/UpdateProfileRequest.cs b/Eshop/Eshop.Application/Auth/UpdateProfileRequest.cs new file mode 100644 index 0000000..176ca4d --- /dev/null +++ b/Eshop/Eshop.Application/Auth/UpdateProfileRequest.cs @@ -0,0 +1,7 @@ +namespace Eshop.Application.Auth; + +public sealed record UpdateProfileRequest( + string UserName, + string Email, + string? CurrentPassword, + string? NewPassword); diff --git a/Eshop/Eshop.Application/Common/ServiceResult.cs b/Eshop/Eshop.Application/Common/ServiceResult.cs new file mode 100644 index 0000000..d1e182c --- /dev/null +++ b/Eshop/Eshop.Application/Common/ServiceResult.cs @@ -0,0 +1,28 @@ +namespace Eshop.Application.Common; + +public class ServiceResult +{ + public bool Success { get; protected set; } + public IReadOnlyList Errors { get; protected set; } = []; + + public static ServiceResult Ok() => new() { Success = true }; + + public static ServiceResult Failure(string message) => + new() { Success = false, Errors = [message] }; + + public static ServiceResult Failure(IEnumerable errors) => + new() { Success = false, Errors = errors.ToList() }; +} + +public class ServiceResult : ServiceResult +{ + public T? Data { get; private set; } + + public static ServiceResult Ok(T data) => new() { Success = true, Data = data }; + + public new static ServiceResult Failure(string message) => + new() { Success = false, Errors = [message] }; + + public new static ServiceResult Failure(IEnumerable errors) => + new() { Success = false, Errors = errors.ToList() }; +} diff --git a/Eshop/Eshop.Application/Eshop.Application.csproj b/Eshop/Eshop.Application/Eshop.Application.csproj new file mode 100644 index 0000000..ff29721 --- /dev/null +++ b/Eshop/Eshop.Application/Eshop.Application.csproj @@ -0,0 +1,13 @@ + + + + net10.0 + enable + enable + + + + + + + diff --git a/Eshop/Eshop.Domain/Common/DomainException.cs b/Eshop/Eshop.Domain/Common/DomainException.cs new file mode 100644 index 0000000..38e2916 --- /dev/null +++ b/Eshop/Eshop.Domain/Common/DomainException.cs @@ -0,0 +1,6 @@ +namespace Eshop.Domain.Common; + +public class DomainException : Exception +{ + public DomainException (string message) : base(message) { } +} \ No newline at end of file diff --git a/Eshop/Eshop.Domain/Eshop.Domain.csproj b/Eshop/Eshop.Domain/Eshop.Domain.csproj new file mode 100644 index 0000000..237d661 --- /dev/null +++ b/Eshop/Eshop.Domain/Eshop.Domain.csproj @@ -0,0 +1,9 @@ + + + + net10.0 + enable + enable + + + diff --git a/Template/TemplateWeb/Data/AppDbContext.cs b/Eshop/Eshop.Infrastructure/AppDbContext.cs similarity index 78% rename from Template/TemplateWeb/Data/AppDbContext.cs rename to Eshop/Eshop.Infrastructure/AppDbContext.cs index a3160b3..452a53f 100644 --- a/Template/TemplateWeb/Data/AppDbContext.cs +++ b/Eshop/Eshop.Infrastructure/AppDbContext.cs @@ -1,8 +1,8 @@ +using Eshop.Infrastructure.Persistence; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using TemplateWeb.Entities; -namespace TemplateWeb.Data; +namespace Eshop.Infrastructure; public class AppDbContext : IdentityDbContext { diff --git a/Template/TemplateWeb/Data/DataSeeder.cs b/Eshop/Eshop.Infrastructure/Data/DataSeeder.cs similarity index 92% rename from Template/TemplateWeb/Data/DataSeeder.cs rename to Eshop/Eshop.Infrastructure/Data/DataSeeder.cs index 7a47dd0..a3e1422 100644 --- a/Template/TemplateWeb/Data/DataSeeder.cs +++ b/Eshop/Eshop.Infrastructure/Data/DataSeeder.cs @@ -1,7 +1,7 @@ +using Eshop.Infrastructure.Persistence; using Microsoft.AspNetCore.Identity; -using TemplateWeb.Entities; -namespace TemplateWeb.Data; +namespace Eshop.Infrastructure.Data; public static class DataSeeder { diff --git a/Eshop/Eshop.Infrastructure/DependencyInjection.cs b/Eshop/Eshop.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..867cf4e --- /dev/null +++ b/Eshop/Eshop.Infrastructure/DependencyInjection.cs @@ -0,0 +1,56 @@ +using Eshop.Application.Auth; +using Eshop.Infrastructure.Identity; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace Eshop.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure(this IServiceCollection services) => + AddApplicationServices(services); + + public static IServiceCollection AddInfrastructure( + this IServiceCollection services, + IConfiguration configuration) + { + var connectionString = configuration.GetConnectionString("DefaultConnection") + ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); + + services.AddDbContext(options => + options.UseNpgsql(connectionString)); + + var pwSection = configuration.GetSection("Identity:Password"); + services.AddIdentity(options => + { + options.Password.RequiredLength = pwSection.GetValue("RequiredLength", 6); + options.Password.RequireDigit = pwSection.GetValue("RequireDigit", true); + options.Password.RequireLowercase = pwSection.GetValue("RequireLowercase", true); + options.Password.RequireUppercase = pwSection.GetValue("RequireUppercase", true); + options.Password.RequireNonAlphanumeric = pwSection.GetValue("RequireNonAlphanumeric", false); + options.Password.RequiredUniqueChars = pwSection.GetValue("RequiredUniqueChars", 1); + }) + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.ConfigureApplicationCookie(options => + { + options.LoginPath = "/Auth/Login"; + options.AccessDeniedPath = "/Auth/AccessDenied"; + options.ExpireTimeSpan = TimeSpan.FromHours(2); + }); + + return AddApplicationServices(services); + } + + private static IServiceCollection AddApplicationServices(IServiceCollection services) + { + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + return services; + } +} diff --git a/Template/TemplateWeb/TemplateWeb.csproj b/Eshop/Eshop.Infrastructure/Eshop.Infrastructure.csproj similarity index 61% rename from Template/TemplateWeb/TemplateWeb.csproj rename to Eshop/Eshop.Infrastructure/Eshop.Infrastructure.csproj index 30e6630..312b13c 100644 --- a/Template/TemplateWeb/TemplateWeb.csproj +++ b/Eshop/Eshop.Infrastructure/Eshop.Infrastructure.csproj @@ -1,26 +1,27 @@ - + net10.0 - enable enable - Linux + enable - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + - - .dockerignore - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + diff --git a/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityAuthenticationService.cs b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityAuthenticationService.cs new file mode 100644 index 0000000..8e2fb1b --- /dev/null +++ b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityAuthenticationService.cs @@ -0,0 +1,30 @@ +using Eshop.Application.Auth; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Identity; + +namespace Eshop.Infrastructure.Identity; + +public class AspNetIdentityAuthenticationService( + UserManager users, + SignInManager signIn) : IAuthenticationService +{ + public async Task SignInAsync( + string emailOrUsername, + string password, + bool rememberMe, + CancellationToken cancellationToken) + { + var user = emailOrUsername.Contains('@') + ? await users.FindByEmailAsync(emailOrUsername) + : await users.FindByNameAsync(emailOrUsername); + + if (user is null) + return SignInResultDto.Failure; + + var result = await signIn.PasswordSignInAsync(user, password, rememberMe, lockoutOnFailure: false); + return result.Succeeded ? SignInResultDto.Success : SignInResultDto.Failure; + } + + public Task SignOutAsync(CancellationToken cancellationToken) => + signIn.SignOutAsync(); +} diff --git a/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAccountService.cs b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAccountService.cs new file mode 100644 index 0000000..0cc66a8 --- /dev/null +++ b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAccountService.cs @@ -0,0 +1,112 @@ +using Eshop.Application.Auth; +using Eshop.Application.Common; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Identity; + +namespace Eshop.Infrastructure.Identity; + +public class AspNetIdentityUserAccountService( + UserManager users, + SignInManager signIn) : IUserAccountService +{ + public async Task RegisterAsync( + string userName, + string email, + string password, + CancellationToken cancellationToken) + { + var user = new UserEntity + { + UserName = userName.Trim(), + Email = email.Trim().ToLower(), + }; + + var result = await users.CreateAsync(user, password); + if (!result.Succeeded) + return ServiceResult.Failure(result.Errors.Select(e => e.Description)); + + await users.AddToRoleAsync(user, "User"); + return ServiceResult.Ok(); + } + + public async Task GetProfileAsync(string userId, CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) return null; + + return new UserProfileDto(user.UserName!, user.Email!); + } + + public async Task UpdateProfileAsync( + string userId, + UpdateProfileRequest request, + CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) + return ServiceResult.Failure("User not found."); + + var errors = new List(); + + if (user.UserName != request.UserName.Trim()) + { + var setName = await users.SetUserNameAsync(user, request.UserName.Trim()); + if (!setName.Succeeded) + errors.AddRange(setName.Errors.Select(e => e.Description)); + } + + var normalizedEmail = request.Email.Trim().ToLower(); + if (user.Email != normalizedEmail) + { + var setEmail = await users.SetEmailAsync(user, normalizedEmail); + if (!setEmail.Succeeded) + errors.AddRange(setEmail.Errors.Select(e => e.Description)); + } + + if (!string.IsNullOrWhiteSpace(request.NewPassword)) + { + if (string.IsNullOrWhiteSpace(request.CurrentPassword)) + return ServiceResult.Failure("Current password is required to set a new password."); + + var changePassword = await users.ChangePasswordAsync( + user, request.CurrentPassword, request.NewPassword); + + if (!changePassword.Succeeded) + errors.AddRange(changePassword.Errors.Select(e => e.Description)); + } + + if (errors.Count > 0) + return ServiceResult.Failure(errors); + + await signIn.RefreshSignInAsync(user); + return ServiceResult.Ok(); + } + + public async Task DeleteAccountAsync(string userId, CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) + return ServiceResult.Failure("User not found."); + + await signIn.SignOutAsync(); + var result = await users.DeleteAsync(user); + return result.Succeeded + ? ServiceResult.Ok() + : ServiceResult.Failure(result.Errors.Select(e => e.Description)); + } + + public async Task ExportUserDataAsync(string userId, CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) + return null; + + var roles = await users.GetRolesAsync(user); + return new UserDataExportDto( + user.Id, + user.UserName!, + user.Email!, + roles.ToList(), + DateTime.UtcNow); + } +} diff --git a/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAdministrationService.cs b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAdministrationService.cs new file mode 100644 index 0000000..9606cdb --- /dev/null +++ b/Eshop/Eshop.Infrastructure/Identity/AspNetIdentityUserAdministrationService.cs @@ -0,0 +1,66 @@ +using Eshop.Application.Auth; +using Eshop.Application.Common; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Identity; + +namespace Eshop.Infrastructure.Identity; + +public class AspNetIdentityUserAdministrationService( + UserManager users, + RoleManager roles) : IUserAdministrationService +{ + public async Task> GetAllUsersWithRolesAsync( + CancellationToken cancellationToken) + { + var allRoles = roles.Roles.Select(r => r.Name!).ToList(); + var viewModels = new List(); + + foreach (var user in users.Users.ToList()) + { + var userRoles = await users.GetRolesAsync(user); + viewModels.Add(ToDto(user, userRoles, allRoles)); + } + + return viewModels; + } + + public async Task GetUserWithRolesAsync( + string userId, + CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) + return null; + + var userRoles = await users.GetRolesAsync(user); + var allRoles = roles.Roles.Select(r => r.Name!).ToList(); + return ToDto(user, userRoles, allRoles); + } + + public async Task UpdateUserRolesAsync( + string userId, + IReadOnlyList selectedRoles, + CancellationToken cancellationToken) + { + var user = await users.FindByIdAsync(userId); + if (user is null) + return ServiceResult.Failure("User not found."); + + var currentRoles = await users.GetRolesAsync(user); + await users.RemoveFromRolesAsync(user, currentRoles); + + if (selectedRoles.Count <= 0) return ServiceResult.Ok(); + + var result = await users.AddToRolesAsync(user, selectedRoles); + return !result.Succeeded ? ServiceResult.Failure(result.Errors.Select(e => e.Description)) : ServiceResult.Ok(); + } + + private static UserWithRolesDto ToDto( + UserEntity user, + IList userRoles, + IList allRoles) => + new( + new UserDto(user.Id, user.UserName!, user.Email!), + userRoles.ToList(), + allRoles.ToList()); +} diff --git a/Template/TemplateWeb/Entities/UserEntity.cs b/Eshop/Eshop.Infrastructure/Persistence/UserEntity.cs similarity index 65% rename from Template/TemplateWeb/Entities/UserEntity.cs rename to Eshop/Eshop.Infrastructure/Persistence/UserEntity.cs index 415b68a..cb2cce3 100644 --- a/Template/TemplateWeb/Entities/UserEntity.cs +++ b/Eshop/Eshop.Infrastructure/Persistence/UserEntity.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Identity; -namespace TemplateWeb.Entities; +namespace Eshop.Infrastructure.Persistence; public class UserEntity : IdentityUser { diff --git a/Template/TemplateWeb/Areas/Admin/Controllers/AdminBaseController.cs b/Eshop/Eshop.Web/Areas/Admin/Controllers/AdminBaseController.cs similarity index 79% rename from Template/TemplateWeb/Areas/Admin/Controllers/AdminBaseController.cs rename to Eshop/Eshop.Web/Areas/Admin/Controllers/AdminBaseController.cs index d357183..b535d87 100644 --- a/Template/TemplateWeb/Areas/Admin/Controllers/AdminBaseController.cs +++ b/Eshop/Eshop.Web/Areas/Admin/Controllers/AdminBaseController.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -namespace TemplateWeb.Areas.Admin.Controllers; +namespace Eshop.Web.Areas.Admin.Controllers; [Area("Admin")] [Authorize(Roles = "Admin")] diff --git a/Template/TemplateWeb/Areas/Admin/Controllers/DashboardController.cs b/Eshop/Eshop.Web/Areas/Admin/Controllers/DashboardController.cs similarity index 77% rename from Template/TemplateWeb/Areas/Admin/Controllers/DashboardController.cs rename to Eshop/Eshop.Web/Areas/Admin/Controllers/DashboardController.cs index 5f451c2..4aed175 100644 --- a/Template/TemplateWeb/Areas/Admin/Controllers/DashboardController.cs +++ b/Eshop/Eshop.Web/Areas/Admin/Controllers/DashboardController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace TemplateWeb.Areas.Admin.Controllers; +namespace Eshop.Web.Areas.Admin.Controllers; public class DashboardController : AdminBaseController { diff --git a/Eshop/Eshop.Web/Areas/Admin/Controllers/UsersController.cs b/Eshop/Eshop.Web/Areas/Admin/Controllers/UsersController.cs new file mode 100644 index 0000000..c6fbee6 --- /dev/null +++ b/Eshop/Eshop.Web/Areas/Admin/Controllers/UsersController.cs @@ -0,0 +1,52 @@ +using Eshop.Application.Auth; +using Microsoft.AspNetCore.Mvc; +using Eshop.Web.Models; + +namespace Eshop.Web.Areas.Admin.Controllers; + +public class UsersController : AdminBaseController +{ + private readonly IUserAdministrationService _userAdministrationService; + + public UsersController(IUserAdministrationService userAdministrationService) + { + _userAdministrationService = userAdministrationService; + } + + public async Task Index() + { + var users = await _userAdministrationService.GetAllUsersWithRolesAsync(); + return View(users.Select(ToViewModel)); + } + + public async Task Details(string id) + { + var user = await _userAdministrationService.GetUserWithRolesAsync(id); + if (user is null) return NotFound(); + + return View(ToViewModel(user)); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task UpdateRoles(string id, List selectedRoles) + { + var result = await _userAdministrationService.UpdateUserRolesAsync(id, selectedRoles); + if (!result.Success) + { + TempData["ErrorMessage"] = string.Join(" ", result.Errors); + return RedirectToAction(nameof(Details), new { id }); + } + + TempData["SuccessMessage"] = "Roles updated successfully."; + return RedirectToAction(nameof(Details), new { id }); + } + + private static UserWithRolesViewModel ToViewModel(UserWithRolesDto dto) => + new() + { + User = dto.User, + Roles = dto.Roles, + AllRoles = dto.AllRoles, + }; +} diff --git a/Template/TemplateWeb/Areas/Admin/Views/Dashboard/Index.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/Dashboard/Index.cshtml similarity index 100% rename from Template/TemplateWeb/Areas/Admin/Views/Dashboard/Index.cshtml rename to Eshop/Eshop.Web/Areas/Admin/Views/Dashboard/Index.cshtml diff --git a/Template/TemplateWeb/Areas/Admin/Views/Shared/_AdminLayout.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml similarity index 100% rename from Template/TemplateWeb/Areas/Admin/Views/Shared/_AdminLayout.cshtml rename to Eshop/Eshop.Web/Areas/Admin/Views/Shared/_AdminLayout.cshtml diff --git a/Template/TemplateWeb/Areas/Admin/Views/Users/Details.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/Users/Details.cshtml similarity index 85% rename from Template/TemplateWeb/Areas/Admin/Views/Users/Details.cshtml rename to Eshop/Eshop.Web/Areas/Admin/Views/Users/Details.cshtml index 94de750..e4d25a9 100644 --- a/Template/TemplateWeb/Areas/Admin/Views/Users/Details.cshtml +++ b/Eshop/Eshop.Web/Areas/Admin/Views/Users/Details.cshtml @@ -12,20 +12,25 @@
@TempData["SuccessMessage"]
} +@if (TempData["ErrorMessage"] != null) +{ +
@TempData["ErrorMessage"]
+} +
-

User Information: @Model.UserEntity.UserName

+

User Information: @Model.User.UserName

ID
-
@Model.UserEntity.Id
+
@Model.User.Id
Username
-
@Model.UserEntity.UserName
+
@Model.User.UserName
Email
-
@Model.UserEntity.Email
+
@Model.User.Email
Roles
@@ -43,7 +48,7 @@
Edit Roles
-
+ @Html.AntiForgeryToken()
@foreach (var role in Model.AllRoles) diff --git a/Template/TemplateWeb/Areas/Admin/Views/Users/Index.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/Users/Index.cshtml similarity index 81% rename from Template/TemplateWeb/Areas/Admin/Views/Users/Index.cshtml rename to Eshop/Eshop.Web/Areas/Admin/Views/Users/Index.cshtml index 50544af..f6d27a8 100644 --- a/Template/TemplateWeb/Areas/Admin/Views/Users/Index.cshtml +++ b/Eshop/Eshop.Web/Areas/Admin/Views/Users/Index.cshtml @@ -21,9 +21,9 @@ @foreach (var vm in Model) { - @vm.UserEntity.Id - @vm.UserEntity.UserName - @vm.UserEntity.Email + @vm.User.Id + @vm.User.UserName + @vm.User.Email @foreach (var role in vm.Roles) { @@ -31,7 +31,7 @@ } - Details + Details } diff --git a/Eshop/Eshop.Web/Areas/Admin/Views/_ViewImports.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/_ViewImports.cshtml new file mode 100644 index 0000000..b23231d --- /dev/null +++ b/Eshop/Eshop.Web/Areas/Admin/Views/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@using Eshop.Web +@using Eshop.Web.Models +@using Eshop.Application.Auth +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Template/TemplateWeb/Areas/Admin/Views/_ViewStart.cshtml b/Eshop/Eshop.Web/Areas/Admin/Views/_ViewStart.cshtml similarity index 100% rename from Template/TemplateWeb/Areas/Admin/Views/_ViewStart.cshtml rename to Eshop/Eshop.Web/Areas/Admin/Views/_ViewStart.cshtml diff --git a/Eshop/Eshop.Web/Controllers/AuthController.cs b/Eshop/Eshop.Web/Controllers/AuthController.cs new file mode 100644 index 0000000..84aaab6 --- /dev/null +++ b/Eshop/Eshop.Web/Controllers/AuthController.cs @@ -0,0 +1,206 @@ +using System.Security.Claims; +using System.Text; +using System.Text.Json; +using Eshop.Application.Auth; +using Eshop.Web.ViewModels; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace Eshop.Web.Controllers; + +public class AuthController : Controller +{ + private readonly ILogger _logger; + private readonly IAuthenticationService _authenticationService; + private readonly IUserAccountService _userAccountService; + + public AuthController( + ILogger logger, + IAuthenticationService authenticationService, + IUserAccountService userAccountService) + { + _logger = logger; + _authenticationService = authenticationService; + _userAccountService = userAccountService; + } + + #region HttpGet + + [HttpGet] + [AllowAnonymous] + public IActionResult Login(string? returnUrl) + { + if (User.Identity?.IsAuthenticated == true) + return RedirectToAction("Index", "Home"); + + return View(new AuthLoginViewModel { ReturnUrl = returnUrl }); + } + + [HttpGet] + [AllowAnonymous] + public IActionResult Register(string? returnUrl) + { + if (User.Identity?.IsAuthenticated == true) + return RedirectToAction("Index", "Home"); + + return View(new AuthRegisterViewModel { ReturnUrl = returnUrl }); + } + + [HttpGet] + public IActionResult AccessDenied() => View(); + + [HttpGet] + [Authorize] + public async Task Profile() + { + var userId = GetCurrentUserId(); + if (userId is null) return RedirectToAction("Login"); + + var profile = await _userAccountService.GetProfileAsync(userId); + if (profile is null) return RedirectToAction("Login"); + + return View(new ProfileViewModel + { + UserName = profile.UserName, + Email = profile.Email, + }); + } + + #endregion + + #region HttpPost + + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Login(AuthLoginViewModel model) + { + if (!ModelState.IsValid) return View(model); + + var result = await _authenticationService.SignInAsync( + model.EmailOrUsername, model.Password, model.RememberMe); + + if (!result.Succeeded) + { + ModelState.AddModelError(string.Empty, "Invalid credentials."); + return View(model); + } + + if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) + return Redirect(model.ReturnUrl); + + return RedirectToAction("Index", "Home"); + } + + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public async Task Register(AuthRegisterViewModel model) + { + if (!ModelState.IsValid) return View(model); + + var result = await _userAccountService.RegisterAsync( + model.UserName, model.Email, model.Password); + + if (!result.Success) + { + foreach (var error in result.Errors) + ModelState.AddModelError(string.Empty, error); + return View(model); + } + + _logger.LogInformation("User {UserName} registered.", model.UserName); + + if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) + return Redirect(model.ReturnUrl); + + return RedirectToAction("Login"); + } + + [HttpPost] + [Authorize] + [ValidateAntiForgeryToken] + public async Task Profile(ProfileViewModel model) + { + if (!ModelState.IsValid) return View(model); + + var userId = GetCurrentUserId(); + if (userId is null) return RedirectToAction("Login"); + + var result = await _userAccountService.UpdateProfileAsync( + userId, + new UpdateProfileRequest( + model.UserName, + model.Email, + model.CurrentPassword, + model.NewPassword)); + + if (!result.Success) + { + foreach (var error in result.Errors) + ModelState.AddModelError(string.Empty, error); + return View(model); + } + + TempData["SuccessMessage"] = "Profile updated successfully."; + return RedirectToAction("Profile"); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Logout() + { + await _authenticationService.SignOutAsync(); + return RedirectToAction("Index", "Home"); + } + + [HttpPost] + [Authorize] + [ValidateAntiForgeryToken] + public async Task DeleteAccount() + { + var userId = GetCurrentUserId(); + if (userId is null) return RedirectToAction("Login"); + + var result = await _userAccountService.DeleteAccountAsync(userId); + if (!result.Success) + { + foreach (var error in result.Errors) + ModelState.AddModelError(string.Empty, error); + return RedirectToAction("Profile"); + } + + _logger.LogInformation("User {UserId} deleted their account.", userId); + return RedirectToAction("Register"); + } + + [HttpGet] + [Authorize] + public async Task DownloadData() + { + var userId = GetCurrentUserId(); + if (userId is null) return RedirectToAction("Login"); + + var data = await _userAccountService.ExportUserDataAsync(userId); + if (data is null) return RedirectToAction("Login"); + + var export = new + { + data.Id, + data.UserName, + data.Email, + Roles = data.Roles, + data.ExportDate, + }; + + var json = JsonSerializer.Serialize(export, new JsonSerializerOptions { WriteIndented = true }); + var bytes = Encoding.UTF8.GetBytes(json); + + return File(bytes, "application/json", $"user_data_{data.UserName}.json"); + } + + #endregion + + private string? GetCurrentUserId() => + User.FindFirstValue(ClaimTypes.NameIdentifier); +} diff --git a/Template/TemplateWeb/Controllers/HomeController.cs b/Eshop/Eshop.Web/Controllers/HomeController.cs similarity index 88% rename from Template/TemplateWeb/Controllers/HomeController.cs rename to Eshop/Eshop.Web/Controllers/HomeController.cs index b9b26f8..603b0ca 100644 --- a/Template/TemplateWeb/Controllers/HomeController.cs +++ b/Eshop/Eshop.Web/Controllers/HomeController.cs @@ -1,8 +1,8 @@ using System.Diagnostics; +using Eshop.Web.ViewModels; using Microsoft.AspNetCore.Mvc; -using TemplateWeb.Models; -namespace TemplateWeb.Controllers; +namespace Eshop.Web.Controllers; public class HomeController : Controller { diff --git a/Template/TemplateWeb/Dockerfile b/Eshop/Eshop.Web/Dockerfile similarity index 100% rename from Template/TemplateWeb/Dockerfile rename to Eshop/Eshop.Web/Dockerfile diff --git a/Eshop/Eshop.Web/Eshop.Web.csproj b/Eshop/Eshop.Web/Eshop.Web.csproj new file mode 100644 index 0000000..afdf69a --- /dev/null +++ b/Eshop/Eshop.Web/Eshop.Web.csproj @@ -0,0 +1,25 @@ + + + + net10.0 + enable + enable + Linux + + + + + + + + + + + + + + .dockerignore + + + + diff --git a/Eshop/Eshop.Web/Models/UserWithRolesViewModel.cs b/Eshop/Eshop.Web/Models/UserWithRolesViewModel.cs new file mode 100644 index 0000000..b490388 --- /dev/null +++ b/Eshop/Eshop.Web/Models/UserWithRolesViewModel.cs @@ -0,0 +1,10 @@ +using Eshop.Application.Auth; + +namespace Eshop.Web.Models; + +public class UserWithRolesViewModel +{ + public UserDto User { get; set; } = null!; + public IReadOnlyList Roles { get; set; } = []; + public IReadOnlyList AllRoles { get; set; } = []; +} diff --git a/Eshop/Eshop.Web/Program.cs b/Eshop/Eshop.Web/Program.cs new file mode 100644 index 0000000..dcc1905 --- /dev/null +++ b/Eshop/Eshop.Web/Program.cs @@ -0,0 +1,52 @@ +using dotenv.net; +using Eshop.Infrastructure; +using Eshop.Infrastructure.Data; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; + +namespace Eshop.Web; + +public class Program +{ + public static void Main(string[] args) + { + DotEnv.Load(); + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddControllersWithViews(); + builder.Services.AddInfrastructure(builder.Configuration); + + var app = builder.Build(); + + using (var scope = app.Services.CreateScope()) + { + var db = scope.ServiceProvider.GetRequiredService(); + if (db.Database.GetPendingMigrations().Any()) + db.Database.Migrate(); + + var userManager = scope.ServiceProvider.GetRequiredService>(); + var roleManager = scope.ServiceProvider.GetRequiredService>(); + DataSeeder.SeedAsync(userManager, roleManager).GetAwaiter().GetResult(); + } + + if (!app.Environment.IsDevelopment()) + app.UseExceptionHandler("/Home/Error"); + + app.UseRouting(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.MapStaticAssets(); + app.MapControllerRoute( + name: "areas", + pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); + + app.MapControllerRoute( + name: "default", + pattern: "{controller=Home}/{action=Index}/{id?}") + .WithStaticAssets(); + + app.Run(); + } +} diff --git a/Template/TemplateWeb/Properties/launchSettings.json b/Eshop/Eshop.Web/Properties/launchSettings.json similarity index 100% rename from Template/TemplateWeb/Properties/launchSettings.json rename to Eshop/Eshop.Web/Properties/launchSettings.json diff --git a/Template/TemplateWeb/ViewModels/AuthLoginViewModel.cs b/Eshop/Eshop.Web/ViewModels/AuthLoginViewModel.cs similarity index 94% rename from Template/TemplateWeb/ViewModels/AuthLoginViewModel.cs rename to Eshop/Eshop.Web/ViewModels/AuthLoginViewModel.cs index fce48c3..16b19f9 100644 --- a/Template/TemplateWeb/ViewModels/AuthLoginViewModel.cs +++ b/Eshop/Eshop.Web/ViewModels/AuthLoginViewModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace TemplateWeb.Models; +namespace Eshop.Web.ViewModels; public class AuthLoginViewModel { diff --git a/Template/TemplateWeb/ViewModels/AuthRegisterViewModel.cs b/Eshop/Eshop.Web/ViewModels/AuthRegisterViewModel.cs similarity index 95% rename from Template/TemplateWeb/ViewModels/AuthRegisterViewModel.cs rename to Eshop/Eshop.Web/ViewModels/AuthRegisterViewModel.cs index 0b5d01c..085c241 100644 --- a/Template/TemplateWeb/ViewModels/AuthRegisterViewModel.cs +++ b/Eshop/Eshop.Web/ViewModels/AuthRegisterViewModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace TemplateWeb.Models; +namespace Eshop.Web.ViewModels; public class AuthRegisterViewModel { diff --git a/Template/TemplateWeb/ViewModels/ErrorViewModel.cs b/Eshop/Eshop.Web/ViewModels/ErrorViewModel.cs similarity index 81% rename from Template/TemplateWeb/ViewModels/ErrorViewModel.cs rename to Eshop/Eshop.Web/ViewModels/ErrorViewModel.cs index 802c193..1706214 100644 --- a/Template/TemplateWeb/ViewModels/ErrorViewModel.cs +++ b/Eshop/Eshop.Web/ViewModels/ErrorViewModel.cs @@ -1,4 +1,4 @@ -namespace TemplateWeb.Models; +namespace Eshop.Web.ViewModels; public class ErrorViewModel { diff --git a/Template/TemplateWeb/ViewModels/ProfileViewModel.cs b/Eshop/Eshop.Web/ViewModels/ProfileViewModel.cs similarity index 96% rename from Template/TemplateWeb/ViewModels/ProfileViewModel.cs rename to Eshop/Eshop.Web/ViewModels/ProfileViewModel.cs index 8057fb6..3eaec91 100644 --- a/Template/TemplateWeb/ViewModels/ProfileViewModel.cs +++ b/Eshop/Eshop.Web/ViewModels/ProfileViewModel.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace TemplateWeb.Models; +namespace Eshop.Web.ViewModels; public class ProfileViewModel { diff --git a/Template/TemplateWeb/Views/Auth/AccessDenied.cshtml b/Eshop/Eshop.Web/Views/Auth/AccessDenied.cshtml similarity index 100% rename from Template/TemplateWeb/Views/Auth/AccessDenied.cshtml rename to Eshop/Eshop.Web/Views/Auth/AccessDenied.cshtml diff --git a/Template/TemplateWeb/Views/Auth/Login.cshtml b/Eshop/Eshop.Web/Views/Auth/Login.cshtml similarity index 97% rename from Template/TemplateWeb/Views/Auth/Login.cshtml rename to Eshop/Eshop.Web/Views/Auth/Login.cshtml index 5e91fb5..6eec434 100644 --- a/Template/TemplateWeb/Views/Auth/Login.cshtml +++ b/Eshop/Eshop.Web/Views/Auth/Login.cshtml @@ -1,4 +1,4 @@ -@model AuthLoginViewModel +@model Eshop.Web.ViewModels.AuthLoginViewModel
diff --git a/Template/TemplateWeb/Views/Auth/Profile.cshtml b/Eshop/Eshop.Web/Views/Auth/Profile.cshtml similarity index 98% rename from Template/TemplateWeb/Views/Auth/Profile.cshtml rename to Eshop/Eshop.Web/Views/Auth/Profile.cshtml index d8ac084..d9110ed 100644 --- a/Template/TemplateWeb/Views/Auth/Profile.cshtml +++ b/Eshop/Eshop.Web/Views/Auth/Profile.cshtml @@ -1,4 +1,4 @@ -@model ProfileViewModel +@model Eshop.Web.ViewModels.ProfileViewModel @{ ViewData["Title"] = "My Profile"; } diff --git a/Template/TemplateWeb/Views/Auth/Register.cshtml b/Eshop/Eshop.Web/Views/Auth/Register.cshtml similarity index 97% rename from Template/TemplateWeb/Views/Auth/Register.cshtml rename to Eshop/Eshop.Web/Views/Auth/Register.cshtml index f7b4776..2fcfcf9 100644 --- a/Template/TemplateWeb/Views/Auth/Register.cshtml +++ b/Eshop/Eshop.Web/Views/Auth/Register.cshtml @@ -1,4 +1,4 @@ -@model AuthRegisterViewModel +@model Eshop.Web.ViewModels.AuthRegisterViewModel
diff --git a/Template/TemplateWeb/Views/Home/Index.cshtml b/Eshop/Eshop.Web/Views/Home/Index.cshtml similarity index 100% rename from Template/TemplateWeb/Views/Home/Index.cshtml rename to Eshop/Eshop.Web/Views/Home/Index.cshtml diff --git a/Template/TemplateWeb/Views/Home/Privacy.cshtml b/Eshop/Eshop.Web/Views/Home/Privacy.cshtml similarity index 100% rename from Template/TemplateWeb/Views/Home/Privacy.cshtml rename to Eshop/Eshop.Web/Views/Home/Privacy.cshtml diff --git a/Template/TemplateWeb/Views/Shared/Error.cshtml b/Eshop/Eshop.Web/Views/Shared/Error.cshtml similarity index 94% rename from Template/TemplateWeb/Views/Shared/Error.cshtml rename to Eshop/Eshop.Web/Views/Shared/Error.cshtml index 4d96f6a..3e6fba4 100644 --- a/Template/TemplateWeb/Views/Shared/Error.cshtml +++ b/Eshop/Eshop.Web/Views/Shared/Error.cshtml @@ -1,4 +1,4 @@ -@model ErrorViewModel +@model Eshop.Web.ViewModels.ErrorViewModel @{ ViewData["Title"] = "Error"; } diff --git a/Template/TemplateWeb/Views/Shared/_Layout.cshtml b/Eshop/Eshop.Web/Views/Shared/_Layout.cshtml similarity index 100% rename from Template/TemplateWeb/Views/Shared/_Layout.cshtml rename to Eshop/Eshop.Web/Views/Shared/_Layout.cshtml diff --git a/Template/TemplateWeb/Views/Shared/_Layout.cshtml.css b/Eshop/Eshop.Web/Views/Shared/_Layout.cshtml.css similarity index 100% rename from Template/TemplateWeb/Views/Shared/_Layout.cshtml.css rename to Eshop/Eshop.Web/Views/Shared/_Layout.cshtml.css diff --git a/Template/TemplateWeb/Views/Shared/_ValidationScriptsPartial.cshtml b/Eshop/Eshop.Web/Views/Shared/_ValidationScriptsPartial.cshtml similarity index 100% rename from Template/TemplateWeb/Views/Shared/_ValidationScriptsPartial.cshtml rename to Eshop/Eshop.Web/Views/Shared/_ValidationScriptsPartial.cshtml diff --git a/Template/TemplateWeb/Views/_ViewImports.cshtml b/Eshop/Eshop.Web/Views/_ViewImports.cshtml similarity index 52% rename from Template/TemplateWeb/Views/_ViewImports.cshtml rename to Eshop/Eshop.Web/Views/_ViewImports.cshtml index f84e040..8130715 100644 --- a/Template/TemplateWeb/Views/_ViewImports.cshtml +++ b/Eshop/Eshop.Web/Views/_ViewImports.cshtml @@ -1,3 +1,3 @@ -@using TemplateWeb -@using TemplateWeb.Models +@using Eshop.Web +@using Eshop.Web.Models @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/Template/TemplateWeb/Views/_ViewStart.cshtml b/Eshop/Eshop.Web/Views/_ViewStart.cshtml similarity index 100% rename from Template/TemplateWeb/Views/_ViewStart.cshtml rename to Eshop/Eshop.Web/Views/_ViewStart.cshtml diff --git a/Template/TemplateWeb/appsettings.Development.json b/Eshop/Eshop.Web/appsettings.Development.json similarity index 100% rename from Template/TemplateWeb/appsettings.Development.json rename to Eshop/Eshop.Web/appsettings.Development.json diff --git a/Template/TemplateWeb/appsettings.json b/Eshop/Eshop.Web/appsettings.json similarity index 100% rename from Template/TemplateWeb/appsettings.json rename to Eshop/Eshop.Web/appsettings.json diff --git a/Template/TemplateWeb/wwwroot/css/site.css b/Eshop/Eshop.Web/wwwroot/css/site.css similarity index 100% rename from Template/TemplateWeb/wwwroot/css/site.css rename to Eshop/Eshop.Web/wwwroot/css/site.css diff --git a/Template/TemplateWeb/wwwroot/favicon.ico b/Eshop/Eshop.Web/wwwroot/favicon.ico similarity index 100% rename from Template/TemplateWeb/wwwroot/favicon.ico rename to Eshop/Eshop.Web/wwwroot/favicon.ico diff --git a/Template/TemplateWeb/wwwroot/js/site.js b/Eshop/Eshop.Web/wwwroot/js/site.js similarity index 100% rename from Template/TemplateWeb/wwwroot/js/site.js rename to Eshop/Eshop.Web/wwwroot/js/site.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/LICENSE b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/LICENSE similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/LICENSE rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/LICENSE diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-grid.rtl.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-reboot.rtl.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap-utilities.rtl.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/css/bootstrap.rtl.min.css.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.bundle.min.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.esm.min.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map b/Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map rename to Eshop/Eshop.Web/wwwroot/lib/bootstrap/dist/js/bootstrap.min.js.map diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation/LICENSE.md b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation/LICENSE.md similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation/LICENSE.md rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation/LICENSE.md diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/additional-methods.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/additional-methods.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/additional-methods.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/additional-methods.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/additional-methods.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/additional-methods.min.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/additional-methods.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/jquery.validate.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/jquery.validate.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js b/Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery-validation/dist/jquery.validate.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/LICENSE.txt b/Eshop/Eshop.Web/wwwroot/lib/jquery/LICENSE.txt similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/LICENSE.txt rename to Eshop/Eshop.Web/wwwroot/lib/jquery/LICENSE.txt diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.js b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.min.js b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.min.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.min.map b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.min.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.min.map rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.min.map diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.js b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.min.js b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.min.js similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.min.js rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.min.js diff --git a/Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.min.map b/Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.min.map similarity index 100% rename from Template/TemplateWeb/wwwroot/lib/jquery/dist/jquery.slim.min.map rename to Eshop/Eshop.Web/wwwroot/lib/jquery/dist/jquery.slim.min.map diff --git a/Eshop/Eshop.slnx b/Eshop/Eshop.slnx new file mode 100644 index 0000000..eb37532 --- /dev/null +++ b/Eshop/Eshop.slnx @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/Eshop/WebTests/AuthServiceTests.cs b/Eshop/WebTests/AuthServiceTests.cs new file mode 100644 index 0000000..60e156f --- /dev/null +++ b/Eshop/WebTests/AuthServiceTests.cs @@ -0,0 +1,111 @@ +using Eshop.Application.Auth; +using Eshop.Infrastructure; +using Eshop.Infrastructure.Persistence; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace WebTests; + +[TestClass] +public class AuthServiceTests +{ + private ServiceProvider _serviceProvider = null!; + private IAuthenticationService _authenticationService = null!; + private IUserAccountService _userAccountService = null!; + + [TestInitialize] + public void Initialize() + { + var services = new ServiceCollection(); + + services.AddLogging(); + services.AddSingleton(); + + services.AddDbContext(options => + options.UseInMemoryDatabase(Guid.NewGuid().ToString())); + + services.AddIdentity() + .AddEntityFrameworkStores() + .AddDefaultTokenProviders(); + + services.AddInfrastructure(); + + _serviceProvider = services.BuildServiceProvider(); + + var httpContext = new DefaultHttpContext { RequestServices = _serviceProvider }; + _serviceProvider.GetRequiredService().HttpContext = httpContext; + + var roleManager = _serviceProvider.GetRequiredService>(); + foreach (var role in new[] { "Admin", "User" }) + { + if (!roleManager.RoleExistsAsync(role).GetAwaiter().GetResult()) + roleManager.CreateAsync(new IdentityRole(role)).GetAwaiter().GetResult(); + } + + _authenticationService = _serviceProvider.GetRequiredService(); + _userAccountService = _serviceProvider.GetRequiredService(); + } + + [TestCleanup] + public void Cleanup() + { + _serviceProvider.Dispose(); + } + + [TestMethod] + public async Task RegisterAsync_ShouldReturnSuccess_WhenUserIsNew() + { + var result = await _userAccountService.RegisterAsync("testuser", "test@example.com", "Password123!"); + + Assert.IsTrue(result.Success); + + var profile = await _userAccountService.GetProfileAsync( + (await _serviceProvider.GetRequiredService>() + .FindByNameAsync("testuser"))!.Id); + + Assert.IsNotNull(profile); + Assert.AreEqual("testuser", profile.UserName); + Assert.AreEqual("test@example.com", profile.Email); + } + + [TestMethod] + public async Task RegisterAsync_ShouldReturnFailure_WhenUsernameAlreadyExists() + { + await _userAccountService.RegisterAsync("existing", "existing@example.com", "Password123!"); + + var result = await _userAccountService.RegisterAsync("existing", "other@example.com", "Password123!"); + + Assert.IsFalse(result.Success); + Assert.IsTrue(result.Errors.Any(e => e.Contains("already taken", StringComparison.OrdinalIgnoreCase))); + } + + [TestMethod] + public async Task SignInAsync_ShouldReturnSuccess_WithValidCredentials() + { + await _userAccountService.RegisterAsync("testuser", "test@example.com", "Password123!"); + + var result = await _authenticationService.SignInAsync("testuser", "Password123!", rememberMe: false); + + Assert.IsTrue(result.Succeeded); + } + + [TestMethod] + public async Task SignInAsync_ShouldReturnFailure_WithInvalidCredentials() + { + await _userAccountService.RegisterAsync("testuser", "test@example.com", "Password123!"); + + var result = await _authenticationService.SignInAsync("testuser", "WrongPassword!", rememberMe: false); + + Assert.IsFalse(result.Succeeded); + } + + [TestMethod] + public async Task SignInAsync_ShouldReturnFailure_WhenUserNotFound() + { + var result = await _authenticationService.SignInAsync("nonexistent", "Password123!", rememberMe: false); + + Assert.IsFalse(result.Succeeded); + } +} diff --git a/Template/WebTests/MSTestSettings.cs b/Eshop/WebTests/MSTestSettings.cs similarity index 100% rename from Template/WebTests/MSTestSettings.cs rename to Eshop/WebTests/MSTestSettings.cs diff --git a/Template/WebTests/TestClassExample.cs b/Eshop/WebTests/TestClassExample.cs similarity index 97% rename from Template/WebTests/TestClassExample.cs rename to Eshop/WebTests/TestClassExample.cs index 7dbc94c..3411fad 100644 --- a/Template/WebTests/TestClassExample.cs +++ b/Eshop/WebTests/TestClassExample.cs @@ -1,5 +1,5 @@ using Microsoft.EntityFrameworkCore; -using TemplateWeb.Data; +using Eshop.Infrastructure; namespace WebTests; diff --git a/Template/WebTests/WebTests.csproj b/Eshop/WebTests/WebTests.csproj similarity index 68% rename from Template/WebTests/WebTests.csproj rename to Eshop/WebTests/WebTests.csproj index 121f38e..164a9ea 100644 --- a/Template/WebTests/WebTests.csproj +++ b/Eshop/WebTests/WebTests.csproj @@ -8,12 +8,11 @@ - - - - + + + - + @@ -21,7 +20,8 @@ - + + diff --git a/Template/compose.yaml b/Eshop/compose.yaml similarity index 100% rename from Template/compose.yaml rename to Eshop/compose.yaml diff --git a/Template/Template.slnx b/Template/Template.slnx deleted file mode 100644 index c5a6adc..0000000 --- a/Template/Template.slnx +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Template/TemplateWeb/Areas/Admin/Controllers/UsersController.cs b/Template/TemplateWeb/Areas/Admin/Controllers/UsersController.cs deleted file mode 100644 index 57a65c9..0000000 --- a/Template/TemplateWeb/Areas/Admin/Controllers/UsersController.cs +++ /dev/null @@ -1,57 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using TemplateWeb.Entities; -using TemplateWeb.Models; - -namespace TemplateWeb.Areas.Admin.Controllers; - -public class UsersController : AdminBaseController -{ - private readonly UserManager _userManager; - private readonly RoleManager _roleManager; - - public UsersController(UserManager userManager, RoleManager roleManager) - { - _userManager = userManager; - _roleManager = roleManager; - } - - public async Task Index() - { - var allRoles = _roleManager.Roles.Select(r => r.Name!).ToList(); - var users = _userManager.Users.ToList(); - var viewModels = new List(); - foreach (var user in users) - { - var roles = await _userManager.GetRolesAsync(user); - viewModels.Add(new UserWithRolesViewModel { UserEntity = user, Roles = roles, AllRoles = allRoles }); - } - return View(viewModels); - } - - public async Task Details(string id) - { - var user = await _userManager.FindByIdAsync(id); - if (user == null) return NotFound(); - - var roles = await _userManager.GetRolesAsync(user); - var allRoles = _roleManager.Roles.Select(r => r.Name!).ToList(); - return View(new UserWithRolesViewModel { UserEntity = user, Roles = roles, AllRoles = allRoles }); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task UpdateRoles(string id, List selectedRoles) - { - var user = await _userManager.FindByIdAsync(id); - if (user == null) return NotFound(); - - var currentRoles = await _userManager.GetRolesAsync(user); - await _userManager.RemoveFromRolesAsync(user, currentRoles); - if (selectedRoles.Count > 0) - await _userManager.AddToRolesAsync(user, selectedRoles); - - TempData["SuccessMessage"] = "Roles updated successfully."; - return RedirectToAction(nameof(Details), new { id }); - } -} diff --git a/Template/TemplateWeb/Areas/Admin/Views/_ViewImports.cshtml b/Template/TemplateWeb/Areas/Admin/Views/_ViewImports.cshtml deleted file mode 100644 index 1d6fbe7..0000000 --- a/Template/TemplateWeb/Areas/Admin/Views/_ViewImports.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@using TemplateWeb -@using TemplateWeb.Models -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Template/TemplateWeb/Controllers/AuthController.cs b/Template/TemplateWeb/Controllers/AuthController.cs deleted file mode 100644 index 692a0f3..0000000 --- a/Template/TemplateWeb/Controllers/AuthController.cs +++ /dev/null @@ -1,240 +0,0 @@ -using System.Text; -using System.Text.Json; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using TemplateWeb.Entities; -using TemplateWeb.Models; - -namespace TemplateWeb.Controllers; - -public class AuthController : Controller -{ - private readonly ILogger _logger; - private readonly UserManager _userManager; - private readonly SignInManager _signInManager; - - public AuthController( - ILogger logger, - UserManager userManager, - SignInManager signInManager) - { - _logger = logger; - _userManager = userManager; - _signInManager = signInManager; - } - - #region HttpGet - - [HttpGet] - [AllowAnonymous] - public IActionResult Login(string? returnUrl) - { - if (User.Identity?.IsAuthenticated == true) - return RedirectToAction("Index", "Home"); - - return View(new AuthLoginViewModel { ReturnUrl = returnUrl }); - } - - [HttpGet] - [AllowAnonymous] - public IActionResult Register(string? returnUrl) - { - if (User.Identity?.IsAuthenticated == true) - return RedirectToAction("Index", "Home"); - - return View(new AuthRegisterViewModel { ReturnUrl = returnUrl }); - } - - [HttpGet] - public IActionResult AccessDenied() => View(); - - [HttpGet] - [Authorize] - public async Task Profile() - { - var user = await _userManager.GetUserAsync(User); - if (user == null) return RedirectToAction("Login"); - - return View(new ProfileViewModel - { - UserName = user.UserName!, - Email = user.Email!, - }); - } - - #endregion - - #region HttpPost - - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Login(AuthLoginViewModel model) - { - if (!ModelState.IsValid) return View(model); - - // Support login by email or username - var user = model.EmailOrUsername.Contains('@') - ? await _userManager.FindByEmailAsync(model.EmailOrUsername) - : await _userManager.FindByNameAsync(model.EmailOrUsername); - - if (user == null) - { - ModelState.AddModelError(string.Empty, "Invalid credentials."); - return View(model); - } - - var result = await _signInManager.PasswordSignInAsync( - user, model.Password, model.RememberMe, lockoutOnFailure: false); - - if (!result.Succeeded) - { - ModelState.AddModelError(string.Empty, "Invalid credentials."); - return View(model); - } - - if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) - return Redirect(model.ReturnUrl); - - return RedirectToAction("Index", "Home"); - } - - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public async Task Register(AuthRegisterViewModel model) - { - if (!ModelState.IsValid) return View(model); - - var user = new UserEntity - { - UserName = model.UserName.Trim(), - Email = model.Email.Trim().ToLower(), - }; - - var result = await _userManager.CreateAsync(user, model.Password); - if (!result.Succeeded) - { - foreach (var error in result.Errors) - ModelState.AddModelError(string.Empty, error.Description); - return View(model); - } - - await _userManager.AddToRoleAsync(user, "User"); - _logger.LogInformation("User {UserName} registered.", user.UserName); - - if (!string.IsNullOrEmpty(model.ReturnUrl) && Url.IsLocalUrl(model.ReturnUrl)) - return Redirect(model.ReturnUrl); - - return RedirectToAction("Login"); - } - - [HttpPost] - [Authorize] - [ValidateAntiForgeryToken] - public async Task Profile(ProfileViewModel model) - { - if (!ModelState.IsValid) return View(model); - - var user = await _userManager.GetUserAsync(User); - if (user == null) return RedirectToAction("Login"); - - // Update username - if (user.UserName != model.UserName.Trim()) - { - var setName = await _userManager.SetUserNameAsync(user, model.UserName.Trim()); - if (!setName.Succeeded) - { - foreach (var e in setName.Errors) - ModelState.AddModelError(string.Empty, e.Description); - return View(model); - } - } - - // Update email - if (user.Email != model.Email.Trim().ToLower()) - { - var setEmail = await _userManager.SetEmailAsync(user, model.Email.Trim().ToLower()); - if (!setEmail.Succeeded) - { - foreach (var e in setEmail.Errors) - ModelState.AddModelError(string.Empty, e.Description); - return View(model); - } - } - - // Change password (requires current password) - if (!string.IsNullOrWhiteSpace(model.NewPassword)) - { - if (string.IsNullOrWhiteSpace(model.CurrentPassword)) - { - ModelState.AddModelError(nameof(model.CurrentPassword), "Current password is required to set a new password."); - return View(model); - } - - var changePassword = await _userManager.ChangePasswordAsync(user, model.CurrentPassword, model.NewPassword); - if (!changePassword.Succeeded) - { - foreach (var e in changePassword.Errors) - ModelState.AddModelError(string.Empty, e.Description); - return View(model); - } - } - - // Refresh cookie so the user stays logged in after security-stamp changes - await _signInManager.RefreshSignInAsync(user); - - TempData["SuccessMessage"] = "Profile updated successfully."; - return RedirectToAction("Profile"); - } - - [HttpPost] - [ValidateAntiForgeryToken] - public async Task Logout() - { - await _signInManager.SignOutAsync(); - return RedirectToAction("Index", "Home"); - } - - [HttpPost] - [Authorize] - [ValidateAntiForgeryToken] - public async Task DeleteAccount() - { - var user = await _userManager.GetUserAsync(User); - if (user == null) return RedirectToAction("Login"); - - await _signInManager.SignOutAsync(); - await _userManager.DeleteAsync(user); - _logger.LogInformation("User {UserId} deleted their account.", user.Id); - - return RedirectToAction("Register"); - } - - [HttpGet] - [Authorize] - public async Task DownloadData() - { - var user = await _userManager.GetUserAsync(User); - if (user == null) return RedirectToAction("Login"); - - var roles = await _userManager.GetRolesAsync(user); - - var data = new - { - user.Id, - user.UserName, - user.Email, - Roles = roles, - ExportDate = DateTime.UtcNow, - }; - - var json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }); - var bytes = Encoding.UTF8.GetBytes(json); - - return File(bytes, "application/json", $"user_data_{user.UserName}.json"); - } - - #endregion -} diff --git a/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.Designer.cs b/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.Designer.cs deleted file mode 100644 index 848312d..0000000 --- a/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.Designer.cs +++ /dev/null @@ -1,277 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using TemplateWeb.Data; - -#nullable disable - -namespace TemplateWeb.Migrations -{ - [DbContext(typeof(AppDbContext))] - [Migration("20260501160159_SwitchToIdentity")] - partial class SwitchToIdentity - { - /// - protected override void BuildTargetModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.6") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("TemplateWeb.Entities.UserEntity", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.cs b/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.cs deleted file mode 100644 index a9ce1e3..0000000 --- a/Template/TemplateWeb/Migrations/20260501160159_SwitchToIdentity.cs +++ /dev/null @@ -1,223 +0,0 @@ -using System; -using Microsoft.EntityFrameworkCore.Migrations; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; - -#nullable disable - -namespace TemplateWeb.Migrations -{ - /// - public partial class SwitchToIdentity : Migration - { - /// - protected override void Up(MigrationBuilder migrationBuilder) - { - migrationBuilder.CreateTable( - name: "AspNetRoles", - columns: table => new - { - Id = table.Column(type: "text", nullable: false), - Name = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoles", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetUsers", - columns: table => new - { - Id = table.Column(type: "text", nullable: false), - UserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedUserName = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - Email = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - NormalizedEmail = table.Column(type: "character varying(256)", maxLength: 256, nullable: true), - EmailConfirmed = table.Column(type: "boolean", nullable: false), - PasswordHash = table.Column(type: "text", nullable: true), - SecurityStamp = table.Column(type: "text", nullable: true), - ConcurrencyStamp = table.Column(type: "text", nullable: true), - PhoneNumber = table.Column(type: "text", nullable: true), - PhoneNumberConfirmed = table.Column(type: "boolean", nullable: false), - TwoFactorEnabled = table.Column(type: "boolean", nullable: false), - LockoutEnd = table.Column(type: "timestamp with time zone", nullable: true), - LockoutEnabled = table.Column(type: "boolean", nullable: false), - AccessFailedCount = table.Column(type: "integer", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUsers", x => x.Id); - }); - - migrationBuilder.CreateTable( - name: "AspNetRoleClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - RoleId = table.Column(type: "text", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserClaims", - columns: table => new - { - Id = table.Column(type: "integer", nullable: false) - .Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn), - UserId = table.Column(type: "text", nullable: false), - ClaimType = table.Column(type: "text", nullable: true), - ClaimValue = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); - table.ForeignKey( - name: "FK_AspNetUserClaims_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserLogins", - columns: table => new - { - LoginProvider = table.Column(type: "text", nullable: false), - ProviderKey = table.Column(type: "text", nullable: false), - ProviderDisplayName = table.Column(type: "text", nullable: true), - UserId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); - table.ForeignKey( - name: "FK_AspNetUserLogins_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserRoles", - columns: table => new - { - UserId = table.Column(type: "text", nullable: false), - RoleId = table.Column(type: "text", nullable: false) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetRoles_RoleId", - column: x => x.RoleId, - principalTable: "AspNetRoles", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - table.ForeignKey( - name: "FK_AspNetUserRoles_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateTable( - name: "AspNetUserTokens", - columns: table => new - { - UserId = table.Column(type: "text", nullable: false), - LoginProvider = table.Column(type: "text", nullable: false), - Name = table.Column(type: "text", nullable: false), - Value = table.Column(type: "text", nullable: true) - }, - constraints: table => - { - table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); - table.ForeignKey( - name: "FK_AspNetUserTokens_AspNetUsers_UserId", - column: x => x.UserId, - principalTable: "AspNetUsers", - principalColumn: "Id", - onDelete: ReferentialAction.Cascade); - }); - - migrationBuilder.CreateIndex( - name: "IX_AspNetRoleClaims_RoleId", - table: "AspNetRoleClaims", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "RoleNameIndex", - table: "AspNetRoles", - column: "NormalizedName", - unique: true); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserClaims_UserId", - table: "AspNetUserClaims", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserLogins_UserId", - table: "AspNetUserLogins", - column: "UserId"); - - migrationBuilder.CreateIndex( - name: "IX_AspNetUserRoles_RoleId", - table: "AspNetUserRoles", - column: "RoleId"); - - migrationBuilder.CreateIndex( - name: "EmailIndex", - table: "AspNetUsers", - column: "NormalizedEmail"); - - migrationBuilder.CreateIndex( - name: "UserNameIndex", - table: "AspNetUsers", - column: "NormalizedUserName", - unique: true); - } - - /// - protected override void Down(MigrationBuilder migrationBuilder) - { - migrationBuilder.DropTable( - name: "AspNetRoleClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserClaims"); - - migrationBuilder.DropTable( - name: "AspNetUserLogins"); - - migrationBuilder.DropTable( - name: "AspNetUserRoles"); - - migrationBuilder.DropTable( - name: "AspNetUserTokens"); - - migrationBuilder.DropTable( - name: "AspNetRoles"); - - migrationBuilder.DropTable( - name: "AspNetUsers"); - } - } -} diff --git a/Template/TemplateWeb/Migrations/AppDbContextModelSnapshot.cs b/Template/TemplateWeb/Migrations/AppDbContextModelSnapshot.cs deleted file mode 100644 index 05b0b72..0000000 --- a/Template/TemplateWeb/Migrations/AppDbContextModelSnapshot.cs +++ /dev/null @@ -1,274 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; -using TemplateWeb.Data; - -#nullable disable - -namespace TemplateWeb.Migrations -{ - [DbContext(typeof(AppDbContext))] - partial class AppDbContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "10.0.6") - .HasAnnotation("Relational:MaxIdentifierLength", 63); - - NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Name") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedName") - .IsUnique() - .HasDatabaseName("RoleNameIndex"); - - b.ToTable("AspNetRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("RoleId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetRoleClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("integer"); - - NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property("Id")); - - b.Property("ClaimType") - .HasColumnType("text"); - - b.Property("ClaimValue") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("Id"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserClaims", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("ProviderKey") - .HasColumnType("text"); - - b.Property("ProviderDisplayName") - .HasColumnType("text"); - - b.Property("UserId") - .IsRequired() - .HasColumnType("text"); - - b.HasKey("LoginProvider", "ProviderKey"); - - b.HasIndex("UserId"); - - b.ToTable("AspNetUserLogins", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("RoleId") - .HasColumnType("text"); - - b.HasKey("UserId", "RoleId"); - - b.HasIndex("RoleId"); - - b.ToTable("AspNetUserRoles", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.Property("UserId") - .HasColumnType("text"); - - b.Property("LoginProvider") - .HasColumnType("text"); - - b.Property("Name") - .HasColumnType("text"); - - b.Property("Value") - .HasColumnType("text"); - - b.HasKey("UserId", "LoginProvider", "Name"); - - b.ToTable("AspNetUserTokens", (string)null); - }); - - modelBuilder.Entity("TemplateWeb.Entities.UserEntity", b => - { - b.Property("Id") - .HasColumnType("text"); - - b.Property("AccessFailedCount") - .HasColumnType("integer"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("text"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("EmailConfirmed") - .HasColumnType("boolean"); - - b.Property("LockoutEnabled") - .HasColumnType("boolean"); - - b.Property("LockoutEnd") - .HasColumnType("timestamp with time zone"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.Property("PasswordHash") - .HasColumnType("text"); - - b.Property("PhoneNumber") - .HasColumnType("text"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("boolean"); - - b.Property("SecurityStamp") - .HasColumnType("text"); - - b.Property("TwoFactorEnabled") - .HasColumnType("boolean"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("character varying(256)"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers", (string)null); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => - { - b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) - .WithMany() - .HasForeignKey("RoleId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); - - modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => - { - b.HasOne("TemplateWeb.Entities.UserEntity", null) - .WithMany() - .HasForeignKey("UserId") - .OnDelete(DeleteBehavior.Cascade) - .IsRequired(); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/Template/TemplateWeb/Models/ServiceResult.cs b/Template/TemplateWeb/Models/ServiceResult.cs deleted file mode 100644 index a7fb717..0000000 --- a/Template/TemplateWeb/Models/ServiceResult.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace TemplateWeb.Models; - -public class ServiceResult -{ - public bool Success { get; protected set; } - public string Message { get; protected set; } = string.Empty; - - public static ServiceResult Ok() => new() { Success = true }; - public static ServiceResult Failure(string message) => new() { Success = false, Message = message }; -} - -public class ServiceResult : ServiceResult -{ - public T? Data { get; private set; } - - public static ServiceResult Ok(T data) => new() { Success = true, Data = data }; - public new static ServiceResult Failure(string message) => new() { Success = false, Message = message }; -} diff --git a/Template/TemplateWeb/Models/UserWithRolesViewModel.cs b/Template/TemplateWeb/Models/UserWithRolesViewModel.cs deleted file mode 100644 index c20f738..0000000 --- a/Template/TemplateWeb/Models/UserWithRolesViewModel.cs +++ /dev/null @@ -1,10 +0,0 @@ -using TemplateWeb.Entities; - -namespace TemplateWeb.Models; - -public class UserWithRolesViewModel -{ - public UserEntity UserEntity { get; set; } = null!; - public IList Roles { get; set; } = []; - public IList AllRoles { get; set; } = []; -} diff --git a/Template/TemplateWeb/Program.cs b/Template/TemplateWeb/Program.cs deleted file mode 100644 index d9f3a9d..0000000 --- a/Template/TemplateWeb/Program.cs +++ /dev/null @@ -1,85 +0,0 @@ -using dotenv.net; -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using TemplateWeb.Data; -using TemplateWeb.Entities; - -namespace TemplateWeb; - -public class Program -{ - public static void Main(string[] args) - { - DotEnv.Load(); - var builder = WebApplication.CreateBuilder(args); - - // gets the connection string from configuration - var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") - ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); - - builder.Services.AddDbContext(options => - options.UseNpgsql(connectionString)); - - builder.Services.AddControllersWithViews(); - - // Configure Identity — password requirements are read from appsettings.json "Identity:Password" - var pwSection = builder.Configuration.GetSection("Identity:Password"); - builder.Services.AddIdentity(options => - { - options.Password.RequiredLength = pwSection.GetValue("RequiredLength", 6); - options.Password.RequireDigit = pwSection.GetValue("RequireDigit", true); - options.Password.RequireLowercase = pwSection.GetValue("RequireLowercase", true); - options.Password.RequireUppercase = pwSection.GetValue("RequireUppercase", true); - options.Password.RequireNonAlphanumeric = pwSection.GetValue("RequireNonAlphanumeric", false); - options.Password.RequiredUniqueChars = pwSection.GetValue("RequiredUniqueChars", 1); - }) - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - builder.Services.ConfigureApplicationCookie(options => - { - options.LoginPath = "/Auth/Login"; - options.AccessDeniedPath = "/Auth/AccessDenied"; - options.ExpireTimeSpan = TimeSpan.FromHours(2); - }); - - var app = builder.Build(); - - // Apply migrations and seed on startup - using (var scope = app.Services.CreateScope()) - { - var db = scope.ServiceProvider.GetRequiredService(); - if (db.Database.GetPendingMigrations().Any()) - { - db.Database.Migrate(); - } - - var userManager = scope.ServiceProvider.GetRequiredService>(); - var roleManager = scope.ServiceProvider.GetRequiredService>(); - DataSeeder.SeedAsync(userManager, roleManager).GetAwaiter().GetResult(); - } - - // Configure the HTTP request pipeline. - if (!app.Environment.IsDevelopment()) - { - app.UseExceptionHandler("/Home/Error"); - } - - app.UseRouting(); - - app.UseAuthentication(); - app.UseAuthorization(); - - app.MapStaticAssets(); - app.MapControllerRoute( - name: "areas", - pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); - - app.MapControllerRoute( - name: "default", - pattern: "{controller=Home}/{action=Index}/{id?}") - .WithStaticAssets(); - - app.Run(); - } -} diff --git a/Template/WebTests/AuthServiceTests.cs b/Template/WebTests/AuthServiceTests.cs deleted file mode 100644 index 9f4f02f..0000000 --- a/Template/WebTests/AuthServiceTests.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Microsoft.AspNetCore.Identity; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.DependencyInjection; -using TemplateWeb.Data; -using TemplateWeb.Entities; - -namespace WebTests; - -[TestClass] -public class AuthServiceTests -{ - private ServiceProvider _serviceProvider = null!; - private UserManager _userManager = null!; - - [TestInitialize] - public void Initialize() - { - var services = new ServiceCollection(); - - services.AddLogging(); - - services.AddDbContext(options => - options.UseInMemoryDatabase(Guid.NewGuid().ToString())); - - services.AddIdentity() - .AddEntityFrameworkStores() - .AddDefaultTokenProviders(); - - _serviceProvider = services.BuildServiceProvider(); - _userManager = _serviceProvider.GetRequiredService>(); - } - - [TestCleanup] - public void Cleanup() - { - _serviceProvider.Dispose(); - } - - [TestMethod] - public async Task CreateAsync_ShouldReturnSuccess_WhenUserIsNew() - { - // Arrange - var user = new UserEntity { UserName = "testuser", Email = "test@example.com" }; - - // Act - var result = await _userManager.CreateAsync(user, "Password123!"); - - // Assert - Assert.IsTrue(result.Succeeded); - - var userInDb = await _userManager.FindByNameAsync("testuser"); - Assert.IsNotNull(userInDb); - Assert.AreEqual("test@example.com", userInDb.Email); - } - - [TestMethod] - public async Task CreateAsync_ShouldReturnFailure_WhenUsernameAlreadyExists() - { - // Arrange - var existing = new UserEntity { UserName = "existing", Email = "existing@example.com" }; - await _userManager.CreateAsync(existing, "Password123!"); - - var duplicate = new UserEntity { UserName = "existing", Email = "other@example.com" }; - - // Act - var result = await _userManager.CreateAsync(duplicate, "Password123!"); - - // Assert - Assert.IsFalse(result.Succeeded); - Assert.IsTrue(result.Errors.Any(e => e.Code == "DuplicateUserName")); - } - - [TestMethod] - public async Task FindByNameAsync_ShouldReturnUser_CaseInsensitive() - { - // Arrange - var user = new UserEntity { UserName = "testuser", Email = "test@example.com" }; - await _userManager.CreateAsync(user, "Password123!"); - - // Act — Identity normalizes usernames, so lookup is always case-insensitive - var found = await _userManager.FindByNameAsync("TESTUSER"); - - // Assert - Assert.IsNotNull(found); - Assert.AreEqual("testuser", found.UserName); - } - - [TestMethod] - public async Task FindByNameAsync_ShouldReturnNull_WhenUserNotFound() - { - // Act - var result = await _userManager.FindByNameAsync("nonexistent"); - - // Assert - Assert.IsNull(result); - } -}