Create models and Clean Arch boilerplate
This commit is contained in:
parent
3926db5446
commit
872dc1e263
21 changed files with 576 additions and 27 deletions
|
@ -1,6 +0,0 @@
|
||||||
namespace CSR.Application;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
13
CSR.Application/Interfaces/IRoleRepository.cs
Normal file
13
CSR.Application/Interfaces/IRoleRepository.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
namespace CSR.Application.Interfaces;
|
||||||
|
|
||||||
|
using CSR.Domain.Entities;
|
||||||
|
|
||||||
|
public interface IRoleRepository
|
||||||
|
{
|
||||||
|
Task<Role?> GetByIdAsync(int id);
|
||||||
|
// Task<Role?> GetByNameAsync(string name);
|
||||||
|
// Task<IEnumerable<Role>> GetAllAsync();
|
||||||
|
Task AddAsync(Role role);
|
||||||
|
// Task UpdateAsync(Role role);
|
||||||
|
Task DeleteAsync(int id);
|
||||||
|
}
|
14
CSR.Application/Interfaces/IUserRepository.cs
Normal file
14
CSR.Application/Interfaces/IUserRepository.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace CSR.Application.Interfaces;
|
||||||
|
|
||||||
|
using CSR.Domain.Entities;
|
||||||
|
|
||||||
|
public interface IUserRepository
|
||||||
|
{
|
||||||
|
Task<User?> GetByIdAsync(int id);
|
||||||
|
// Task<User?> GetByEmailAsync(string email);
|
||||||
|
// Task<User?> GetByUsernameAsync(string username);
|
||||||
|
// Task<IEnumerable<User>> GetAllAsync();
|
||||||
|
Task AddAsync(User user);
|
||||||
|
Task UpdateAsync(User user);
|
||||||
|
Task DeleteAsync(int id);
|
||||||
|
}
|
8
CSR.Application/Interfaces/IUserService.cs
Normal file
8
CSR.Application/Interfaces/IUserService.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace CSR.Application.Interfaces;
|
||||||
|
|
||||||
|
public interface IUserService
|
||||||
|
{
|
||||||
|
public record CreateUserResult(Domain.Entities.User? User, string? ErrorMessage);
|
||||||
|
|
||||||
|
public void CreateUser(string username, string email, string password, bool isAdmin = false);
|
||||||
|
}
|
14
CSR.Application/Services/UserService.cs
Normal file
14
CSR.Application/Services/UserService.cs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
namespace CSR.Application.Services;
|
||||||
|
|
||||||
|
using CSR.Application.Interfaces;
|
||||||
|
using CSR.Domain.Entities;
|
||||||
|
|
||||||
|
public class UserService(IUserRepository userRepository) : IUserService
|
||||||
|
{
|
||||||
|
private readonly IUserRepository _userRepository = userRepository;
|
||||||
|
|
||||||
|
public void CreateNewUser(string username, string email, string password, bool isAdmin = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,4 +6,8 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<InternalsVisibleTo Include="CSR.Infrastructure" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
33
CSR.Domain/Entities/Role.cs
Normal file
33
CSR.Domain/Entities/Role.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
namespace CSR.Domain.Entities;
|
||||||
|
|
||||||
|
public record Role
|
||||||
|
{
|
||||||
|
public int Id { get; private set; }
|
||||||
|
public string Name { get; private set; }
|
||||||
|
|
||||||
|
private Role(int id, string name)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Role LoadExisting(int id, string name)
|
||||||
|
{
|
||||||
|
return new Role(id, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Role Admin
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new Role(1, "Admin");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static Role User
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new Role(2, "User");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
173
CSR.Domain/Entities/User.cs
Normal file
173
CSR.Domain/Entities/User.cs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
namespace CSR.Domain.Entities;
|
||||||
|
|
||||||
|
using System.Net.Mail;
|
||||||
|
using CSR.Domain.Interfaces;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; private set; }
|
||||||
|
public string Username { get; private set; }
|
||||||
|
public string Email { get; private set; }
|
||||||
|
public string PasswordHash { get; private set; }
|
||||||
|
|
||||||
|
public int RoleId { get; private set; }
|
||||||
|
public Role Role { get; private set; }
|
||||||
|
|
||||||
|
private User(int id, string username, string email, string passwordHash, int roleId, Role role)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
Username = username;
|
||||||
|
Email = email;
|
||||||
|
PasswordHash = passwordHash;
|
||||||
|
RoleId = roleId;
|
||||||
|
Role = role;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This role is used when a new user is created
|
||||||
|
public static readonly Role DefaultRole = Role.User;
|
||||||
|
|
||||||
|
|
||||||
|
internal static User LoadExisting(int id, string username, string email, string passwordHash, int roleId, Role role)
|
||||||
|
{
|
||||||
|
return new User(id, username, email, passwordHash, roleId, role);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public (bool Success, IEnumerable<string>? Errors) UpdatePassword(string password, IPasswordHasher passwordHasher, User requestingUser)
|
||||||
|
{
|
||||||
|
if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Only admins or the same user can update passwords.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var validityCheck = IsValidPassword(password);
|
||||||
|
|
||||||
|
if (validityCheck.IsValid)
|
||||||
|
{
|
||||||
|
PasswordHash = passwordHasher.HashPassword(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return validityCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public bool VerifyPasswordAgainstHash(string providedPassword, IPasswordHasher passwordHasher, User requestingUser)
|
||||||
|
{
|
||||||
|
if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Only admins or the same user can verify passwords.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return passwordHasher.VerifyHashedPassword(PasswordHash, providedPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static (bool IsValid, IEnumerable<string>? Errors) IsValidPassword(string password)
|
||||||
|
{
|
||||||
|
var Errors = new List<string>();
|
||||||
|
if (password.Length < 8 || password.Length > 32)
|
||||||
|
{
|
||||||
|
Errors.Add("Password must be between 8 and 32 characters long.");
|
||||||
|
}
|
||||||
|
if (!password.Any(char.IsUpper))
|
||||||
|
{
|
||||||
|
Errors.Add("Password must contain at least one uppercase letter.");
|
||||||
|
}
|
||||||
|
if (!password.Any(char.IsLower))
|
||||||
|
{
|
||||||
|
Errors.Add("Password must contain at least one lowercase letter.");
|
||||||
|
}
|
||||||
|
if (!password.Any(char.IsDigit))
|
||||||
|
{
|
||||||
|
Errors.Add("Password must contain at least one digit.");
|
||||||
|
}
|
||||||
|
if (Errors.Count > 0)
|
||||||
|
{
|
||||||
|
return (false, Errors);
|
||||||
|
}
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public (bool Success, IEnumerable<string>? Errors) UpdateEmail(string email, User requestingUser)
|
||||||
|
{
|
||||||
|
if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Only admins or the same user can update email.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsValidEmail(email))
|
||||||
|
{
|
||||||
|
Email = email;
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
return (false, new List<string> { "Invalid email format." });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static bool IsValidEmail(string email)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var mailAddress = new MailAddress(email);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public (bool Success, IEnumerable<string>? Errors) UpdateUsername(string username, User requestingUser)
|
||||||
|
{
|
||||||
|
if (requestingUser.Id != Id || requestingUser.Role.Name != "Admin")
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Only admins can update username.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var validityCheck = IsValidUsername(username);
|
||||||
|
if (validityCheck.IsValid)
|
||||||
|
{
|
||||||
|
Username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validityCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static (bool IsValid, IEnumerable<string>? Errors) IsValidUsername(string username)
|
||||||
|
{
|
||||||
|
var Errors = new List<string>();
|
||||||
|
if (!username.All(char.IsLetterOrDigit))
|
||||||
|
{
|
||||||
|
Errors.Add("Username must be alphanumeric.");
|
||||||
|
}
|
||||||
|
if (string.IsNullOrWhiteSpace(username) || username.Length < 3)
|
||||||
|
{
|
||||||
|
Errors.Add("Username must be at least 3 characters long.");
|
||||||
|
}
|
||||||
|
if (username.Length > 32)
|
||||||
|
{
|
||||||
|
Errors.Add("Username must be less than 32 characters long.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Errors.Count > 0)
|
||||||
|
{
|
||||||
|
return (false, Errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (true, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void UpdateRole(Role role, User requestingUser)
|
||||||
|
{
|
||||||
|
if (requestingUser.Role.Name != "Admin")
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException("Only admins can assign roles.");
|
||||||
|
}
|
||||||
|
Role = role;
|
||||||
|
RoleId = role.Id;
|
||||||
|
}
|
||||||
|
}
|
7
CSR.Domain/Interfaces/IPasswordHasher.cs
Normal file
7
CSR.Domain/Interfaces/IPasswordHasher.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace CSR.Domain.Interfaces;
|
||||||
|
|
||||||
|
public interface IPasswordHasher
|
||||||
|
{
|
||||||
|
string HashPassword(string password);
|
||||||
|
bool VerifyHashedPassword(string hashedPassword, string providedPassword);
|
||||||
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
namespace CSR.Domain;
|
|
||||||
|
|
||||||
public class Role
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace CSR.Domain;
|
|
||||||
|
|
||||||
public class User
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\CSR.Application\CSR.Application.csproj" />
|
<ProjectReference Include="..\CSR.Application\CSR.Application.csproj" />
|
||||||
|
<ProjectReference Include="..\CSR.Domain\CSR.Domain.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.5">
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
namespace CSR.Infrastructure;
|
|
||||||
|
|
||||||
public class Class1
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
37
CSR.Infrastructure/Persistence/CSRDbContext.cs
Normal file
37
CSR.Infrastructure/Persistence/CSRDbContext.cs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
namespace CSR.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using CSR.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
|
||||||
|
public class CSRDbContext(DbContextOptions<CSRDbContext> options) : DbContext(options)
|
||||||
|
{
|
||||||
|
public DbSet<User> Users { get; set; }
|
||||||
|
public DbSet<Role> Roles { get; set; }
|
||||||
|
|
||||||
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
|
{
|
||||||
|
modelBuilder.ApplyConfiguration(new UserConfiguration());
|
||||||
|
modelBuilder.ApplyConfiguration(new RoleConfiguration());
|
||||||
|
|
||||||
|
modelBuilder.Entity<User>()
|
||||||
|
.HasIndex(u => u.Username)
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
modelBuilder.Entity<User>()
|
||||||
|
.HasOne(u => u.Role)
|
||||||
|
.WithMany(r => r.Users)
|
||||||
|
.HasForeignKey(u => u.RoleId);
|
||||||
|
|
||||||
|
// --- Seed data --- //
|
||||||
|
|
||||||
|
var adminRole = new Role { Id = 1, Name = "Admin" };
|
||||||
|
var userRole = new Role { Id = 2, Name = "User" };
|
||||||
|
|
||||||
|
modelBuilder.Entity<Role>()
|
||||||
|
.HasData(adminRole, userRole);
|
||||||
|
|
||||||
|
|
||||||
|
base.OnModelCreating(modelBuilder);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
namespace CSR.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
|
||||||
|
public class RoleConfiguration : IEntityTypeConfiguration<Role>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<Role> builder)
|
||||||
|
{
|
||||||
|
builder.Property(u => u.Id)
|
||||||
|
.HasColumnName("RoleId")
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
|
builder.Property(u => u.Name)
|
||||||
|
.HasColumnName("RoleName")
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace CSR.Infrastructure.Persistence.Configurations;
|
||||||
|
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
|
||||||
|
public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<User> builder)
|
||||||
|
{
|
||||||
|
builder.Property(u => u.PasswordHash)
|
||||||
|
.HasColumnName("Password")
|
||||||
|
.IsRequired();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,55 @@
|
||||||
|
namespace Csr.Infrastructure.Persistence.Repositories;
|
||||||
|
|
||||||
|
using CSR.Domain.Entities;
|
||||||
|
using CSR.Application.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public class RoleRepository(CSR.Infrastructure.Persistence.CSRDbContext context) : IRoleRepository
|
||||||
|
{
|
||||||
|
private readonly CSR.Infrastructure.Persistence.CSRDbContext _context = context;
|
||||||
|
|
||||||
|
public async Task<Role?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
var roleEntity = await _context.Roles
|
||||||
|
.Include(r => r.Id)
|
||||||
|
.SingleOrDefaultAsync(r => r.Id == id);
|
||||||
|
|
||||||
|
if (roleEntity == null)
|
||||||
|
{
|
||||||
|
return null; // No entity found, return null domain model
|
||||||
|
}
|
||||||
|
|
||||||
|
var role = Role.LoadExisting(
|
||||||
|
roleEntity.Id,
|
||||||
|
roleEntity.Name
|
||||||
|
);
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task AddAsync(Role role)
|
||||||
|
{
|
||||||
|
var roleEntity = new CSR.Infrastructure.Persistence.Role
|
||||||
|
{
|
||||||
|
Id = role.Id,
|
||||||
|
Name = role.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Roles.Add(roleEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id)
|
||||||
|
{
|
||||||
|
var roleEntity = new CSR.Infrastructure.Persistence.Role
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = string.Empty
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Roles.Remove(roleEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
105
CSR.Infrastructure/Persistence/Repositories/UserRepository.cs
Normal file
105
CSR.Infrastructure/Persistence/Repositories/UserRepository.cs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
namespace Csr.Infrastructure.Persistence.Repositories;
|
||||||
|
|
||||||
|
using CSR.Domain.Entities;
|
||||||
|
using CSR.Application.Interfaces;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
public class UserRepository(CSR.Infrastructure.Persistence.CSRDbContext context) : IUserRepository
|
||||||
|
{
|
||||||
|
private readonly CSR.Infrastructure.Persistence.CSRDbContext _context = context;
|
||||||
|
|
||||||
|
public async Task<User?> GetByIdAsync(int id)
|
||||||
|
{
|
||||||
|
var userEntity = await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.SingleOrDefaultAsync(u => u.Id == id);
|
||||||
|
|
||||||
|
if (userEntity == null)
|
||||||
|
{
|
||||||
|
return null; // No entity found, return null domain model
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = User.LoadExisting(
|
||||||
|
userEntity.Id,
|
||||||
|
userEntity.Username,
|
||||||
|
userEntity.Email,
|
||||||
|
userEntity.PasswordHash,
|
||||||
|
userEntity.RoleId,
|
||||||
|
Role.LoadExisting(
|
||||||
|
userEntity.Role.Id,
|
||||||
|
userEntity.Role.Name
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task AddAsync(User user)
|
||||||
|
{
|
||||||
|
var userEntity = new CSR.Infrastructure.Persistence.User
|
||||||
|
{
|
||||||
|
Username = user.Username,
|
||||||
|
Email = user.Email,
|
||||||
|
PasswordHash = user.PasswordHash,
|
||||||
|
RoleId = user.RoleId,
|
||||||
|
Role = new CSR.Infrastructure.Persistence.Role
|
||||||
|
{
|
||||||
|
Id = user.Role.Id,
|
||||||
|
Name = user.Role.Name
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Users.Add(userEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task UpdateAsync(User user)
|
||||||
|
{
|
||||||
|
var userEntity = await _context.Users
|
||||||
|
.Include(u => u.Role)
|
||||||
|
.FirstOrDefaultAsync(u => u.Id == user.Id);
|
||||||
|
|
||||||
|
if (userEntity == null)
|
||||||
|
{
|
||||||
|
// NOTE should I throw an exception here?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
userEntity.Id = user.Id;
|
||||||
|
userEntity.Username = user.Username;
|
||||||
|
userEntity.Email = user.Email;
|
||||||
|
userEntity.PasswordHash = user.PasswordHash;
|
||||||
|
userEntity.RoleId = user.RoleId;
|
||||||
|
userEntity.Role = new CSR.Infrastructure.Persistence.Role { Id = user.Role.Id, Name = user.Role.Name };
|
||||||
|
|
||||||
|
// Prevent EF from trying to update the Role entity
|
||||||
|
_context.Entry(userEntity.Role).State = EntityState.Unchanged;
|
||||||
|
|
||||||
|
_context.Users.Update(userEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task DeleteAsync(int id)
|
||||||
|
{
|
||||||
|
var userEntity = new CSR.Infrastructure.Persistence.User
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Username = string.Empty,
|
||||||
|
Email = string.Empty,
|
||||||
|
PasswordHash = string.Empty,
|
||||||
|
RoleId = 0,
|
||||||
|
Role = new CSR.Infrastructure.Persistence.Role
|
||||||
|
{
|
||||||
|
Id = 0,
|
||||||
|
Name = string.Empty
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
_context.Users.Remove(userEntity);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
10
CSR.Infrastructure/Persistence/Role.cs
Normal file
10
CSR.Infrastructure/Persistence/Role.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
namespace CSR.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
public class Role
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
// Navigation property
|
||||||
|
public ICollection<User> Users { get; set; } = new HashSet<User>();
|
||||||
|
}
|
17
CSR.Infrastructure/Persistence/User.cs
Normal file
17
CSR.Infrastructure/Persistence/User.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace CSR.Infrastructure.Persistence;
|
||||||
|
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public required string Username { get; set; }
|
||||||
|
public required string Email { get; set; }
|
||||||
|
public required string PasswordHash { get; set; }
|
||||||
|
public required int RoleId { get; set; }
|
||||||
|
|
||||||
|
// Navigation property
|
||||||
|
public Role Role { get; set; } = null!;
|
||||||
|
|
||||||
|
// prevent direct instantiation
|
||||||
|
// use UserService to create a new user
|
||||||
|
internal User() { }
|
||||||
|
}
|
|
@ -1,16 +1,57 @@
|
||||||
|
using CSR.Infrastructure.Persistence;
|
||||||
|
using CSR.Application.Services;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
|
// Get configuration from appsettings.json, environment variables, and Docker secrets in that order
|
||||||
|
builder.Configuration
|
||||||
|
.AddJsonFile("appsettings.json", optional: true)
|
||||||
|
.AddEnvironmentVariables()
|
||||||
|
.AddKeyPerFile("/run/secrets", optional: true);
|
||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorPages();
|
builder.Services.AddRazorPages();
|
||||||
|
|
||||||
|
// Set up connection to SQLite database
|
||||||
|
var dbPath = builder.Configuration["Database:Path"];
|
||||||
|
if (string.IsNullOrEmpty(dbPath))
|
||||||
|
{
|
||||||
|
var folder = Environment.SpecialFolder.LocalApplicationData;
|
||||||
|
var path = Environment.GetFolderPath(folder);
|
||||||
|
dbPath = Path.Join(path, "csr.db");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddDbContext<CSRDbContext>(options =>
|
||||||
|
{
|
||||||
|
options.UseSqlite($"Data Source={dbPath}");
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
using (var scope = app.Services.CreateScope())
|
||||||
|
{
|
||||||
|
var services = scope.ServiceProvider;
|
||||||
|
var config = services.GetRequiredService<IConfiguration>();
|
||||||
|
|
||||||
|
var context = services.GetRequiredService<CSRDbContext>();
|
||||||
|
var userService = services.GetRequiredService<UserService>();
|
||||||
|
|
||||||
|
var adminUsername = config["Admin:Username"] ?? "admin";
|
||||||
|
var adminEmail = config["Admin:Email"] ?? "";
|
||||||
|
var adminPassword = config["Admin:Password"] ?? "password";
|
||||||
|
|
||||||
|
userService.CreateUser(adminUsername, adminEmail, adminPassword, true);
|
||||||
|
}
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
app.UseExceptionHandler("/Error");
|
app.UseExceptionHandler("/Error");
|
||||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||||
app.UseHsts();
|
app.UseHsts();
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseHttpsRedirection();
|
app.UseHttpsRedirection();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue