generated from egmont11/ASP.Net-Core-MVC-Template
InitEshop+ddd+ca
This commit is contained in:
Generated
+15
@@ -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
|
||||||
Generated
Generated
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+4
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" addBOMForNewFiles="with BOM under Windows, with no BOM otherwise" />
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders />
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -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 };
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public sealed record UserDataExportDto(
|
||||||
|
string Id,
|
||||||
|
string UserName,
|
||||||
|
string Email,
|
||||||
|
IReadOnlyList<string> Roles,
|
||||||
|
DateTime ExportDate);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public sealed record UserDto(string Id, string UserName, string Email);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public sealed record UserProfileDto(string UserName, string Email);
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public sealed record UserWithRolesDto(
|
||||||
|
UserDto User,
|
||||||
|
IReadOnlyList<string> Roles,
|
||||||
|
IReadOnlyList<string> AllRoles);
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public interface IAuthenticationService
|
||||||
|
{
|
||||||
|
Task<SignInResultDto> SignInAsync(
|
||||||
|
string emailOrUsername,
|
||||||
|
string password,
|
||||||
|
bool rememberMe,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task SignOutAsync(CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using Eshop.Application.Common;
|
||||||
|
|
||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public interface IUserAccountService
|
||||||
|
{
|
||||||
|
Task<ServiceResult> RegisterAsync(
|
||||||
|
string userName,
|
||||||
|
string email,
|
||||||
|
string password,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<UserProfileDto?> GetProfileAsync(
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<ServiceResult> UpdateProfileAsync(
|
||||||
|
string userId,
|
||||||
|
UpdateProfileRequest request,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<ServiceResult> DeleteAccountAsync(
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<UserDataExportDto?> ExportUserDataAsync(
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using Eshop.Application.Common;
|
||||||
|
|
||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public interface IUserAdministrationService
|
||||||
|
{
|
||||||
|
Task<IReadOnlyList<UserWithRolesDto>> GetAllUsersWithRolesAsync(
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<UserWithRolesDto?> GetUserWithRolesAsync(
|
||||||
|
string userId,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<ServiceResult> UpdateUserRolesAsync(
|
||||||
|
string userId,
|
||||||
|
IReadOnlyList<string> selectedRoles,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Eshop.Application.Auth;
|
||||||
|
|
||||||
|
public sealed record UpdateProfileRequest(
|
||||||
|
string UserName,
|
||||||
|
string Email,
|
||||||
|
string? CurrentPassword,
|
||||||
|
string? NewPassword);
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
namespace Eshop.Application.Common;
|
||||||
|
|
||||||
|
public class ServiceResult
|
||||||
|
{
|
||||||
|
public bool Success { get; protected set; }
|
||||||
|
public IReadOnlyList<string> 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<string> errors) =>
|
||||||
|
new() { Success = false, Errors = errors.ToList() };
|
||||||
|
}
|
||||||
|
|
||||||
|
public class ServiceResult<T> : ServiceResult
|
||||||
|
{
|
||||||
|
public T? Data { get; private set; }
|
||||||
|
|
||||||
|
public static ServiceResult<T> Ok(T data) => new() { Success = true, Data = data };
|
||||||
|
|
||||||
|
public new static ServiceResult<T> Failure(string message) =>
|
||||||
|
new() { Success = false, Errors = [message] };
|
||||||
|
|
||||||
|
public new static ServiceResult<T> Failure(IEnumerable<string> errors) =>
|
||||||
|
new() { Success = false, Errors = errors.ToList() };
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Eshop.Domain\Eshop.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace Eshop.Domain.Common;
|
||||||
|
|
||||||
|
public class DomainException : Exception
|
||||||
|
{
|
||||||
|
public DomainException (string message) : base(message) { }
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
|
using Eshop.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using TemplateWeb.Entities;
|
|
||||||
|
|
||||||
namespace TemplateWeb.Data;
|
namespace Eshop.Infrastructure;
|
||||||
|
|
||||||
public class AppDbContext : IdentityDbContext<UserEntity>
|
public class AppDbContext : IdentityDbContext<UserEntity>
|
||||||
{
|
{
|
||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
|
using Eshop.Infrastructure.Persistence;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using TemplateWeb.Entities;
|
|
||||||
|
|
||||||
namespace TemplateWeb.Data;
|
namespace Eshop.Infrastructure.Data;
|
||||||
|
|
||||||
public static class DataSeeder
|
public static class DataSeeder
|
||||||
{
|
{
|
||||||
@@ -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<AppDbContext>(options =>
|
||||||
|
options.UseNpgsql(connectionString));
|
||||||
|
|
||||||
|
var pwSection = configuration.GetSection("Identity:Password");
|
||||||
|
services.AddIdentity<UserEntity, IdentityRole>(options =>
|
||||||
|
{
|
||||||
|
options.Password.RequiredLength = pwSection.GetValue<int>("RequiredLength", 6);
|
||||||
|
options.Password.RequireDigit = pwSection.GetValue<bool>("RequireDigit", true);
|
||||||
|
options.Password.RequireLowercase = pwSection.GetValue<bool>("RequireLowercase", true);
|
||||||
|
options.Password.RequireUppercase = pwSection.GetValue<bool>("RequireUppercase", true);
|
||||||
|
options.Password.RequireNonAlphanumeric = pwSection.GetValue<bool>("RequireNonAlphanumeric", false);
|
||||||
|
options.Password.RequiredUniqueChars = pwSection.GetValue<int>("RequiredUniqueChars", 1);
|
||||||
|
})
|
||||||
|
.AddEntityFrameworkStores<AppDbContext>()
|
||||||
|
.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<IAuthenticationService, AspNetIdentityAuthenticationService>();
|
||||||
|
services.AddScoped<IUserAccountService, AspNetIdentityUserAccountService>();
|
||||||
|
services.AddScoped<IUserAdministrationService, AspNetIdentityUserAdministrationService>();
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-13
@@ -1,26 +1,27 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="dotenv.net" Version="4.0.2" />
|
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.6" />
|
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.6">
|
|
||||||
<PrivateAssets>all</PrivateAssets>
|
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
|
||||||
</PackageReference>
|
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="..\.dockerignore">
|
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.10" />
|
||||||
<Link>.dockerignore</Link>
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
|
||||||
</Content>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Eshop.Application\Eshop.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\Eshop.Domain\Eshop.Domain.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Eshop.Application.Auth;
|
||||||
|
using Eshop.Infrastructure.Persistence;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
|
namespace Eshop.Infrastructure.Identity;
|
||||||
|
|
||||||
|
public class AspNetIdentityAuthenticationService(
|
||||||
|
UserManager<UserEntity> users,
|
||||||
|
SignInManager<UserEntity> signIn) : IAuthenticationService
|
||||||
|
{
|
||||||
|
public async Task<SignInResultDto> 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();
|
||||||
|
}
|
||||||
@@ -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<UserEntity> users,
|
||||||
|
SignInManager<UserEntity> signIn) : IUserAccountService
|
||||||
|
{
|
||||||
|
public async Task<ServiceResult> 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<UserProfileDto?> 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<ServiceResult> 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<string>();
|
||||||
|
|
||||||
|
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<ServiceResult> 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<UserDataExportDto?> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<UserEntity> users,
|
||||||
|
RoleManager<IdentityRole> roles) : IUserAdministrationService
|
||||||
|
{
|
||||||
|
public async Task<IReadOnlyList<UserWithRolesDto>> GetAllUsersWithRolesAsync(
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var allRoles = roles.Roles.Select(r => r.Name!).ToList();
|
||||||
|
var viewModels = new List<UserWithRolesDto>();
|
||||||
|
|
||||||
|
foreach (var user in users.Users.ToList())
|
||||||
|
{
|
||||||
|
var userRoles = await users.GetRolesAsync(user);
|
||||||
|
viewModels.Add(ToDto(user, userRoles, allRoles));
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewModels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserWithRolesDto?> 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<ServiceResult> UpdateUserRolesAsync(
|
||||||
|
string userId,
|
||||||
|
IReadOnlyList<string> 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<string> userRoles,
|
||||||
|
IList<string> allRoles) =>
|
||||||
|
new(
|
||||||
|
new UserDto(user.Id, user.UserName!, user.Email!),
|
||||||
|
userRoles.ToList(),
|
||||||
|
allRoles.ToList());
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
|
||||||
namespace TemplateWeb.Entities;
|
namespace Eshop.Infrastructure.Persistence;
|
||||||
|
|
||||||
public class UserEntity : IdentityUser
|
public class UserEntity : IdentityUser
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace TemplateWeb.Areas.Admin.Controllers;
|
namespace Eshop.Web.Areas.Admin.Controllers;
|
||||||
|
|
||||||
[Area("Admin")]
|
[Area("Admin")]
|
||||||
[Authorize(Roles = "Admin")]
|
[Authorize(Roles = "Admin")]
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
namespace TemplateWeb.Areas.Admin.Controllers;
|
namespace Eshop.Web.Areas.Admin.Controllers;
|
||||||
|
|
||||||
public class DashboardController : AdminBaseController
|
public class DashboardController : AdminBaseController
|
||||||
{
|
{
|
||||||
@@ -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<IActionResult> Index()
|
||||||
|
{
|
||||||
|
var users = await _userAdministrationService.GetAllUsersWithRolesAsync();
|
||||||
|
return View(users.Select(ToViewModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> Details(string id)
|
||||||
|
{
|
||||||
|
var user = await _userAdministrationService.GetUserWithRolesAsync(id);
|
||||||
|
if (user is null) return NotFound();
|
||||||
|
|
||||||
|
return View(ToViewModel(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> UpdateRoles(string id, List<string> 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
+10
-5
@@ -12,20 +12,25 @@
|
|||||||
<div class="alert alert-success">@TempData["SuccessMessage"]</div>
|
<div class="alert alert-success">@TempData["SuccessMessage"]</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (TempData["ErrorMessage"] != null)
|
||||||
|
{
|
||||||
|
<div class="alert alert-danger">@TempData["ErrorMessage"]</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header bg-primary text-white">
|
<div class="card-header bg-primary text-white">
|
||||||
<h3 class="card-title mb-0">User Information: @Model.UserEntity.UserName</h3>
|
<h3 class="card-title mb-0">User Information: @Model.User.UserName</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-sm-3">ID</dt>
|
<dt class="col-sm-3">ID</dt>
|
||||||
<dd class="col-sm-9">@Model.UserEntity.Id</dd>
|
<dd class="col-sm-9">@Model.User.Id</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">Username</dt>
|
<dt class="col-sm-3">Username</dt>
|
||||||
<dd class="col-sm-9">@Model.UserEntity.UserName</dd>
|
<dd class="col-sm-9">@Model.User.UserName</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">Email</dt>
|
<dt class="col-sm-3">Email</dt>
|
||||||
<dd class="col-sm-9">@Model.UserEntity.Email</dd>
|
<dd class="col-sm-9">@Model.User.Email</dd>
|
||||||
|
|
||||||
<dt class="col-sm-3">Roles</dt>
|
<dt class="col-sm-3">Roles</dt>
|
||||||
<dd class="col-sm-9">
|
<dd class="col-sm-9">
|
||||||
@@ -43,7 +48,7 @@
|
|||||||
<h5 class="mb-0">Edit Roles</h5>
|
<h5 class="mb-0">Edit Roles</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form asp-action="UpdateRoles" asp-route-id="@Model.UserEntity.Id" method="post">
|
<form asp-action="UpdateRoles" asp-route-id="@Model.User.Id" method="post">
|
||||||
@Html.AntiForgeryToken()
|
@Html.AntiForgeryToken()
|
||||||
<div class="d-flex gap-4 flex-wrap mb-3">
|
<div class="d-flex gap-4 flex-wrap mb-3">
|
||||||
@foreach (var role in Model.AllRoles)
|
@foreach (var role in Model.AllRoles)
|
||||||
+4
-4
@@ -21,9 +21,9 @@
|
|||||||
@foreach (var vm in Model)
|
@foreach (var vm in Model)
|
||||||
{
|
{
|
||||||
<tr>
|
<tr>
|
||||||
<td>@vm.UserEntity.Id</td>
|
<td>@vm.User.Id</td>
|
||||||
<td>@vm.UserEntity.UserName</td>
|
<td>@vm.User.UserName</td>
|
||||||
<td>@vm.UserEntity.Email</td>
|
<td>@vm.User.Email</td>
|
||||||
<td>
|
<td>
|
||||||
@foreach (var role in vm.Roles)
|
@foreach (var role in vm.Roles)
|
||||||
{
|
{
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a asp-action="Details" asp-route-id="@vm.UserEntity.Id" class="btn btn-sm btn-outline-primary">Details</a>
|
<a asp-action="Details" asp-route-id="@vm.User.Id" class="btn btn-sm btn-outline-primary">Details</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
@using Eshop.Web
|
||||||
|
@using Eshop.Web.Models
|
||||||
|
@using Eshop.Application.Auth
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
@@ -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<AuthController> _logger;
|
||||||
|
private readonly IAuthenticationService _authenticationService;
|
||||||
|
private readonly IUserAccountService _userAccountService;
|
||||||
|
|
||||||
|
public AuthController(
|
||||||
|
ILogger<AuthController> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> Logout()
|
||||||
|
{
|
||||||
|
await _authenticationService.SignOutAsync();
|
||||||
|
return RedirectToAction("Index", "Home");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Authorize]
|
||||||
|
[ValidateAntiForgeryToken]
|
||||||
|
public async Task<IActionResult> 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<IActionResult> 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);
|
||||||
|
}
|
||||||
+2
-2
@@ -1,8 +1,8 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using Eshop.Web.ViewModels;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using TemplateWeb.Models;
|
|
||||||
|
|
||||||
namespace TemplateWeb.Controllers;
|
namespace Eshop.Web.Controllers;
|
||||||
|
|
||||||
public class HomeController : Controller
|
public class HomeController : Controller
|
||||||
{
|
{
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="dotenv.net" Version="4.0.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Eshop.Application\Eshop.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\Eshop.Infrastructure\Eshop.Infrastructure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="..\.dockerignore">
|
||||||
|
<Link>.dockerignore</Link>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using Eshop.Application.Auth;
|
||||||
|
|
||||||
|
namespace Eshop.Web.Models;
|
||||||
|
|
||||||
|
public class UserWithRolesViewModel
|
||||||
|
{
|
||||||
|
public UserDto User { get; set; } = null!;
|
||||||
|
public IReadOnlyList<string> Roles { get; set; } = [];
|
||||||
|
public IReadOnlyList<string> AllRoles { get; set; } = [];
|
||||||
|
}
|
||||||
@@ -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<AppDbContext>();
|
||||||
|
if (db.Database.GetPendingMigrations().Any())
|
||||||
|
db.Database.Migrate();
|
||||||
|
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<UserEntity>>();
|
||||||
|
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<IdentityRole>>();
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace TemplateWeb.Models;
|
namespace Eshop.Web.ViewModels;
|
||||||
|
|
||||||
public class AuthLoginViewModel
|
public class AuthLoginViewModel
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace TemplateWeb.Models;
|
namespace Eshop.Web.ViewModels;
|
||||||
|
|
||||||
public class AuthRegisterViewModel
|
public class AuthRegisterViewModel
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
namespace TemplateWeb.Models;
|
namespace Eshop.Web.ViewModels;
|
||||||
|
|
||||||
public class ErrorViewModel
|
public class ErrorViewModel
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace TemplateWeb.Models;
|
namespace Eshop.Web.ViewModels;
|
||||||
|
|
||||||
public class ProfileViewModel
|
public class ProfileViewModel
|
||||||
{
|
{
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@model AuthLoginViewModel
|
@model Eshop.Web.ViewModels.AuthLoginViewModel
|
||||||
|
|
||||||
<div class="row justify-content-center mt-5">
|
<div class="row justify-content-center mt-5">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@model ProfileViewModel
|
@model Eshop.Web.ViewModels.ProfileViewModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "My Profile";
|
ViewData["Title"] = "My Profile";
|
||||||
}
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@model AuthRegisterViewModel
|
@model Eshop.Web.ViewModels.AuthRegisterViewModel
|
||||||
|
|
||||||
<div class="row justify-content-center mt-5">
|
<div class="row justify-content-center mt-5">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
@model ErrorViewModel
|
@model Eshop.Web.ViewModels.ErrorViewModel
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Error";
|
ViewData["Title"] = "Error";
|
||||||
}
|
}
|
||||||
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
@using TemplateWeb
|
@using Eshop.Web
|
||||||
@using TemplateWeb.Models
|
@using Eshop.Web.Models
|
||||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.3 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user