diff --git a/src/backend/src/Randall.Domain/Reservations/Reservation.cs b/src/backend/src/Randall.Domain/Reservations/Reservation.cs index 421a48d..7f4b322 100644 --- a/src/backend/src/Randall.Domain/Reservations/Reservation.cs +++ b/src/backend/src/Randall.Domain/Reservations/Reservation.cs @@ -13,11 +13,21 @@ public class Reservation : Entity public ReservationStatus Status { get; private set; } public DateTime CreatedAt { get; private set; } - // EF Core constructor - private Reservation() : base() + public static Reservation Reconstitute( + Guid id, Guid workplaceId, string employeeEmail, string employeeName, + DateOnly date, ReservationStatus status, DateTime createdAt) => + new(id, workplaceId, employeeEmail, employeeName, date, status, createdAt); + + private Reservation( + Guid id, Guid workplaceId, string employeeEmail, string employeeName, + DateOnly date, ReservationStatus status, DateTime createdAt) : base(id) { - EmployeeEmail = string.Empty; - EmployeeName = string.Empty; + WorkplaceId = workplaceId; + EmployeeEmail = employeeEmail; + EmployeeName = employeeName; + Date = date; + Status = status; + CreatedAt = createdAt; } private Reservation(Guid workplaceId, string employeeEmail, string employeeName, DateOnly date) diff --git a/src/backend/src/Randall.Domain/Users/User.cs b/src/backend/src/Randall.Domain/Users/User.cs index abc10ca..422b796 100644 --- a/src/backend/src/Randall.Domain/Users/User.cs +++ b/src/backend/src/Randall.Domain/Users/User.cs @@ -10,11 +10,16 @@ public class User : Entity public bool IsApproved { get; private set; } public bool IsAdmin { get; private set; } - private User() : base() + public static User Reconstitute(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) => + new(id, email, name, passwordHash, isApproved, isAdmin); + + private User(Guid id, string email, string name, string passwordHash, bool isApproved, bool isAdmin) : base(id) { - Email = string.Empty; - Name = string.Empty; - PasswordHash = string.Empty; + Email = email; + Name = name; + PasswordHash = passwordHash; + IsApproved = isApproved; + IsAdmin = isAdmin; } private User(string email, string name, string passwordHash, bool isAdmin) : base() diff --git a/src/backend/src/Randall.Domain/Workplaces/Workplace.cs b/src/backend/src/Randall.Domain/Workplaces/Workplace.cs index fd47377..eca5e77 100644 --- a/src/backend/src/Randall.Domain/Workplaces/Workplace.cs +++ b/src/backend/src/Randall.Domain/Workplaces/Workplace.cs @@ -8,10 +8,14 @@ public class Workplace : Entity public string Location { get; private set; } public bool IsActive { get; private set; } - private Workplace() : base() + public static Workplace Reconstitute(Guid id, string name, string location, bool isActive) => + new(id, name, location, isActive); + + private Workplace(Guid id, string name, string location, bool isActive) : base(id) { - Name = string.Empty; - Location = string.Empty; + Name = name; + Location = location; + IsActive = isActive; } public Workplace(string name, string location) : base() diff --git a/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.Designer.cs b/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.Designer.cs new file mode 100644 index 0000000..d23a6ae --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.Designer.cs @@ -0,0 +1,120 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Randall.Infrastructure.Persistence; + +#nullable disable + +namespace Randall.Infrastructure.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260327154255_SeparateStorageModels")] + partial class SeparateStorageModels + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); + + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.ReservationRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("EmployeeEmail") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("EmployeeName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("WorkplaceId") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("EmployeeEmail", "Date"); + + b.HasIndex("WorkplaceId", "Date"); + + b.ToTable("Reservations", (string)null); + }); + + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.UserRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Email") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("IsAdmin") + .HasColumnType("INTEGER"); + + b.Property("IsApproved") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Email") + .IsUnique(); + + b.ToTable("Users", (string)null); + }); + + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.WorkplaceRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("Location") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Workplaces", (string)null); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.cs b/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.cs new file mode 100644 index 0000000..ecb04f1 --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Migrations/20260327154255_SeparateStorageModels.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Randall.Infrastructure.Migrations +{ + /// + public partial class SeparateStorageModels : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/src/backend/src/Randall.Infrastructure/Migrations/AppDbContextModelSnapshot.cs b/src/backend/src/Randall.Infrastructure/Migrations/AppDbContextModelSnapshot.cs index 5efe56b..25e1cfe 100644 --- a/src/backend/src/Randall.Infrastructure/Migrations/AppDbContextModelSnapshot.cs +++ b/src/backend/src/Randall.Infrastructure/Migrations/AppDbContextModelSnapshot.cs @@ -17,7 +17,7 @@ namespace Randall.Infrastructure.Migrations #pragma warning disable 612, 618 modelBuilder.HasAnnotation("ProductVersion", "10.0.5"); - modelBuilder.Entity("Randall.Domain.Reservations.Reservation", b => + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.ReservationRecord", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -51,10 +51,10 @@ namespace Randall.Infrastructure.Migrations b.HasIndex("WorkplaceId", "Date"); - b.ToTable("Reservations"); + b.ToTable("Reservations", (string)null); }); - modelBuilder.Entity("Randall.Domain.Users.User", b => + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.UserRecord", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -85,10 +85,10 @@ namespace Randall.Infrastructure.Migrations b.HasIndex("Email") .IsUnique(); - b.ToTable("Users"); + b.ToTable("Users", (string)null); }); - modelBuilder.Entity("Randall.Domain.Workplaces.Workplace", b => + modelBuilder.Entity("Randall.Infrastructure.Persistence.Records.WorkplaceRecord", b => { b.Property("Id") .ValueGeneratedOnAdd() @@ -109,7 +109,7 @@ namespace Randall.Infrastructure.Migrations b.HasKey("Id"); - b.ToTable("Workplaces"); + b.ToTable("Workplaces", (string)null); }); #pragma warning restore 612, 618 } diff --git a/src/backend/src/Randall.Infrastructure/Persistence/AppDbContext.cs b/src/backend/src/Randall.Infrastructure/Persistence/AppDbContext.cs index 3d5c9f0..7104c34 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/AppDbContext.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/AppDbContext.cs @@ -1,28 +1,28 @@ using Microsoft.EntityFrameworkCore; -using Randall.Domain.Reservations; -using Randall.Domain.Users; -using Randall.Domain.Workplaces; +using Randall.Infrastructure.Persistence.Records; namespace Randall.Infrastructure.Persistence; public class AppDbContext(DbContextOptions options) : DbContext(options) { - public DbSet Workplaces => Set(); - public DbSet Reservations => Set(); - public DbSet Users => Set(); + public DbSet Workplaces => Set(); + public DbSet Reservations => Set(); + public DbSet Users => Set(); protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { + entity.ToTable("Workplaces"); entity.HasKey(w => w.Id); entity.Property(w => w.Name).IsRequired().HasMaxLength(100); entity.Property(w => w.Location).IsRequired().HasMaxLength(200); entity.Property(w => w.IsActive).IsRequired(); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { + entity.ToTable("Reservations"); entity.HasKey(r => r.Id); entity.Property(r => r.WorkplaceId).IsRequired(); entity.Property(r => r.EmployeeEmail).IsRequired().HasMaxLength(200); @@ -35,8 +35,9 @@ public class AppDbContext(DbContextOptions options) : DbContext(op entity.HasIndex(r => new { r.EmployeeEmail, r.Date }); }); - modelBuilder.Entity(entity => + modelBuilder.Entity(entity => { + entity.ToTable("Users"); entity.HasKey(u => u.Id); entity.Property(u => u.Email).IsRequired().HasMaxLength(200); entity.Property(u => u.Name).IsRequired().HasMaxLength(200); diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Mappers/ReservationMapper.cs b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/ReservationMapper.cs new file mode 100644 index 0000000..621ce91 --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/ReservationMapper.cs @@ -0,0 +1,34 @@ +using Randall.Domain.Reservations; +using Randall.Infrastructure.Persistence.Records; + +namespace Randall.Infrastructure.Persistence.Mappers; + +public static class ReservationMapper +{ + public static Reservation ToDomain(ReservationRecord record) => + Reservation.Reconstitute( + record.Id, + record.WorkplaceId, + record.EmployeeEmail, + record.EmployeeName, + record.Date, + record.Status, + record.CreatedAt); + + public static ReservationRecord ToRecord(Reservation domain) => + new() + { + Id = domain.Id, + WorkplaceId = domain.WorkplaceId, + EmployeeEmail = domain.EmployeeEmail, + EmployeeName = domain.EmployeeName, + Date = domain.Date, + Status = domain.Status, + CreatedAt = domain.CreatedAt, + }; + + public static void SyncToRecord(Reservation domain, ReservationRecord record) + { + record.Status = domain.Status; + } +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Mappers/UserMapper.cs b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/UserMapper.cs new file mode 100644 index 0000000..979f71a --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/UserMapper.cs @@ -0,0 +1,33 @@ +using Randall.Domain.Users; +using Randall.Infrastructure.Persistence.Records; + +namespace Randall.Infrastructure.Persistence.Mappers; + +public static class UserMapper +{ + public static User ToDomain(UserRecord record) => + User.Reconstitute( + record.Id, + record.Email, + record.Name, + record.PasswordHash, + record.IsApproved, + record.IsAdmin); + + public static UserRecord ToRecord(User domain) => + new() + { + Id = domain.Id, + Email = domain.Email, + Name = domain.Name, + PasswordHash = domain.PasswordHash, + IsApproved = domain.IsApproved, + IsAdmin = domain.IsAdmin, + }; + + public static void SyncToRecord(User domain, UserRecord record) + { + record.IsApproved = domain.IsApproved; + record.IsAdmin = domain.IsAdmin; + } +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Mappers/WorkplaceMapper.cs b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/WorkplaceMapper.cs new file mode 100644 index 0000000..e7af9c6 --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Mappers/WorkplaceMapper.cs @@ -0,0 +1,23 @@ +using Randall.Domain.Workplaces; +using Randall.Infrastructure.Persistence.Records; + +namespace Randall.Infrastructure.Persistence.Mappers; + +public static class WorkplaceMapper +{ + public static Workplace ToDomain(WorkplaceRecord record) => + Workplace.Reconstitute( + record.Id, + record.Name, + record.Location, + record.IsActive); + + public static WorkplaceRecord ToRecord(Workplace domain) => + new() + { + Id = domain.Id, + Name = domain.Name, + Location = domain.Location, + IsActive = domain.IsActive, + }; +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Records/ReservationRecord.cs b/src/backend/src/Randall.Infrastructure/Persistence/Records/ReservationRecord.cs new file mode 100644 index 0000000..6f51210 --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Records/ReservationRecord.cs @@ -0,0 +1,14 @@ +using Randall.Domain.Reservations; + +namespace Randall.Infrastructure.Persistence.Records; + +public class ReservationRecord +{ + public Guid Id { get; set; } + public Guid WorkplaceId { get; set; } + public string EmployeeEmail { get; set; } = string.Empty; + public string EmployeeName { get; set; } = string.Empty; + public DateOnly Date { get; set; } + public ReservationStatus Status { get; set; } + public DateTime CreatedAt { get; set; } +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Records/UserRecord.cs b/src/backend/src/Randall.Infrastructure/Persistence/Records/UserRecord.cs new file mode 100644 index 0000000..8b885fb --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Records/UserRecord.cs @@ -0,0 +1,11 @@ +namespace Randall.Infrastructure.Persistence.Records; + +public class UserRecord +{ + public Guid Id { get; set; } + public string Email { get; set; } = string.Empty; + public string Name { get; set; } = string.Empty; + public string PasswordHash { get; set; } = string.Empty; + public bool IsApproved { get; set; } + public bool IsAdmin { get; set; } +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Records/WorkplaceRecord.cs b/src/backend/src/Randall.Infrastructure/Persistence/Records/WorkplaceRecord.cs new file mode 100644 index 0000000..c9de0a2 --- /dev/null +++ b/src/backend/src/Randall.Infrastructure/Persistence/Records/WorkplaceRecord.cs @@ -0,0 +1,9 @@ +namespace Randall.Infrastructure.Persistence.Records; + +public class WorkplaceRecord +{ + public Guid Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Location { get; set; } = string.Empty; + public bool IsActive { get; set; } +} diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Reservations/ReservationRepository.cs b/src/backend/src/Randall.Infrastructure/Persistence/Reservations/ReservationRepository.cs index 4369d2d..58e1269 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/Reservations/ReservationRepository.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/Reservations/ReservationRepository.cs @@ -1,23 +1,40 @@ using Microsoft.EntityFrameworkCore; using Randall.Domain.Reservations; +using Randall.Infrastructure.Persistence.Mappers; +using Randall.Infrastructure.Persistence.Records; namespace Randall.Infrastructure.Persistence.Reservations; public class ReservationRepository(AppDbContext context) : IReservationRepository { - public Task GetByIdAsync(Guid id, CancellationToken ct = default) => - context.Reservations.FirstOrDefaultAsync(r => r.Id == id, ct); + private readonly Dictionary _tracked = []; - public async Task> GetByEmployeeAsync(string employeeEmail, CancellationToken ct = default) => - await context.Reservations - .Where(r => r.EmployeeEmail.ToLower() == employeeEmail.ToLower()) + public async Task GetByIdAsync(Guid id, CancellationToken ct = default) + { + var record = await context.Reservations.FirstOrDefaultAsync(r => r.Id == id, ct); + if (record is null) return null; + var domain = ReservationMapper.ToDomain(record); + _tracked[domain.Id] = (domain, record); + return domain; + } + + public async Task> GetByEmployeeAsync(string employeeEmail, CancellationToken ct = default) + { + var today = DateOnly.FromDateTime(DateTime.UtcNow); + var records = await context.Reservations + .Where(r => r.EmployeeEmail.ToLower() == employeeEmail.ToLower() && r.Date >= today) .ToListAsync(ct); + return records.Select(ReservationMapper.ToDomain).ToList(); + } public async Task> GetByWorkplaceAndDateAsync( - Guid workplaceId, DateOnly date, CancellationToken ct = default) => - await context.Reservations + Guid workplaceId, DateOnly date, CancellationToken ct = default) + { + var records = await context.Reservations .Where(r => r.WorkplaceId == workplaceId && r.Date == date) .ToListAsync(ct); + return records.Select(ReservationMapper.ToDomain).ToList(); + } public Task ExistsActiveForEmployeeOnDateAsync( string employeeEmail, DateOnly date, CancellationToken ct = default) => @@ -36,14 +53,24 @@ public class ReservationRepository(AppDbContext context) : IReservationRepositor ct); public async Task> GetActiveReservationsForDateAsync( - DateOnly date, CancellationToken ct = default) => - await context.Reservations + DateOnly date, CancellationToken ct = default) + { + var records = await context.Reservations .Where(r => r.Date == date && r.Status == ReservationStatus.Active) .ToListAsync(ct); + return records.Select(ReservationMapper.ToDomain).ToList(); + } - public async Task AddAsync(Reservation reservation, CancellationToken ct = default) => - await context.Reservations.AddAsync(reservation, ct); + public async Task AddAsync(Reservation reservation, CancellationToken ct = default) + { + var record = ReservationMapper.ToRecord(reservation); + await context.Reservations.AddAsync(record, ct); + } - public Task SaveChangesAsync(CancellationToken ct = default) => - context.SaveChangesAsync(ct); + public Task SaveChangesAsync(CancellationToken ct = default) + { + foreach (var (domain, record) in _tracked.Values) + ReservationMapper.SyncToRecord(domain, record); + return context.SaveChangesAsync(ct); + } } diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Seeding/DatabaseSeeder.cs b/src/backend/src/Randall.Infrastructure/Persistence/Seeding/DatabaseSeeder.cs index 82fac1f..967a8a7 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/Seeding/DatabaseSeeder.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/Seeding/DatabaseSeeder.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Randall.Application.Common; using Randall.Domain.Users; using Randall.Domain.Workplaces; +using Randall.Infrastructure.Persistence.Mappers; namespace Randall.Infrastructure.Persistence.Seeding; @@ -25,7 +26,7 @@ public static class DatabaseSeeder // Seed workplaces only if none exist yet if (!await context.Workplaces.AnyAsync()) { - var workplaces = ExpectedWorkplaces.Select(w => new Workplace(w.Name, w.Location)); + var workplaces = ExpectedWorkplaces.Select(w => WorkplaceMapper.ToRecord(new Workplace(w.Name, w.Location))); context.Workplaces.AddRange(workplaces); await context.SaveChangesAsync(); } @@ -35,7 +36,7 @@ public static class DatabaseSeeder { var hash = passwordHasher.Hash(AdminPassword); var admin = User.Create(AdminEmail, "Admin", hash, isAdmin: true).Value!; - context.Users.Add(admin); + context.Users.Add(UserMapper.ToRecord(admin)); await context.SaveChangesAsync(); } } diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs b/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs index 62c89ae..311dbc3 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs @@ -1,37 +1,73 @@ using Microsoft.EntityFrameworkCore; using Randall.Domain.Users; +using Randall.Infrastructure.Persistence.Mappers; +using Randall.Infrastructure.Persistence.Records; namespace Randall.Infrastructure.Persistence.Users; public class UserRepository(AppDbContext context) : IUserRepository { - public Task GetByEmailAsync(string email, CancellationToken ct = default) => - context.Users.FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant(), ct); + private readonly Dictionary _tracked = []; - public Task GetByIdAsync(Guid id, CancellationToken ct = default) => - context.Users.FirstOrDefaultAsync(u => u.Id == id, ct); + private User Track(UserRecord record) + { + var domain = UserMapper.ToDomain(record); + _tracked[domain.Id] = (domain, record); + return domain; + } + + public async Task GetByEmailAsync(string email, CancellationToken ct = default) + { + var record = await context.Users.FirstOrDefaultAsync(u => u.Email == email.ToLowerInvariant(), ct); + return record is null ? null : Track(record); + } + + public async Task GetByIdAsync(Guid id, CancellationToken ct = default) + { + var record = await context.Users.FirstOrDefaultAsync(u => u.Id == id, ct); + return record is null ? null : Track(record); + } public Task ExistsByEmailAsync(string email, CancellationToken ct = default) => context.Users.AnyAsync(u => u.Email == email.ToLowerInvariant(), ct); - public Task> GetPendingAsync(CancellationToken ct = default) => - context.Users.Where(u => !u.IsApproved && !u.IsAdmin).ToListAsync(ct); + public async Task> GetPendingAsync(CancellationToken ct = default) + { + var records = await context.Users.Where(u => !u.IsApproved && !u.IsAdmin).ToListAsync(ct); + return records.Select(UserMapper.ToDomain).ToList(); + } - public Task> GetAllAsync(CancellationToken ct = default) => - context.Users.OrderBy(u => u.Name).ToListAsync(ct); + public async Task> GetAllAsync(CancellationToken ct = default) + { + var records = await context.Users.OrderBy(u => u.Name).ToListAsync(ct); + return records.Select(UserMapper.ToDomain).ToList(); + } public Task CountAdminsAsync(CancellationToken ct = default) => context.Users.CountAsync(u => u.IsAdmin); - public Task> GetAllNonAdminAsync(CancellationToken ct = default) => - context.Users.Where(u => !u.IsAdmin).OrderBy(u => u.Name).ToListAsync(ct); + public async Task> GetAllNonAdminAsync(CancellationToken ct = default) + { + var records = await context.Users.Where(u => !u.IsAdmin).OrderBy(u => u.Name).ToListAsync(ct); + return records.Select(UserMapper.ToDomain).ToList(); + } - public async Task AddAsync(User user, CancellationToken ct = default) => - await context.Users.AddAsync(user, ct); + public async Task AddAsync(User user, CancellationToken ct = default) + { + var record = UserMapper.ToRecord(user); + await context.Users.AddAsync(record, ct); + } - public void Delete(User user) => - context.Users.Remove(user); + public void Delete(User user) + { + if (_tracked.TryGetValue(user.Id, out var entry)) + context.Users.Remove(entry.Record); + } - public Task SaveChangesAsync(CancellationToken ct = default) => - context.SaveChangesAsync(ct); + public Task SaveChangesAsync(CancellationToken ct = default) + { + foreach (var (domain, record) in _tracked.Values) + UserMapper.SyncToRecord(domain, record); + return context.SaveChangesAsync(ct); + } } diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Workplaces/WorkplaceRepository.cs b/src/backend/src/Randall.Infrastructure/Persistence/Workplaces/WorkplaceRepository.cs index 9a69c07..6ef7da3 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/Workplaces/WorkplaceRepository.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/Workplaces/WorkplaceRepository.cs @@ -1,13 +1,20 @@ using Microsoft.EntityFrameworkCore; using Randall.Domain.Workplaces; +using Randall.Infrastructure.Persistence.Mappers; namespace Randall.Infrastructure.Persistence.Workplaces; public class WorkplaceRepository(AppDbContext context) : IWorkplaceRepository { - public Task GetByIdAsync(Guid id, CancellationToken ct = default) => - context.Workplaces.FirstOrDefaultAsync(w => w.Id == id, ct); + public async Task GetByIdAsync(Guid id, CancellationToken ct = default) + { + var record = await context.Workplaces.FirstOrDefaultAsync(w => w.Id == id, ct); + return record is null ? null : WorkplaceMapper.ToDomain(record); + } - public async Task> GetAllActiveAsync(CancellationToken ct = default) => - await context.Workplaces.Where(w => w.IsActive).ToListAsync(ct); + public async Task> GetAllActiveAsync(CancellationToken ct = default) + { + var records = await context.Workplaces.Where(w => w.IsActive).ToListAsync(ct); + return records.Select(WorkplaceMapper.ToDomain).ToList(); + } }