InitEshop+ddd+ca

This commit is contained in:
Matěj Kubíček
2026-07-16 18:33:05 +02:00
parent ffd8a087c8
commit 7e080eaef9
144 changed files with 972 additions and 1339 deletions
@@ -0,0 +1,13 @@
using Eshop.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace Eshop.Infrastructure;
public class AppDbContext : IdentityDbContext<UserEntity>
{
public AppDbContext(DbContextOptions<AppDbContext> options)
: base(options)
{
}
}
@@ -0,0 +1,33 @@
using Eshop.Infrastructure.Persistence;
using Microsoft.AspNetCore.Identity;
namespace Eshop.Infrastructure.Data;
public static class DataSeeder
{
public static async Task SeedAsync(UserManager<UserEntity> userManager, RoleManager<IdentityRole> roleManager)
{
// Ensure roles exist
foreach (var role in new[] { "Admin", "User" })
{
if (!await roleManager.RoleExistsAsync(role))
await roleManager.CreateAsync(new IdentityRole(role));
}
// Seed initial admin user if none exists
if (!userManager.Users.Any())
{
var admin = new UserEntity
{
UserName = "admin",
Email = "admin@template.com",
EmailConfirmed = true,
};
// You should change this password on first login
var result = await userManager.CreateAsync(admin, "Admin123!");
if (result.Succeeded)
await userManager.AddToRoleAsync(admin, "Admin");
}
}
}
@@ -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;
}
}
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="10.0.10">
<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>
</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());
}
@@ -0,0 +1,7 @@
using Microsoft.AspNetCore.Identity;
namespace Eshop.Infrastructure.Persistence;
public class UserEntity : IdentityUser
{
}