Merge pull request #138 from aaronpo97/add-domain-project

Add domain project
This commit is contained in:
Aaron Po
2026-02-11 13:31:49 -05:00
committed by GitHub
25 changed files with 114 additions and 82 deletions

View File

@@ -3,7 +3,7 @@
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>WebAPI</RootNamespace>
<RootNamespace>API.Core</RootNamespace>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
@@ -11,16 +11,17 @@
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.6.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Infrastructure\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Core\Domain.Core.csproj" />
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
<ProjectReference Include="..\..\Service\Service.Core\Service.Core.csproj" />
</ItemGroup>
<ItemGroup>
<Content Include="..\..\.dockerignore">
<Link>.dockerignore</Link>

View File

@@ -1,10 +1,9 @@
using System.Net;
using Repository.Core.Entities;
using Domain.Core.Entities;
using Microsoft.AspNetCore.Mvc;
using Service.Core.Auth;
using Service.Core.Jwt;
namespace WebAPI.Controllers
namespace API.Core.Controllers
{
[ApiController]
[Route("api/[controller]")]

View File

@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc;
namespace WebAPI.Controllers
namespace API.Core.Controllers
{
[ApiController]
[ApiExplorerSettings(IgnoreApi = true)]

View File

@@ -1,8 +1,8 @@
using Repository.Core.Entities;
using Domain.Core.Entities;
using Microsoft.AspNetCore.Mvc;
using Service.Core.User;
namespace WebAPI.Controllers
namespace API.Core.Controllers
{
[ApiController]
[Route("api/[controller]")]

View File

@@ -7,6 +7,10 @@
<Project Path="Database/Database.Migrations/Database.Migrations.csproj" />
<Project Path="Database/Database.Seed/Database.Seed.csproj" />
</Folder>
<Folder Name="/Domain/">
<Project Path="Domain/Domain.Core/Domain.Core.csproj" />
<Project Path="Domain/Domain.Validation/Domain.Validation.csproj" />
</Folder>
<Folder Name="/Repository/">
<Project Path="Repository/Repository.Core/Repository.Core.csproj" />
<Project Path="Repository/Repository.Tests/Repository.Tests.csproj" />

View File

@@ -18,6 +18,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Core\Domain.Core.csproj" />
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,7 +1,7 @@
using System.Data;
using System.Security.Cryptography;
using System.Text;
using Repository.Core.Entities;
using Domain.Core.Entities;
using Repository.Core.Repositories;
using idunno.Password;
using Konscious.Security.Cryptography;

View File

@@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Domain.Core</RootNamespace>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,14 @@
namespace Domain.Core.Entities;
public class UserAccount
{
public Guid UserAccountId { get; set; }
public string Username { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,11 @@
namespace Domain.Core.Entities;
public class UserCredential
{
public Guid UserCredentialId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime Expiry { get; set; }
public string Hash { get; set; } = string.Empty;
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,9 @@
namespace Domain.Core.Entities;
public class UserVerification
{
public Guid UserVerificationId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime VerificationDateTime { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Domain.Validation</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Domain.Core\Domain.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,16 +0,0 @@
namespace Repository.Core.Entities;
public class UserAccount
{
public Guid UserAccountId { get; set; }
public string Username { get; set; } = string.Empty;
public string FirstName { get; set; } = string.Empty;
public string LastName { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
public DateTime DateOfBirth { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -1,11 +0,0 @@
namespace Repository.Core.Entities;
public class UserCredential
{
public Guid UserCredentialId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime Expiry { get; set; }
public string Hash { get; set; } = string.Empty;
public byte[]? Timer { get; set; }
}

View File

@@ -1,9 +0,0 @@
namespace Repository.Core.Entities;
public class UserVerification
{
public Guid UserVerificationId { get; set; }
public Guid UserAccountId { get; set; }
public DateTime VerificationDateTime { get; set; }
public byte[]? Timer { get; set; }
}

View File

@@ -1,10 +1,11 @@
using System.Data;
using System.Data.Common;
using Domain.Core.Entities;
using Repository.Core.Sql;
namespace Repository.Core.Repositories.Auth
{
public class AuthRepository : Repository<Entities.UserAccount>, IAuthRepository
public class AuthRepository : Repository<Domain.Core.Entities.UserAccount>, IAuthRepository
{
public AuthRepository(ISqlConnectionFactory connectionFactory)
: base(connectionFactory)
@@ -12,7 +13,7 @@ namespace Repository.Core.Repositories.Auth
}
public async Task<Entities.UserAccount> RegisterUserAsync(
public async Task<Domain.Core.Entities.UserAccount> RegisterUserAsync(
string username,
string firstName,
string lastName,
@@ -36,7 +37,7 @@ namespace Repository.Core.Repositories.Auth
var result = await command.ExecuteScalarAsync();
var userAccountId = result != null ? (Guid)result : Guid.Empty;
return new Entities.UserAccount
return new Domain.Core.Entities.UserAccount
{
UserAccountId = userAccountId,
Username = username,
@@ -49,7 +50,7 @@ namespace Repository.Core.Repositories.Auth
}
public async Task<Entities.UserAccount?> GetUserByEmailAsync(string email)
public async Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -63,7 +64,7 @@ namespace Repository.Core.Repositories.Auth
}
public async Task<Entities.UserAccount?> GetUserByUsernameAsync(string username)
public async Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -76,7 +77,7 @@ namespace Repository.Core.Repositories.Auth
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public async Task<Entities.UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId)
public async Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -105,9 +106,9 @@ namespace Repository.Core.Repositories.Auth
/// <summary>
/// Maps a data reader row to a UserAccount entity.
/// </summary>
protected override Entities.UserAccount MapToEntity(DbDataReader reader)
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
{
return new Entities.UserAccount
return new Domain.Core.Entities.UserAccount
{
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
Username = reader.GetString(reader.GetOrdinal("Username")),
@@ -128,9 +129,9 @@ namespace Repository.Core.Repositories.Auth
/// <summary>
/// Maps a data reader row to a UserCredential entity.
/// </summary>
private static Entities.UserCredential MapToCredentialEntity(DbDataReader reader)
private static UserCredential MapToCredentialEntity(DbDataReader reader)
{
var entity = new Entities.UserCredential
var entity = new UserCredential
{
UserCredentialId = reader.GetGuid(reader.GetOrdinal("UserCredentialId")),
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),

View File

@@ -1,3 +1,5 @@
using Domain.Core.Entities;
namespace Repository.Core.Repositories.Auth
{
/// <summary>
@@ -16,7 +18,7 @@ namespace Repository.Core.Repositories.Auth
/// <param name="dateOfBirth">User's date of birth</param>
/// <param name="passwordHash">Hashed password</param>
/// <returns>The newly created UserAccount with generated ID</returns>
Task<Entities.UserAccount> RegisterUserAsync(
Task<Domain.Core.Entities.UserAccount> RegisterUserAsync(
string username,
string firstName,
string lastName,
@@ -30,7 +32,7 @@ namespace Repository.Core.Repositories.Auth
/// </summary>
/// <param name="email">Email address to search for</param>
/// <returns>UserAccount if found, null otherwise</returns>
Task<Entities.UserAccount?> GetUserByEmailAsync(string email);
Task<Domain.Core.Entities.UserAccount?> GetUserByEmailAsync(string email);
/// <summary>
/// Retrieves a user account by username (typically used for login).
@@ -38,7 +40,7 @@ namespace Repository.Core.Repositories.Auth
/// </summary>
/// <param name="username">Username to search for</param>
/// <returns>UserAccount if found, null otherwise</returns>
Task<Entities.UserAccount?> GetUserByUsernameAsync(string username);
Task<Domain.Core.Entities.UserAccount?> GetUserByUsernameAsync(string username);
/// <summary>
/// Retrieves the active (non-revoked) credential for a user account.
@@ -46,7 +48,7 @@ namespace Repository.Core.Repositories.Auth
/// </summary>
/// <param name="userAccountId">ID of the user account</param>
/// <returns>Active UserCredential if found, null otherwise</returns>
Task<Entities.UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId);
Task<UserCredential?> GetActiveCredentialByUserAccountIdAsync(Guid userAccountId);
/// <summary>
/// Rotates a user's credential by invalidating all existing credentials and creating a new one.

View File

@@ -1,14 +1,15 @@
using Domain.Core.Entities;
namespace Repository.Core.Repositories.UserAccount
{
public interface IUserAccountRepository
{
Task<Entities.UserAccount?> GetByIdAsync(Guid id);
Task<IEnumerable<Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
Task UpdateAsync(Entities.UserAccount userAccount);
Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id);
Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset);
Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount);
Task DeleteAsync(Guid id);
Task<Entities.UserAccount?> GetByUsernameAsync(string username);
Task<Entities.UserAccount?> GetByEmailAsync(string email);
Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username);
Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email);
}
}

View File

@@ -1,13 +1,14 @@
using System.Data;
using System.Data.Common;
using Domain.Core.Entities;
using Repository.Core.Sql;
namespace Repository.Core.Repositories.UserAccount
{
public class UserAccountRepository(ISqlConnectionFactory connectionFactory)
: Repository<Entities.UserAccount>(connectionFactory), IUserAccountRepository
: Repository<Domain.Core.Entities.UserAccount>(connectionFactory), IUserAccountRepository
{
public async Task<Entities.UserAccount?> GetByIdAsync(Guid id)
public async Task<Domain.Core.Entities.UserAccount?> GetByIdAsync(Guid id)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -20,7 +21,7 @@ namespace Repository.Core.Repositories.UserAccount
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public async Task<IEnumerable<Entities.UserAccount>> GetAllAsync(int? limit, int? offset)
public async Task<IEnumerable<Domain.Core.Entities.UserAccount>> GetAllAsync(int? limit, int? offset)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -34,7 +35,7 @@ namespace Repository.Core.Repositories.UserAccount
AddParameter(command, "@Offset", offset.Value);
await using var reader = await command.ExecuteReaderAsync();
var users = new List<Entities.UserAccount>();
var users = new List<Domain.Core.Entities.UserAccount>();
while (await reader.ReadAsync())
{
@@ -44,7 +45,7 @@ namespace Repository.Core.Repositories.UserAccount
return users;
}
public async Task UpdateAsync(Entities.UserAccount userAccount)
public async Task UpdateAsync(Domain.Core.Entities.UserAccount userAccount)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -72,7 +73,7 @@ namespace Repository.Core.Repositories.UserAccount
await command.ExecuteNonQueryAsync();
}
public async Task<Entities.UserAccount?> GetByUsernameAsync(string username)
public async Task<Domain.Core.Entities.UserAccount?> GetByUsernameAsync(string username)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -85,7 +86,7 @@ namespace Repository.Core.Repositories.UserAccount
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
public async Task<Entities.UserAccount?> GetByEmailAsync(string email)
public async Task<Domain.Core.Entities.UserAccount?> GetByEmailAsync(string email)
{
await using var connection = await CreateConnection();
await using var command = connection.CreateCommand();
@@ -98,9 +99,9 @@ namespace Repository.Core.Repositories.UserAccount
return await reader.ReadAsync() ? MapToEntity(reader) : null;
}
protected override Entities.UserAccount MapToEntity(DbDataReader reader)
protected override Domain.Core.Entities.UserAccount MapToEntity(DbDataReader reader)
{
return new Entities.UserAccount
return new Domain.Core.Entities.UserAccount
{
UserAccountId = reader.GetGuid(reader.GetOrdinal("UserAccountId")),
Username = reader.GetString(reader.GetOrdinal("Username")),

View File

@@ -14,4 +14,7 @@
<PackageReference Include="System.Data.SqlClient" Version="4.9.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Core\Domain.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
using Repository.Core.Entities;
using Domain.Core.Entities;
using Repository.Core.Repositories.Auth;
using Service.Core.Password;

View File

@@ -1,4 +1,4 @@
using Repository.Core.Entities;
using Domain.Core.Entities;
namespace Service.Core.Auth;
@@ -6,4 +6,4 @@ public interface IAuthService
{
Task<UserAccount> RegisterAsync(UserAccount userAccount, string password);
Task<UserAccount?> LoginAsync(string username, string password);
}
}

View File

@@ -11,6 +11,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Domain\Domain.Core\Domain.Core.csproj" />
<ProjectReference Include="..\..\Repository\Repository.Core\Repository.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
using Repository.Core.Entities;
using Domain.Core.Entities;
namespace Service.Core.User;
@@ -8,4 +8,4 @@ public interface IUserService
Task<UserAccount?> GetByIdAsync(Guid id);
Task UpdateAsync(UserAccount userAccount);
}
}

View File

@@ -1,4 +1,4 @@
using Repository.Core.Entities;
using Domain.Core.Entities;
using Repository.Core.Repositories.UserAccount;
namespace Service.Core.User;
@@ -19,4 +19,4 @@ public class UserService(IUserAccountRepository repository) : IUserService
{
await repository.UpdateAsync(userAccount);
}
}
}