From 950642815864eb913ffb6cab702c73e0a11d9094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Bj=C3=B8ralt?= <61310258+jonasbjoralt@users.noreply.github.com> Date: Wed, 9 Oct 2024 13:15:20 +0200 Subject: [PATCH] add consultant-repository and implement getters for the simple use-cases (#529) --- backend/Api/Common/StorageService.cs | 41 ++--------------- .../Api/Consultants/ConsultantController.cs | 24 ++++------ backend/Api/Consultants/ConsultantModels.cs | 2 +- .../StaffingController/ReadModelFactory.cs | 1 - .../VacationsController.cs | 34 +++++++------- .../VacationsController/VacationsValidator.cs | 7 ++- backend/Core/Consultants/Consultant.cs | 2 +- .../Core/Consultants/IConsultantRepository.cs | 16 +++++++ .../Core/PlannedAbsences/PlannedAbsence.cs | 2 +- backend/Core/Staffings/Staffing.cs | 2 +- backend/Core/Vacations/Vacation.cs | 2 +- .../DatabaseContext/ApplicationContext.cs | 2 +- .../Consultants/ConsultantDbRepository.cs | 46 +++++++++++++++++++ .../Repositories/RepositoryExtensions.cs | 4 ++ 14 files changed, 106 insertions(+), 79 deletions(-) create mode 100644 backend/Core/Consultants/IConsultantRepository.cs create mode 100644 backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs diff --git a/backend/Api/Common/StorageService.cs b/backend/Api/Common/StorageService.cs index a716c5dd..0ea036eb 100644 --- a/backend/Api/Common/StorageService.cs +++ b/backend/Api/Common/StorageService.cs @@ -25,39 +25,6 @@ public StorageService(IMemoryCache cache, ApplicationContext context) _dbContext = context; } - public Consultant? GetConsultantByEmail(string orgUrlKey, string email) - { - var consultant = _dbContext.Consultant.Include(c => c.Department).ThenInclude(d => d.Organization) - .SingleOrDefault(c => c.Email == email); - if (consultant is null || consultant.Department.Organization.UrlKey != orgUrlKey) return null; - - return consultant; - } - - public List GetConsultants(string orgUrlKey) - { - var consultants = _dbContext.Consultant - .Include(c => c.Department) - .ThenInclude(d => d.Organization) - .Include(c => c.CompetenceConsultant) - .ThenInclude(cc => cc.Competence) - .Where(c => c.Department.Organization.UrlKey == orgUrlKey) - .ToList(); - - return consultants; - } - - public List GetConsultantsEmploymentVariant(string orgUrlKey) - { - var consultants = _dbContext.Consultant - .Include(c => c.Department) - .ThenInclude(d => d.Organization) - .Where(c => c.Department.Organization.UrlKey == orgUrlKey) - .ToList(); - - return consultants; - } - public void ClearConsultantCache(string orgUrlKey) { _cache.Remove($"{ConsultantCacheKey}/{orgUrlKey}"); @@ -158,11 +125,11 @@ private List LoadConsultantsFromDb(string orgUrlKey) : new List(); consultant.PlannedAbsences = - plannedAbsencePrConsultant.TryGetValue(consultant.Id, out List? plannedAbsences) + plannedAbsencePrConsultant.TryGetValue(consultant.Id, out var plannedAbsences) ? plannedAbsences : new List(); - consultant.Vacations = vacationsPrConsultant.TryGetValue(consultant.Id, out List? vacations) + consultant.Vacations = vacationsPrConsultant.TryGetValue(consultant.Id, out var vacations) ? vacations : new List(); @@ -331,7 +298,7 @@ public void UpdateOrCreatePlannedAbsences(int consultantId, int absenceId, List< ClearConsultantCache(orgUrlKey); } - public Consultant CreateConsultant(Organization org, ConsultantWriteModel body) + public Consultant? CreateConsultant(Organization org, ConsultantWriteModel body) { var consultant = new Consultant { @@ -364,7 +331,7 @@ public Consultant CreateConsultant(Organization org, ConsultantWriteModel body) return consultant; } - public Consultant UpdateConsultant(Organization org, ConsultantWriteModel body) + public Consultant? UpdateConsultant(Organization org, ConsultantWriteModel body) { var consultant = _dbContext.Consultant .Include(c => c.CompetenceConsultant) diff --git a/backend/Api/Consultants/ConsultantController.cs b/backend/Api/Consultants/ConsultantController.cs index 214a9338..23c7228f 100644 --- a/backend/Api/Consultants/ConsultantController.cs +++ b/backend/Api/Consultants/ConsultantController.cs @@ -1,4 +1,5 @@ using Api.Common; +using Core.Consultants; using Core.Organizations; using Infrastructure.DatabaseContext; using Microsoft.AspNetCore.Authorization; @@ -13,17 +14,15 @@ namespace Api.Consultants; public class ConsultantController( ApplicationContext context, IMemoryCache cache, - IOrganisationRepository organisationRepository) : ControllerBase + IOrganisationRepository organisationRepository, + IConsultantRepository consultantRepository) : ControllerBase { [HttpGet] [Route("{Email}")] - public ActionResult Get([FromRoute] string orgUrlKey, + public async Task> Get([FromRoute] string orgUrlKey, CancellationToken ct, [FromRoute(Name = "Email")] string? email = "") { - var service = new StorageService(cache, context); - - var consultant = service.GetConsultantByEmail(orgUrlKey, email ?? ""); - + var consultant = await consultantRepository.GetConsultantByEmail(orgUrlKey, email ?? "", ct); if (consultant is null) return NotFound(); @@ -31,11 +30,9 @@ public ActionResult Get([FromRoute] string orgUrlKey, } [HttpGet] - public ActionResult> GetAll([FromRoute] string orgUrlKey) + public async Task GetAll([FromRoute] string orgUrlKey, CancellationToken ct) { - var service = new StorageService(cache, context); - - var consultants = service.GetConsultants(orgUrlKey); + var consultants = await consultantRepository.GetConsultantsInOrganizationByUrlKey(orgUrlKey, ct); var readModels = consultants .Select(c => new SingleConsultantReadModel(c)) @@ -46,11 +43,9 @@ public ActionResult> GetAll([FromRoute] string o [HttpGet] [Route("employment")] - public ActionResult> GetConsultantsEmployment([FromRoute] string orgUrlKey) + public async Task GetConsultantsEmployment([FromRoute] string orgUrlKey, CancellationToken ct) { - var service = new StorageService(cache, context); - - var consultants = service.GetConsultantsEmploymentVariant(orgUrlKey); + var consultants = await consultantRepository.GetConsultantsInOrganizationByUrlKey(orgUrlKey, ct); var readModels = consultants .Select(c => new ConsultantsEmploymentReadModel(c)) @@ -72,7 +67,6 @@ public async Task> Put([FromRoute] strin var consultant = service.UpdateConsultant(selectedOrg, body); - var responseModel = new SingleConsultantReadModel(consultant); diff --git a/backend/Api/Consultants/ConsultantModels.cs b/backend/Api/Consultants/ConsultantModels.cs index 42eea68b..98222137 100644 --- a/backend/Api/Consultants/ConsultantModels.cs +++ b/backend/Api/Consultants/ConsultantModels.cs @@ -16,7 +16,7 @@ public record SingleConsultantReadModel( [property: Required] Degree Degree) { - public SingleConsultantReadModel(Consultant consultant) + public SingleConsultantReadModel(Consultant? consultant) : this( consultant.Id, consultant.Name, diff --git a/backend/Api/StaffingController/ReadModelFactory.cs b/backend/Api/StaffingController/ReadModelFactory.cs index 680e80fa..6d8de1df 100644 --- a/backend/Api/StaffingController/ReadModelFactory.cs +++ b/backend/Api/StaffingController/ReadModelFactory.cs @@ -1,5 +1,4 @@ using Api.Common; -using Api.Organisation; using Core.Consultants; using Core.DomainModels; using Core.Engagements; diff --git a/backend/Api/VacationsController/VacationsController.cs b/backend/Api/VacationsController/VacationsController.cs index 1fb4aef0..e2be30c0 100644 --- a/backend/Api/VacationsController/VacationsController.cs +++ b/backend/Api/VacationsController/VacationsController.cs @@ -16,7 +16,8 @@ namespace Api.VacationsController; public class VacationsController( ApplicationContext context, IMemoryCache cache, - IOrganisationRepository organisationRepository) : ControllerBase + IOrganisationRepository organisationRepository, + IConsultantRepository consultantRepository) : ControllerBase { [HttpGet] [Route("publicHolidays")] @@ -46,12 +47,12 @@ public async Task> GetVacations([FromRoute] stri var service = new StorageService(cache, context); - if (!VacationsValidator.ValidateVacation(consultantId, service, orgUrlKey)) - return BadRequest(); - var vacationDays = service.LoadConsultantVacation(consultantId); - var consultant = service.GetBaseConsultantById(consultantId); - if (consultant is null) return BadRequest(); + var consultant = await consultantRepository.GetConsultantById(consultantId, ct); + + if (consultant is null || !VacationsValidator.ValidateVacation(consultant, orgUrlKey)) + return NotFound(); + var vacationMetaData = new VacationMetaData(consultant, DateOnly.FromDateTime(DateTime.Now)); return new VacationReadModel(consultantId, vacationDays, vacationMetaData); } @@ -68,8 +69,11 @@ public async Task> DeleteVacation([FromRoute] st var service = new StorageService(cache, context); - if (!VacationsValidator.ValidateVacation(consultantId, service, orgUrlKey)) - return BadRequest(); + var vacationDays = service.LoadConsultantVacation(consultantId); + var consultant = await consultantRepository.GetConsultantById(consultantId, ct); + + if (consultant is null || !VacationsValidator.ValidateVacation(consultant, orgUrlKey)) + return NotFound(); try { @@ -82,9 +86,6 @@ public async Task> DeleteVacation([FromRoute] st return BadRequest("Something went wrong"); } - var vacationDays = service.LoadConsultantVacation(consultantId); - var consultant = service.GetBaseConsultantById(consultantId); - if (consultant is null) return BadRequest(); var vacationMetaData = new VacationMetaData(consultant, DateOnly.FromDateTime(DateTime.Now)); return new VacationReadModel(consultantId, vacationDays, vacationMetaData); } @@ -101,8 +102,11 @@ public async Task> UpdateVacation([FromRoute] st var service = new StorageService(cache, context); - if (!VacationsValidator.ValidateVacation(consultantId, service, orgUrlKey)) - return BadRequest(); + var vacationDays = service.LoadConsultantVacation(consultantId); + var consultant = await consultantRepository.GetConsultantById(consultantId, ct); + + if (consultant is null || !VacationsValidator.ValidateVacation(consultant, orgUrlKey)) + return NotFound(); try { @@ -115,9 +119,7 @@ public async Task> UpdateVacation([FromRoute] st return BadRequest("Something went wrong"); } - var vacationDays = service.LoadConsultantVacation(consultantId); - var consultant = service.GetBaseConsultantById(consultantId); - if (consultant is null) return BadRequest(); + var vacationMetaData = new VacationMetaData(consultant, DateOnly.FromDateTime(DateTime.Now)); return new VacationReadModel(consultantId, vacationDays, vacationMetaData); } diff --git a/backend/Api/VacationsController/VacationsValidator.cs b/backend/Api/VacationsController/VacationsValidator.cs index 5485f35c..eb94e452 100644 --- a/backend/Api/VacationsController/VacationsValidator.cs +++ b/backend/Api/VacationsController/VacationsValidator.cs @@ -1,13 +1,12 @@ -using Api.Common; +using Core.Consultants; namespace Api.VacationsController; public static class VacationsValidator { - public static bool ValidateVacation(int consultantId, StorageService storageService, + public static bool ValidateVacation(Consultant consultant, string orgUrlKey) { - return storageService.GetBaseConsultantById(consultantId)?.Department.Organization.UrlKey == - orgUrlKey; + return consultant.Department.Organization.UrlKey == orgUrlKey; } } \ No newline at end of file diff --git a/backend/Core/Consultants/Consultant.cs b/backend/Core/Consultants/Consultant.cs index a97e1842..20fb167b 100644 --- a/backend/Core/Consultants/Consultant.cs +++ b/backend/Core/Consultants/Consultant.cs @@ -80,7 +80,7 @@ public class Competence public class CompetenceConsultant { public int ConsultantId { get; set; } - public Consultant Consultant { get; set; } = null!; + public Consultant? Consultant { get; set; } = null!; public required string CompetencesId { get; set; } public Competence Competence { get; set; } = null!; } diff --git a/backend/Core/Consultants/IConsultantRepository.cs b/backend/Core/Consultants/IConsultantRepository.cs new file mode 100644 index 00000000..b826dd0f --- /dev/null +++ b/backend/Core/Consultants/IConsultantRepository.cs @@ -0,0 +1,16 @@ +namespace Core.Consultants; + +public interface IConsultantRepository +{ + /** + * Get consultant, including common Department, Organization and Competense-data + */ + Task GetConsultantById(int id, CancellationToken ct); + + /** + * Get consultant, including common Department, Organization and Competense-data + */ + Task GetConsultantByEmail(string orgUrlKey, string email, CancellationToken ct); + + Task> GetConsultantsInOrganizationByUrlKey(string urlKey, CancellationToken ct); +} \ No newline at end of file diff --git a/backend/Core/PlannedAbsences/PlannedAbsence.cs b/backend/Core/PlannedAbsences/PlannedAbsence.cs index a4572d04..1667f825 100644 --- a/backend/Core/PlannedAbsences/PlannedAbsence.cs +++ b/backend/Core/PlannedAbsences/PlannedAbsence.cs @@ -10,7 +10,7 @@ public class PlannedAbsence public required Absence Absence { get; set; } = null!; public required int ConsultantId { get; set; } - public required Consultant Consultant { get; set; } = null!; + public required Consultant? Consultant { get; set; } = null!; public required Week Week { get; set; } public double Hours { get; set; } = 0; diff --git a/backend/Core/Staffings/Staffing.cs b/backend/Core/Staffings/Staffing.cs index d429c913..78e8baf1 100644 --- a/backend/Core/Staffings/Staffing.cs +++ b/backend/Core/Staffings/Staffing.cs @@ -12,7 +12,7 @@ public class Staffing public required int ConsultantId { get; set; } - public required Consultant Consultant { get; set; } = null!; + public required Consultant? Consultant { get; set; } = null!; public required Week Week { get; set; } diff --git a/backend/Core/Vacations/Vacation.cs b/backend/Core/Vacations/Vacation.cs index 4046060d..2265e613 100644 --- a/backend/Core/Vacations/Vacation.cs +++ b/backend/Core/Vacations/Vacation.cs @@ -8,6 +8,6 @@ public class Vacation [Required] public int Id { get; set; } public required int ConsultantId { get; set; } - public required Consultant Consultant { get; set; } + public required Consultant? Consultant { get; set; } [Required] public DateOnly Date { get; set; } } \ No newline at end of file diff --git a/backend/Infrastructure/DatabaseContext/ApplicationContext.cs b/backend/Infrastructure/DatabaseContext/ApplicationContext.cs index 91149c04..ca2a7913 100644 --- a/backend/Infrastructure/DatabaseContext/ApplicationContext.cs +++ b/backend/Infrastructure/DatabaseContext/ApplicationContext.cs @@ -18,7 +18,7 @@ public ApplicationContext(DbContextOptions options) : base(options) { } - public DbSet Consultant { get; set; } = null!; + public DbSet Consultant { get; set; } = null!; public DbSet Competence { get; set; } = null!; public DbSet CompetenceConsultant { get; set; } = null!; public DbSet Department { get; set; } = null!; diff --git a/backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs b/backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs new file mode 100644 index 00000000..4a2fb0ab --- /dev/null +++ b/backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs @@ -0,0 +1,46 @@ +using Core.Consultants; +using Infrastructure.DatabaseContext; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Query; + +namespace Infrastructure.Repositories.Consultants; + +public class ConsultantDbRepository(ApplicationContext context) : IConsultantRepository +{ + public Task GetConsultantById(int id, CancellationToken ct) + { + return BaseConsultantQuery() + .SingleOrDefaultAsync(c => c.Id == id, ct); + } + + public async Task GetConsultantByEmail(string orgUrlKey, string email, CancellationToken ct) + { + var consultant = await BaseConsultantQuery() + .SingleOrDefaultAsync(c => c.Email == email, ct); + + if (consultant is null || consultant.Department.Organization.UrlKey != orgUrlKey) return null; + + return consultant; + } + + public Task> GetConsultantsInOrganizationByUrlKey(string urlKey, + CancellationToken ct) + { + return BaseConsultantQuery() + .Where(c => c.Department.Organization.UrlKey == urlKey) + .ToListAsync(ct); + } + + + /* + * Ensures consistent Includes to keep expected base data present + */ + private IIncludableQueryable BaseConsultantQuery() + { + return context.Consultant + .Include(c => c.Department) + .ThenInclude(d => d.Organization) + .Include(c => c.CompetenceConsultant) + .ThenInclude(cc => cc.Competence); + } +} \ No newline at end of file diff --git a/backend/Infrastructure/Repositories/RepositoryExtensions.cs b/backend/Infrastructure/Repositories/RepositoryExtensions.cs index 77c79299..77a8e306 100644 --- a/backend/Infrastructure/Repositories/RepositoryExtensions.cs +++ b/backend/Infrastructure/Repositories/RepositoryExtensions.cs @@ -1,5 +1,7 @@ +using Core.Consultants; using Core.Engagements; using Core.Organizations; +using Infrastructure.Repositories.Consultants; using Infrastructure.Repositories.Engagement; using Infrastructure.Repositories.Organization; using Microsoft.AspNetCore.Builder; @@ -16,5 +18,7 @@ public static void AddRepositories(this WebApplicationBuilder builder) builder.Services.AddScoped(); builder.Services.AddScoped(); + + builder.Services.AddScoped(); } } \ No newline at end of file