Skip to content

Commit

Permalink
173197 - Persons API new endpoints (#601)
Browse files Browse the repository at this point in the history
* Added cache service, new entities for the new endpoints

* Added Mediator pattern 
E2E test is now via PersonsAPI.Client

* - Added GetAllPersonsAssociatedWithAcademyAsync endpoint
- Fixed the repository pattern
- Refactored project references and moved interfaces to adhere with CA/DDD
- Implemented Mediatr pattern
- Added E2E API Client tests

* Added value objects

* Implemented Token based authentication

* Added cache service, new entities for the new endpoints
* Added Mediator pattern 
E2E test is now via PersonsAPI.Client
* - Added GetAllPersonsAssociatedWithAcademyAsync endpoint
- Fixed the repository pattern
- Refactored project references and moved interfaces to adhere with CA/DDD
- Implemented Mediatr pattern
- Added E2E API Client tests

* Implemented Token based authentication

* Added API Key authentication support as a temporary workaround if JWT Token is not provided

* Implemented batch endpoint to retrieve MPs by a collection of Constituencies

* Added validation to the requests, refactored the pattern and palcements of the interfaces

* Excluded EducationEstablishmentGovernance and GovernanceRoleType from migration

* General refactoring, added auto fixture attributes
  • Loading branch information
FrostyApeOne authored Sep 16, 2024
1 parent 2521439 commit 949b246
Show file tree
Hide file tree
Showing 141 changed files with 3,311 additions and 881 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-push-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Build and Push NuGet Package
on:
push:
branches:
- package/release.*
- release/*
paths:
- 'Dfe.PersonsApi.Client*/**'

Expand Down
8 changes: 8 additions & 0 deletions Dfe.Academies.Api.Infrastructure/Caching/CacheSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Dfe.Academies.Infrastructure.Caching
{
public class CacheSettings
{
public int DefaultDurationInSeconds { get; set; } = 5;
public Dictionary<string, int> Durations { get; set; } = new();
}
}
52 changes: 52 additions & 0 deletions Dfe.Academies.Api.Infrastructure/Caching/MemoryCacheService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;
using Dfe.Academies.Domain.Interfaces.Caching;

namespace Dfe.Academies.Infrastructure.Caching
{
[ExcludeFromCodeCoverage]
public class MemoryCacheService(
IMemoryCache memoryCache,
ILogger<MemoryCacheService> logger,
IOptions<CacheSettings> cacheSettings)
: ICacheService
{
private readonly CacheSettings _cacheSettings = cacheSettings.Value;

public async Task<T> GetOrAddAsync<T>(string cacheKey, Func<Task<T>> fetchFunction, string methodName)
{
if (memoryCache.TryGetValue(cacheKey, out T? cachedValue))
{
logger.LogInformation("Cache hit for key: {CacheKey}", cacheKey);
return cachedValue!;
}

logger.LogInformation("Cache miss for key: {CacheKey}. Fetching from source...", cacheKey);
var result = await fetchFunction();

if (Equals(result, default(T))) return result;
var cacheDuration = GetCacheDurationForMethod(methodName);
memoryCache.Set(cacheKey, result, cacheDuration);
logger.LogInformation("Cached result for key: {CacheKey} for duration: {CacheDuration}", cacheKey, cacheDuration);

return result;
}

public void Remove(string cacheKey)
{
memoryCache.Remove(cacheKey);
logger.LogInformation("Cache removed for key: {CacheKey}", cacheKey);
}

private TimeSpan GetCacheDurationForMethod(string methodName)
{
if (_cacheSettings.Durations.TryGetValue(methodName, out int durationInSeconds))
{
return TimeSpan.FromSeconds(durationInSeconds);
}
return TimeSpan.FromSeconds(_cacheSettings.DefaultDurationInSeconds);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@
<ItemGroup>
<PackageReference Include="CsvHelper" Version="33.0.1" />
<PackageReference Include="Dfe.Academies.Contracts" Version="1.0.10" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.8" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Identity.Web" Version="3.1.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Dfe.Academies.Domain\Dfe.Academies.Domain.csproj" />
<Folder Include="Migrations\Mop\" />
</ItemGroup>

<ItemGroup>
<Folder Include="Migrations\Mop\" />
<ProjectReference Include="..\Dfe.Academies.Application\Dfe.Academies.Application.csproj" />
<ProjectReference Include="..\Dfe.Academies.Domain\Dfe.Academies.Domain.csproj" />
</ItemGroup>

</Project>
10 changes: 1 addition & 9 deletions Dfe.Academies.Api.Infrastructure/EdperfContext.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
using Dfe.Academies.Academisation.Data;
using Dfe.Academies.Domain.EducationalPerformance;
using Dfe.Academies.Domain.Establishment;
using Dfe.Academies.Domain.Trust;
using Dfe.Academies.Domain.EducationalPerformance;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Dfe.Academies.Infrastructure
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using Dfe.Academies.Application.Common.Interfaces;
using Dfe.Academies.Infrastructure;
using Dfe.Academies.Infrastructure.Caching;
using Dfe.Academies.Infrastructure.Repositories;
using Dfe.Academies.Infrastructure.Security.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Domain.Interfaces.Caching;
using Dfe.Academies.Infrastructure.QueryServices;

namespace Microsoft.Extensions.DependencyInjection
{
public static class InfrastructureServiceCollectionExtensions
{
public static IServiceCollection AddInfrastructureDependencyGroup(
this IServiceCollection services, IConfiguration config)
{
//Repos
services.AddScoped<ITrustRepository, TrustRepository>();
services.AddScoped<IEstablishmentRepository, EstablishmentRepository>();
services.AddSingleton<ICensusDataRepository, CensusDataRepository>();
services.AddScoped<IEducationalPerformanceRepository, EducationalPerformanceRepository>();

//Db
var connectionString = config.GetConnectionString("DefaultConnection");

services.AddDbContext<MstrContext>(options =>
options.UseSqlServer(connectionString));

services.AddDbContext<EdperfContext>(options =>
options.UseSqlServer(connectionString));

return services;
}

public static IServiceCollection AddPersonsApiInfrastructureDependencyGroup(
this IServiceCollection services, IConfiguration config)
{
//Repos
services.AddScoped<ITrustRepository, TrustRepository>();
services.AddScoped<IEstablishmentRepository, EstablishmentRepository>();
services.AddScoped<IConstituencyRepository, ConstituencyRepository>();
services.AddScoped(typeof(IMstrRepository<>), typeof(MstrRepository<>));
services.AddScoped(typeof(IMopRepository<>), typeof(MopRepository<>));

// Query Services
services.AddScoped<IEstablishmentQueryService, EstablishmentQueryService>();

//Cache service
services.Configure<CacheSettings>(config.GetSection("CacheSettings"));
services.AddSingleton<ICacheService, MemoryCacheService>();

//Db
var connectionString = config.GetConnectionString("DefaultConnection");

services.AddDbContext<MstrContext>(options =>
options.UseSqlServer(connectionString));

services.AddDbContext<MopContext>(options =>
options.UseSqlServer(connectionString));

// Authentication
services.AddCustomAuthorization(config);

return services;
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// <auto-generated />
using System;
using Dfe.Academies.Academisation.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
Expand All @@ -20,15 +19,15 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasAnnotation("ProductVersion", "6.0.25")
.HasAnnotation("Relational:MaxIdentifierLength", 128);

SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder, 1L, 1);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);

modelBuilder.Entity("Dfe.Academies.Domain.Establishment.EducationEstablishmentTrust", b =>
{
b.Property<int>("SK")
.ValueGeneratedOnAdd()
.HasColumnType("int");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("SK"));

b.Property<int>("EducationEstablishmentId")
.HasColumnType("int")
Expand All @@ -49,7 +48,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"));

b.Property<string>("AddressLine1")
.HasColumnType("nvarchar(max)")
Expand Down Expand Up @@ -413,7 +412,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"));

b.Property<string>("Code")
.HasColumnType("nvarchar(max)");
Expand Down Expand Up @@ -446,7 +445,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"));

b.Property<string>("DeliveryProcessPAN")
.HasColumnType("nvarchar(max)")
Expand Down Expand Up @@ -479,7 +478,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"));

b.Property<string>("Code")
.HasColumnType("nvarchar(max)");
Expand Down Expand Up @@ -518,7 +517,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long?>("SK"));

b.Property<string>("AMSDTerritory")
.HasColumnType("nvarchar(max)")
Expand Down Expand Up @@ -699,7 +698,7 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.ValueGeneratedOnAdd()
.HasColumnType("bigint");

SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"), 1L, 1);
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<long>("SK"));

b.Property<string>("Code")
.HasColumnType("nvarchar(max)");
Expand Down
43 changes: 32 additions & 11 deletions Dfe.Academies.Api.Infrastructure/MopContext.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
using Dfe.Academies.Domain.Persons;
using Dfe.Academies.Domain.Constituencies;
using Dfe.Academies.Domain.ValueObjects;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace Dfe.Academies.Academisation.Data;
namespace Dfe.Academies.Infrastructure;

public class MopContext : DbContext
{
Expand Down Expand Up @@ -37,27 +38,47 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
}


private void ConfigureMemberContactDetails(EntityTypeBuilder<MemberContactDetails> memberContactDetailsConfiguration)
private static void ConfigureMemberContactDetails(EntityTypeBuilder<MemberContactDetails> memberContactDetailsConfiguration)
{
memberContactDetailsConfiguration.HasKey(e => e.MemberID);
memberContactDetailsConfiguration.HasKey(e => e.MemberId);

memberContactDetailsConfiguration.ToTable("MemberContactDetails", DEFAULT_SCHEMA);
memberContactDetailsConfiguration.Property(e => e.MemberID).HasColumnName("memberID");
memberContactDetailsConfiguration.Property(e => e.MemberId).HasColumnName("memberID")
.HasConversion(
v => v.Value,
v => new MemberId(v));
memberContactDetailsConfiguration.Property(e => e.Email).HasColumnName("email");
memberContactDetailsConfiguration.Property(e => e.Phone).HasColumnName("phone");
memberContactDetailsConfiguration.Property(e => e.TypeId).HasColumnName("typeId");
}

private void ConfigureConstituency(EntityTypeBuilder<Constituency> constituencyConfiguration)
{
constituencyConfiguration.ToTable("Constituencies", DEFAULT_SCHEMA);
constituencyConfiguration.Property(e => e.ConstituencyId).HasColumnName("constituencyId");
constituencyConfiguration.Property(e => e.ConstituencyId).HasColumnName("constituencyId")
.HasConversion(
v => v.Value,
v => new ConstituencyId(v));
constituencyConfiguration.Property(e => e.MemberId)
.HasConversion(
v => v.Value,
v => new MemberId(v));
constituencyConfiguration.Property(e => e.ConstituencyName).HasColumnName("constituencyName");
constituencyConfiguration.Property(e => e.NameList).HasColumnName("nameListAs");
constituencyConfiguration.Property(e => e.NameDisplayAs).HasColumnName("nameDisplayAs");
constituencyConfiguration.Property(e => e.NameFullTitle).HasColumnName("nameFullTitle");
constituencyConfiguration.Property(e => e.NameFullTitle).HasColumnName("nameFullTitle");

constituencyConfiguration.OwnsOne(e => e.NameDetails, nameDetails =>
{
nameDetails.Property(nd => nd.NameListAs).HasColumnName("nameListAs");
nameDetails.Property(nd => nd.NameDisplayAs).HasColumnName("nameDisplayAs");
nameDetails.Property(nd => nd.NameFullTitle).HasColumnName("nameFullTitle");
});

constituencyConfiguration.Property(e => e.LastRefresh).HasColumnName("lastRefresh");

constituencyConfiguration
.HasOne(c => c.MemberContactDetails)
.WithOne()
.HasForeignKey<Constituency>(c => c.MemberId)
.HasPrincipalKey<MemberContactDetails>(m => m.MemberId);
}


Expand Down
9 changes: 2 additions & 7 deletions Dfe.Academies.Api.Infrastructure/MopRepository.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,7 @@
using Dfe.Academies.Academisation.Data;
using Dfe.Academies.Domain.Interfaces.Repositories;
using Dfe.Academies.Infrastructure.Repositories;

namespace Dfe.Academies.Infrastructure
{
public class MopRepository<TEntity> : Repository<TEntity, MopContext> where TEntity : class, new()
{
public MopRepository(MopContext dbContext) : base(dbContext)
{
}
}
public class MopRepository<TEntity>(MopContext dbContext) : Repository<TEntity, MopContext>(dbContext), IMopRepository<TEntity> where TEntity : class, new();
}
Loading

0 comments on commit 949b246

Please sign in to comment.