Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion TeachingRecordSystem/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageVersion Include="AngleSharp" Version="1.4.0" />
<PackageVersion Include="AspNetCore.HealthChecks.NpgSql" Version="9.0.0" />
<PackageVersion Include="AspNetCore.HealthChecks.Redis" Version="9.0.0" />
<PackageVersion Include="AutoMapper" Version="13.0.1" />
<PackageVersion Include="Riok.Mapperly" Version="4.3.1" />
<PackageVersion Include="Azure.Extensions.AspNetCore.DataProtection.Blobs" Version="1.5.1" />
<PackageVersion Include="Azure.Storage.Blobs" Version="12.26.0" />
<PackageVersion Include="Azure.Storage.Files.DataLake" Version="12.24.0" />
Expand Down
36 changes: 15 additions & 21 deletions TeachingRecordSystem/src/TeachingRecordSystem.Api/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@
using FluentValidation.AspNetCore;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using OneOf;
using Optional;
using TeachingRecordSystem.Api.Infrastructure.ApplicationModel;
using TeachingRecordSystem.Api.Infrastructure.Filters;
using TeachingRecordSystem.Api.Infrastructure.Mapping;
using TeachingRecordSystem.Api.Infrastructure.ModelBinding;
using TeachingRecordSystem.Api.Infrastructure.OpenApi;
using TeachingRecordSystem.Api.Infrastructure.RateLimiting;
Expand Down Expand Up @@ -47,14 +44,6 @@ public static IHostApplicationBuilder AddApiServices(this IHostApplicationBuilde

public static IServiceCollection AddApiServices(this IServiceCollection services, IConfiguration configuration, IHostEnvironment environment)
{
services.Scan(scan =>
{
scan.FromAssemblies(typeof(Extensions).Assembly)
.AddClasses(filter => filter.AssignableTo(typeof(ITypeConverter<,>)))
.AsSelf()
.WithTransientLifetime();
});

services
.AddMvc(options =>
{
Expand Down Expand Up @@ -132,7 +121,7 @@ public static IServiceCollection AddApiServices(this IServiceCollection services
.AddWebhookOptions(configuration)
.AddOpenApi(configuration)
.AddFluentValidation()
.AddAutoMapper()
.AddApiMappers()
.AddHttpContextAccessor()
.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining<Program>())
.AddSingleton<ICurrentUserProvider, ClaimsPrincipalCurrentUserProvider>()
Expand All @@ -156,16 +145,21 @@ public static IServiceCollection AddApiServices(this IServiceCollection services
return services;
}

private static IServiceCollection AddAutoMapper(this IServiceCollection services)
private static IServiceCollection AddApiMappers(this IServiceCollection services)
{
services.AddAutoMapper(cfg =>
{
cfg.AddMaps(typeof(Program).Assembly);
cfg.CreateMap(typeof(Option<>), typeof(Option<>)).ConvertUsing(typeof(OptionToOptionTypeConverter<,>));
cfg.CreateMap(typeof(OneOf<,>), typeof(OneOf<,>)).ConvertUsing(typeof(OneOfToOneOfTypeConverter<,,,>));
})
.AddTransient(typeof(WrapWithOptionValueConverter<>))
.AddTransient(typeof(WrapWithOptionValueConverter<,>));
services
.AddSingleton<V3.V20240101.ApiMapper>()
.AddSingleton<V3.V20240307.ApiMapper>()
.AddSingleton<V3.V20240412.ApiMapper>()
.AddSingleton<V3.V20240416.ApiMapper>()
.AddSingleton<V3.V20240606.ApiMapper>()
.AddSingleton<V3.V20240814.ApiMapper>()
.AddSingleton<V3.V20240912.ApiMapper>()
.AddSingleton<V3.V20240920.ApiMapper>()
.AddSingleton<V3.V20250203.ApiMapper>()
.AddSingleton<V3.V20250327.ApiMapper>()
.AddSingleton<V3.V20250425.ApiMapper>()
.AddSingleton<V3.V20250627.ApiMapper>();

return services;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
global using AutoMapper;
global using TeachingRecordSystem.Core.ApiSchema;
global using PostgresModels = TeachingRecordSystem.Core.DataStore.Postgres.Models;

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="AutoMapper" />
<PackageReference Include="Riok.Mapperly" />
<PackageReference Include="FluentValidation.AspNetCore" />
<PackageReference Include="MediatR" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
using Optional;
using Riok.Mapperly.Abstractions;
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.V20240101.Responses;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240101.Dtos;

namespace TeachingRecordSystem.Api.V3.V20240101;

[Mapper]
public partial class ApiMapper
{
public GetTeacherResponse MapGetTeacherResponse(GetPersonResult source) =>
new()
{
Trn = source.Trn,
FirstName = source.FirstName,
MiddleName = source.MiddleName,
LastName = source.LastName,
DateOfBirth = source.DateOfBirth,
NationalInsuranceNumber = source.NationalInsuranceNumber,
PendingNameChange = source.PendingNameChange,
PendingDateOfBirthChange = source.PendingDateOfBirthChange,
Email = source.EmailAddress,
Qts = source.Qts is { } qts ? MapQts(qts) : null,
Eyts = source.Eyts is { } eyts ? MapEyts(eyts) : null,
Induction = source.DqtInduction.Map(d => d is null ? (GetTeacherResponseInduction?)null : MapDqtInduction(d)),
InitialTeacherTraining = source.InitialTeacherTraining.Map(
itt => (IReadOnlyCollection<GetTeacherResponseInitialTeacherTraining>)itt.AsT0
.Select(MapInitialTeacherTraining).AsReadOnly()),
NpqQualifications = Option.None<IReadOnlyCollection<GetTeacherResponseNpqQualification>>(),
MandatoryQualifications = source.MandatoryQualifications.Map(
mqs => (IReadOnlyCollection<GetTeacherResponseMandatoryQualification>)mqs
.Select(MapMandatoryQualification).AsReadOnly()),
HigherEducationQualifications = Option.None<IReadOnlyCollection<GetTeacherResponseHigherEducationQualification>>(),
Sanctions = source.Sanctions.Map(
ss => (IReadOnlyCollection<SanctionInfo>)ss.Select(MapSanctionInfo).AsReadOnly()),
Alerts = source.Alerts.Map(
alerts => (IReadOnlyCollection<AlertInfo>)alerts.Select(MapAlertInfo).AsReadOnly()),
PreviousNames = source.PreviousNames.Map(
pns => (IReadOnlyCollection<NameInfo>)pns.Select(MapNameInfo).AsReadOnly()),
AllowIdSignInWithProhibitions = source.AllowIdSignInWithProhibitions
};

public FindTeachersResponseResult MapFindTeachersResponseResult(FindPersonsResultItem source) =>
new()
{
Trn = source.Trn,
DateOfBirth = source.DateOfBirth,
FirstName = source.FirstName,
MiddleName = source.MiddleName,
LastName = source.LastName,
Sanctions = source.Sanctions.Select(MapSanctionInfo).AsReadOnly(),
PreviousNames = source.PreviousNames.Select(MapNameInfo).AsReadOnly()
};

internal GetTeacherResponseQts MapQts(Implementation.Dtos.QtsInfo source) =>
new()
{
Awarded = source.HoldsFrom,
CertificateUrl = source.CertificateUrl,
StatusDescription = source.StatusDescription
};

internal GetTeacherResponseEyts MapEyts(Implementation.Dtos.EytsInfo source) =>
new()
{
Awarded = source.HoldsFrom,
CertificateUrl = source.CertificateUrl,
StatusDescription = source.StatusDescription
};

internal GetTeacherResponseInduction MapDqtInduction(GetPersonResultDqtInduction source) =>
new()
{
StartDate = source.StartDate,
EndDate = source.EndDate,
Status = MapDqtInductionStatus(source.Status),
StatusDescription = source.StatusDescription,
CertificateUrl = source.CertificateUrl,
Periods = source.Periods.Select(MapInductionPeriod).AsReadOnly()
};

private GetTeacherResponseInductionPeriod MapInductionPeriod(GetPersonResultDqtInductionPeriod source) =>
new()
{
StartDate = source.StartDate,
EndDate = source.EndDate,
Terms = source.Terms,
AppropriateBody = source.AppropriateBody is { } ab
? new GetTeacherResponseInductionPeriodAppropriateBody { Name = ab.Name }
: null
};

internal GetTeacherResponseInitialTeacherTraining MapInitialTeacherTraining(GetPersonResultInitialTeacherTraining source) =>
new()
{
Qualification = source.Qualification is { } q
? new GetTeacherResponseInitialTeacherTrainingQualification { Name = q.Name }
: null,
StartDate = source.StartDate,
EndDate = source.EndDate,
ProgrammeType = null,
ProgrammeTypeDescription = null,
Result = null,
AgeRange = source.AgeRange is { } ar
? new GetTeacherResponseInitialTeacherTrainingAgeRange { Description = ar.Description }
: null,
Provider = source.Provider is { } p
? new GetTeacherResponseInitialTeacherTrainingProvider { Name = p.Name, Ukprn = p.Ukprn }
: null,
Subjects = source.Subjects
.Select(s => new GetTeacherResponseInitialTeacherTrainingSubject { Code = s.Code, Name = s.Name })
.AsReadOnly()
};

private GetTeacherResponseMandatoryQualification MapMandatoryQualification(GetPersonResultMandatoryQualification source) =>
new()
{
Awarded = source.EndDate,
Specialism = source.Specialism
};

internal SanctionInfo MapSanctionInfo(Implementation.Dtos.SanctionInfo source) =>
new() { Code = source.Code, StartDate = source.StartDate };

internal AlertInfo MapAlertInfo(Implementation.Dtos.Alert source) =>
new()
{
AlertType = AlertType.Prohibition,
DqtSanctionCode = source.AlertType.DqtSanctionCode!,
StartDate = source.StartDate,
EndDate = source.EndDate
};

internal NameInfo MapNameInfo(Implementation.Dtos.NameInfo source) =>
new() { FirstName = source.FirstName, MiddleName = source.MiddleName, LastName = source.LastName };

internal static Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus MapDqtInductionStatus(Implementation.Dtos.DqtInductionStatus source) =>
source switch
{
Implementation.Dtos.DqtInductionStatus.Exempt => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.Exempt,
Implementation.Dtos.DqtInductionStatus.Fail => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.Fail,
Implementation.Dtos.DqtInductionStatus.FailedInWales => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.FailedinWales,
Implementation.Dtos.DqtInductionStatus.InductionExtended => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.InductionExtended,
Implementation.Dtos.DqtInductionStatus.InProgress => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.InProgress,
Implementation.Dtos.DqtInductionStatus.NotYetCompleted => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.NotYetCompleted,
Implementation.Dtos.DqtInductionStatus.Pass => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.Pass,
Implementation.Dtos.DqtInductionStatus.PassedInWales => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.PassedinWales,
Implementation.Dtos.DqtInductionStatus.RequiredToComplete => Core.ApiSchema.V3.V20240101.Dtos.DqtInductionStatus.RequiredtoComplete,
_ => throw new ArgumentOutOfRangeException(nameof(source))
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
namespace TeachingRecordSystem.Api.V3.V20240101.Controllers;

[Route("teacher")]
public class TeacherController(ICommandDispatcher commandDispatcher, IMapper mapper) : ControllerBase
public class TeacherController(ICommandDispatcher commandDispatcher, ApiMapper mapper) : ControllerBase
{
[Authorize(AuthorizationPolicies.IdentityUserWithTrn)]
[HttpGet]
Expand All @@ -36,7 +36,7 @@ public async Task<IActionResult> GetAsync(

var result = await commandDispatcher.DispatchAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
return result.ToActionResult(r => Ok(mapper.MapGetTeacherResponse(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status403Forbidden);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace TeachingRecordSystem.Api.V3.V20240101.Controllers;

[Route("teachers")]
public class TeachersController(ICommandDispatcher commandDispatcher, IMapper mapper) : ControllerBase
public class TeachersController(ICommandDispatcher commandDispatcher, ApiMapper mapper) : ControllerBase
{
[HttpGet("{trn}")]
[SwaggerOperation(
Expand All @@ -37,7 +37,7 @@ public async Task<IActionResult> GetAsync(

var result = await commandDispatcher.DispatchAsync(command);

return result.ToActionResult(r => Ok(mapper.Map<GetTeacherResponse>(r)))
return result.ToActionResult(r => Ok(mapper.MapGetTeacherResponse(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.RecordIsDeactivated, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.RecordIsMerged, StatusCodes.Status404NotFound);
Expand Down Expand Up @@ -113,7 +113,7 @@ public async Task<IActionResult> FindTeachersAsync(FindTeachersRequest request)
{
Total = r.Total,
Query = request,
Results = r.Items.Select(mapper.Map<FindTeachersResponseResult>).AsReadOnly()
Results = r.Items.Select(mapper.MapFindTeachersResponseResult).AsReadOnly()
}));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.V20240101.Requests;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240101.Dtos;

Expand All @@ -11,7 +10,6 @@ public record FindTeachersResponse
public required IReadOnlyCollection<FindTeachersResponseResult> Results { get; init; }
}

[AutoMap(typeof(FindPersonsResultItem))]
public record FindTeachersResponseResult
{
public required string Trn { get; init; }
Expand Down
Loading