Skip to content

Commit

Permalink
add consultant-repository and implement getters for the simple use-ca…
Browse files Browse the repository at this point in the history
…ses (#529)
  • Loading branch information
jonasbjoralt authored Oct 9, 2024
1 parent 5d4ba00 commit 9506428
Show file tree
Hide file tree
Showing 14 changed files with 106 additions and 79 deletions.
41 changes: 4 additions & 37 deletions backend/Api/Common/StorageService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Consultant> 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<Consultant> 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}");
Expand Down Expand Up @@ -158,11 +125,11 @@ private List<Consultant> LoadConsultantsFromDb(string orgUrlKey)
: new List<Staffing>();

consultant.PlannedAbsences =
plannedAbsencePrConsultant.TryGetValue(consultant.Id, out List<PlannedAbsence>? plannedAbsences)
plannedAbsencePrConsultant.TryGetValue(consultant.Id, out var plannedAbsences)
? plannedAbsences
: new List<PlannedAbsence>();

consultant.Vacations = vacationsPrConsultant.TryGetValue(consultant.Id, out List<Vacation>? vacations)
consultant.Vacations = vacationsPrConsultant.TryGetValue(consultant.Id, out var vacations)
? vacations
: new List<Vacation>();

Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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)
Expand Down
24 changes: 9 additions & 15 deletions backend/Api/Consultants/ConsultantController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Api.Common;
using Core.Consultants;
using Core.Organizations;
using Infrastructure.DatabaseContext;
using Microsoft.AspNetCore.Authorization;
Expand All @@ -13,29 +14,25 @@ namespace Api.Consultants;
public class ConsultantController(
ApplicationContext context,
IMemoryCache cache,
IOrganisationRepository organisationRepository) : ControllerBase
IOrganisationRepository organisationRepository,
IConsultantRepository consultantRepository) : ControllerBase
{
[HttpGet]
[Route("{Email}")]
public ActionResult<SingleConsultantReadModel> Get([FromRoute] string orgUrlKey,
public async Task<ActionResult<SingleConsultantReadModel>> 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();

return Ok(new SingleConsultantReadModel(consultant));
}

[HttpGet]
public ActionResult<List<SingleConsultantReadModel>> GetAll([FromRoute] string orgUrlKey)
public async Task<OkObjectResult> 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))
Expand All @@ -46,11 +43,9 @@ public ActionResult<List<SingleConsultantReadModel>> GetAll([FromRoute] string o

[HttpGet]
[Route("employment")]
public ActionResult<List<ConsultantsEmploymentReadModel>> GetConsultantsEmployment([FromRoute] string orgUrlKey)
public async Task<OkObjectResult> 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))
Expand All @@ -72,7 +67,6 @@ public async Task<ActionResult<SingleConsultantReadModel>> Put([FromRoute] strin

var consultant = service.UpdateConsultant(selectedOrg, body);


var responseModel =
new SingleConsultantReadModel(consultant);

Expand Down
2 changes: 1 addition & 1 deletion backend/Api/Consultants/ConsultantModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public record SingleConsultantReadModel(
[property: Required] Degree Degree)

{
public SingleConsultantReadModel(Consultant consultant)
public SingleConsultantReadModel(Consultant? consultant)
: this(
consultant.Id,
consultant.Name,
Expand Down
1 change: 0 additions & 1 deletion backend/Api/StaffingController/ReadModelFactory.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using Api.Common;
using Api.Organisation;
using Core.Consultants;
using Core.DomainModels;
using Core.Engagements;
Expand Down
34 changes: 18 additions & 16 deletions backend/Api/VacationsController/VacationsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -46,12 +47,12 @@ public async Task<ActionResult<VacationReadModel>> 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);
}
Expand All @@ -68,8 +69,11 @@ public async Task<ActionResult<VacationReadModel>> 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
{
Expand All @@ -82,9 +86,6 @@ public async Task<ActionResult<VacationReadModel>> 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);
}
Expand All @@ -101,8 +102,11 @@ public async Task<ActionResult<VacationReadModel>> 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
{
Expand All @@ -115,9 +119,7 @@ public async Task<ActionResult<VacationReadModel>> 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);
}
Expand Down
7 changes: 3 additions & 4 deletions backend/Api/VacationsController/VacationsValidator.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
2 changes: 1 addition & 1 deletion backend/Core/Consultants/Consultant.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}
Expand Down
16 changes: 16 additions & 0 deletions backend/Core/Consultants/IConsultantRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Core.Consultants;

public interface IConsultantRepository
{
/**
* Get consultant, including common Department, Organization and Competense-data
*/
Task<Consultant?> GetConsultantById(int id, CancellationToken ct);

/**
* Get consultant, including common Department, Organization and Competense-data
*/
Task<Consultant?> GetConsultantByEmail(string orgUrlKey, string email, CancellationToken ct);

Task<List<Consultant>> GetConsultantsInOrganizationByUrlKey(string urlKey, CancellationToken ct);
}
2 changes: 1 addition & 1 deletion backend/Core/PlannedAbsences/PlannedAbsence.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion backend/Core/Staffings/Staffing.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }

Expand Down
2 changes: 1 addition & 1 deletion backend/Core/Vacations/Vacation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public ApplicationContext(DbContextOptions options) : base(options)
{
}

public DbSet<Consultant> Consultant { get; set; } = null!;
public DbSet<Consultant?> Consultant { get; set; } = null!;

Check warning on line 21 in backend/Infrastructure/DatabaseContext/ApplicationContext.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

The type 'Core.Consultants.Consultant?' cannot be used as type parameter 'TEntity' in the generic type or method 'DbSet<TEntity>'. Nullability of type argument 'Core.Consultants.Consultant?' doesn't match 'class' constraint.
public DbSet<Competence> Competence { get; set; } = null!;
public DbSet<CompetenceConsultant> CompetenceConsultant { get; set; } = null!;
public DbSet<Department> Department { get; set; } = null!;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Consultant?> GetConsultantById(int id, CancellationToken ct)
{
return BaseConsultantQuery()
.SingleOrDefaultAsync(c => c.Id == id, ct);
}

public async Task<Consultant?> 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<List<Consultant>> 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<Consultant, Competence> BaseConsultantQuery()
{
return context.Consultant

Check warning on line 40 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

The type 'Core.Consultants.Consultant?' cannot be used as type parameter 'TEntity' in the generic type or method 'EntityFrameworkQueryableExtensions.Include<TEntity, TProperty>(IQueryable<TEntity>, Expression<Func<TEntity, TProperty>>)'. Nullability of type argument 'Core.Consultants.Consultant?' doesn't match 'class' constraint.

Check warning on line 40 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

The type 'Core.Consultants.Consultant?' cannot be used as type parameter 'TEntity' in the generic type or method 'EntityFrameworkQueryableExtensions.ThenInclude<TEntity, TPreviousProperty, TProperty>(IIncludableQueryable<TEntity, TPreviousProperty>, Expression<Func<TPreviousProperty, TProperty>>)'. Nullability of type argument 'Core.Consultants.Consultant?' doesn't match 'class' constraint.

Check warning on line 40 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

The type 'Core.Consultants.Consultant?' cannot be used as type parameter 'TEntity' in the generic type or method 'EntityFrameworkQueryableExtensions.Include<TEntity, TProperty>(IQueryable<TEntity>, Expression<Func<TEntity, TProperty>>)'. Nullability of type argument 'Core.Consultants.Consultant?' doesn't match 'class' constraint.
.Include(c => c.Department)

Check warning on line 41 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

Dereference of a possibly null reference.

Check warning on line 41 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

Nullability of reference types in type of parameter 'c' of 'lambda expression' doesn't match the target delegate 'Func<Consultant?, Department>' (possibly because of nullability attributes).
.ThenInclude(d => d.Organization)
.Include(c => c.CompetenceConsultant)

Check warning on line 43 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

Dereference of a possibly null reference.

Check warning on line 43 in backend/Infrastructure/Repositories/Consultants/ConsultantDbRepository.cs

View workflow job for this annotation

GitHub Actions / dotnet_core_project_tests (8.x.x)

Nullability of reference types in type of parameter 'c' of 'lambda expression' doesn't match the target delegate 'Func<Consultant?, ICollection<CompetenceConsultant>>' (possibly because of nullability attributes).
.ThenInclude(cc => cc.Competence);
}
}
4 changes: 4 additions & 0 deletions backend/Infrastructure/Repositories/RepositoryExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -16,5 +18,7 @@ public static void AddRepositories(this WebApplicationBuilder builder)

builder.Services.AddScoped<IEngagementRepository, EngagementDbRepository>();
builder.Services.AddScoped<IDepartmentRepository, DepartmentDbRepository>();

builder.Services.AddScoped<IConsultantRepository, ConsultantDbRepository>();
}
}

0 comments on commit 9506428

Please sign in to comment.