Skip to content

Commit

Permalink
Release V20250203 API version (#1851)
Browse files Browse the repository at this point in the history
In preparation for induction migration, we need the related APIs to be
available in a formal API version.

I've added every other pending change in `VNext` too save for those that
are not implemented.
  • Loading branch information
gunndabad authored Feb 4, 2025
1 parent 99cfc3a commit d9738f1
Show file tree
Hide file tree
Showing 30 changed files with 242 additions and 178 deletions.
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,47 @@
# API Changelog

## 20250203

The `PUT /v3/persons/<trn>/cpd-induction` endpoint has been added.

The `GET /v3/persons/<trn>` endpoint now supports passing a `nationalInsuranceNumber` query parameter.
If specified, it must match the National Insurance number on the teaching record with TRN `<trn>`.

The TRN request endpoint `PUT /v3/trn-requests` has the following additional properties:
- `oneLoginUserSubject` (if the TRN request is for a One Login user);
- `identityVerified` (if the One Login user's identity has been verified).
- `gender`.

The ability to return higher education qualifications has been removed from `GET /v3/person` and `GET /v3/persons/<trn>`.

The `induction` object in responses to the following endpoints has the `statusDescription` property removed and
the `endDate` property replaced by `completedDate`:
- `GET /v3/persons/<trn>`
- `GET /v3/person`
- `GET /v3/persons?findBy=LastNameAndDateOfBirth`
- `GET /v3/persons/find`.
In addition, the status field will now only contain the following values:
- `None`
- `RequiredToComplete`
- `Exempt`
- `InProgress`
- `Passed`
- `Failed`
- `FailedInWales`.
Note that `null` will no longer be returned.

The responses for the following endpoints now contain a `qtlsStatus` property:
- `GET /v3/persons/<trn>`
- `GET /v3/person`
- `GET /v3/persons?findBy=LastNameAndDateOfBirth`
- `GET /v3/persons/find`.

The following certificate endpoints have been removed:
- `GET /v3/certificates/qts`
- `GET /v3/certificates/eyts`
- `GET /v3/certificates/induction`
- `GET /v3/certificates/npq/{qualificationId}`

## 20240920

All references to `sanctions` have been removed and replaced with `alerts`; the following endpoints are affected:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
using Microsoft.AspNetCore.Mvc;
using TeachingRecordSystem.Api.Infrastructure.Security;

namespace TeachingRecordSystem.Api.V3.VNext.Controllers;
namespace TeachingRecordSystem.Api.V3.V20250203.Controllers;

[Route("certificates")]
[Authorize(AuthorizationPolicies.IdentityUserWithTrn)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using TeachingRecordSystem.Api.Infrastructure.ModelBinding;
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.VNext.Requests;
using TeachingRecordSystem.Api.V3.VNext.Responses;
using TeachingRecordSystem.Api.V3.V20250203.Requests;
using TeachingRecordSystem.Api.V3.V20250203.Responses;

namespace TeachingRecordSystem.Api.V3.VNext.Controllers;
namespace TeachingRecordSystem.Api.V3.V20250203.Controllers;

[Route("person")]
public class PersonController(IMapper mapper) : ControllerBase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Swashbuckle.AspNetCore.Annotations;
using TeachingRecordSystem.Api.Infrastructure.ModelBinding;
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.V20250203.Requests;
using TeachingRecordSystem.Api.V3.V20250203.Responses;

namespace TeachingRecordSystem.Api.V3.V20250203.Controllers;

[Route("persons")]
public class PersonsController(IMapper mapper) : ControllerBase
{
[HttpPut("{trn}/cpd-induction")]
[SwaggerOperation(
OperationId = "SetPersonCpdInductionStatus",
Summary = "Set person induction status",
Description = "Sets the induction details of the person with the given TRN.")]
[ProducesResponseType(typeof(void), StatusCodes.Status204NoContent)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = ApiRoles.SetCpdInduction)]
public async Task<IActionResult> SetCpdInductionStatusAsync(
[FromRoute] string trn,
[FromBody] SetCpdInductionStatusRequest request,
[FromServices] SetCpdInductionStatusHandler handler)
{
var command = new SetCpdInductionStatusCommand(
trn,
mapper.Map<InductionStatus>(request.Status),
request.StartDate,
request.CompletedDate,
request.ModifiedOn.UtcDateTime);

var result = await handler.HandleAsync(command);

return result.ToActionResult(_ => NoContent())
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.StaleRequest, StatusCodes.Status409Conflict);
}

[HttpGet("{trn}")]
[SwaggerOperation(
OperationId = "GetPersonByTrn",
Summary = "Get person details by TRN",
Description = "Gets the details of the person corresponding to the given TRN.")]
[ProducesResponseType(typeof(GetPersonResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
[Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = $"{ApiRoles.GetPerson},{ApiRoles.AppropriateBody}")]
public async Task<IActionResult> GetAsync(
[FromRoute] string trn,
[FromQuery, ModelBinder(typeof(FlagsEnumStringListModelBinder)), SwaggerParameter("The additional properties to include in the response.")] GetPersonRequestIncludes? include,
[FromQuery, SwaggerParameter("Adds an additional check that the record has the specified dateOfBirth, if provided.")] DateOnly? dateOfBirth,
[FromQuery, SwaggerParameter("Adds an additional check that the record has the specified nationalInsuranceNumber, if provided.")] string? nationalInsuranceNumber,
[FromServices] GetPersonHandler handler)
{
include ??= GetPersonRequestIncludes.None;

// For now we don't support both a DOB and NINO being passed
if (dateOfBirth is not null && nationalInsuranceNumber is not null)
{
return BadRequest();
}

var command = new GetPersonCommand(
trn,
(GetPersonCommandIncludes)include,
dateOfBirth,
ApplyLegacyAlertsBehavior: false,
ApplyAppropriateBodyUserRestrictions: User.IsInRole(ApiRoles.AppropriateBody),
nationalInsuranceNumber);

var result = await handler.HandleAsync(command);

return result
.ToActionResult(r => Ok(mapper.Map<GetPersonResponse>(r)))
.MapErrorCode(ApiError.ErrorCodes.PersonNotFound, StatusCodes.Status404NotFound)
.MapErrorCode(ApiError.ErrorCodes.ForbiddenForAppropriateBody, StatusCodes.Status403Forbidden);
}

[HttpPost("find")]
[SwaggerOperation(
OperationId = "FindPersons",
Summary = "Find persons",
Description = "Finds persons matching the specified criteria.")]
[ProducesResponseType(typeof(FindPersonsResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = ApiRoles.GetPerson)]
public async Task<IActionResult> FindPersonsAsync(
[FromBody] FindPersonsRequest request,
[FromServices] FindPersonsByTrnAndDateOfBirthHandler handler)
{
var command = new FindPersonsByTrnAndDateOfBirthCommand(request.Persons.Select(p => (p.Trn, p.DateOfBirth)));
var result = await handler.HandleAsync(command);
return result.ToActionResult(r => Ok(mapper.Map<FindPersonsResponse>(r)));
}

[HttpGet("")]
[SwaggerOperation(
OperationId = "FindPerson",
Summary = "Find person",
Description = "Finds a person matching the specified criteria.")]
[ProducesResponseType(typeof(FindPersonResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
[Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = ApiRoles.GetPerson)]
public async Task<IActionResult> FindPersonsAsync(
FindPersonRequest request,
[FromServices] FindPersonByLastNameAndDateOfBirthHandler handler)
{
var command = new FindPersonByLastNameAndDateOfBirthCommand(request.LastName!, request.DateOfBirth!.Value);
var result = await handler.HandleAsync(command);

return result.ToActionResult(r =>
Ok(new FindPersonResponse()
{
Total = r.Total,
Query = request,
Results = r.Items.Select(mapper.Map<FindPersonResponseResult>).AsReadOnly()
}));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
using Swashbuckle.AspNetCore.Annotations;
using TeachingRecordSystem.Api.Infrastructure.Security;
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.VNext.Requests;
using TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos;
using TeachingRecordSystem.Api.V3.V20250203.Requests;
using TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos;
using TrnRequestInfo = TeachingRecordSystem.Core.ApiSchema.V3.V20240606.Dtos.TrnRequestInfo;

namespace TeachingRecordSystem.Api.V3.VNext.Controllers;
namespace TeachingRecordSystem.Api.V3.V20250203.Controllers;

[Route("trn-requests")]
[Authorize(Policy = AuthorizationPolicies.ApiKey, Roles = ApiRoles.CreateTrn)]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos;
using TrnRequestInfo = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.TrnRequestInfo;

namespace TeachingRecordSystem.Api.V3.V20250203;

public class MapperProfile : Profile
{
public MapperProfile()
{
CreateMap<Implementation.Dtos.TrnRequestInfo, TrnRequestInfo>();
CreateMap<Implementation.Dtos.InductionInfo, InductionInfo>();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Swashbuckle.AspNetCore.Annotations;
using TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos;

namespace TeachingRecordSystem.Api.V3.VNext.Requests;
namespace TeachingRecordSystem.Api.V3.V20250203.Requests;

public record CreateTrnRequestRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc;

namespace TeachingRecordSystem.Api.V3.VNext.Requests;
namespace TeachingRecordSystem.Api.V3.V20250203.Requests;

public record FindPersonRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace TeachingRecordSystem.Api.V3.VNext.Requests;
namespace TeachingRecordSystem.Api.V3.V20250203.Requests;

public record FindPersonsRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using System.ComponentModel;

namespace TeachingRecordSystem.Api.V3.VNext.Requests;
namespace TeachingRecordSystem.Api.V3.V20250203.Requests;

[Flags]
[Description("Comma-separated list of data to include in response.")]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos.InductionStatus;
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.InductionStatus;

namespace TeachingRecordSystem.Api.V3.VNext.Requests;
namespace TeachingRecordSystem.Api.V3.V20250203.Requests;

public record SetCpdInductionStatusRequest
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Api.V3.VNext.Requests;
using TeachingRecordSystem.Api.V3.V20250203.Requests;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240101.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240814.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240920.Dtos;
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos.InductionStatus;
using QtlsStatus = TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos.QtlsStatus;
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.InductionStatus;
using QtlsStatus = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.QtlsStatus;

namespace TeachingRecordSystem.Api.V3.VNext.Responses;
namespace TeachingRecordSystem.Api.V3.V20250203.Responses;

public record FindPersonResponse
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240814.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240920.Dtos;
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos.InductionStatus;
using InductionStatus = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.InductionStatus;
using NameInfo = TeachingRecordSystem.Core.ApiSchema.V3.V20240101.Dtos.NameInfo;
using QtlsStatus = TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos.QtlsStatus;
using QtlsStatus = TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos.QtlsStatus;

namespace TeachingRecordSystem.Api.V3.VNext.Responses;
namespace TeachingRecordSystem.Api.V3.V20250203.Responses;

[AutoMap(typeof(FindPersonsResult))]
public record FindPersonsResponse
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
using TeachingRecordSystem.Api.V3.Implementation.Operations;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240101.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20240920.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.VNext.Dtos;
using TeachingRecordSystem.Core.ApiSchema.V3.V20250203.Dtos;

namespace TeachingRecordSystem.Api.V3.VNext.Responses;
namespace TeachingRecordSystem.Api.V3.V20250203.Responses;

[AutoMap(typeof(GetPersonResult))]
public record GetPersonResponse
Expand All @@ -20,7 +20,7 @@ public record GetPersonResponse
public required string? EmailAddress { get; init; }
public required GetPersonResponseQts? Qts { get; init; }
public required GetPersonResponseEyts? Eyts { get; init; }
public required Option<GetPersonResponseInduction> Induction { get; init; }
public required Option<InductionInfo> Induction { get; init; }
public required Option<IReadOnlyCollection<GetPersonResponseInitialTeacherTraining>> InitialTeacherTraining { get; init; }
public required Option<IReadOnlyCollection<GetPersonResponseNpqQualification>> NpqQualifications { get; init; }
public required Option<IReadOnlyCollection<GetPersonResponseMandatoryQualification>> MandatoryQualifications { get; init; }
Expand All @@ -35,15 +35,13 @@ public record GetPersonResponse
public record GetPersonResponseQts
{
public required DateOnly? Awarded { get; init; }
public required string CertificateUrl { get; init; }
public required string? StatusDescription { get; init; }
}

[AutoMap(typeof(Implementation.Dtos.EytsInfo))]
public record GetPersonResponseEyts
{
public required DateOnly? Awarded { get; init; }
public required string CertificateUrl { get; init; }
public required string? StatusDescription { get; init; }
}

Expand Down Expand Up @@ -92,7 +90,6 @@ public record GetPersonResponseNpqQualification
{
public required DateOnly Awarded { get; init; }
public required GetPersonResponseNpqQualificationType Type { get; init; }
public required string CertificateUrl { get; init; }
}

[AutoMap(typeof(GetPersonResultNpqQualificationType))]
Expand All @@ -108,9 +105,3 @@ public record GetPersonResponseMandatoryQualification
public required DateOnly Awarded { get; init; }
public required string Specialism { get; init; }
}

[AutoMap(typeof(GetPersonResultInduction))]
public record GetPersonResponseInduction : InductionInfo
{
public required string? CertificateUrl { get; init; }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using FluentValidation;
using TeachingRecordSystem.Api.V3.VNext.Requests;
using TeachingRecordSystem.Api.V3.V20250203.Requests;

namespace TeachingRecordSystem.Api.V3.VNext.Validators;
namespace TeachingRecordSystem.Api.V3.V20250203.Validators;

public class CreateTrnRequestRequestValidator : AbstractValidator<CreateTrnRequestRequest>
{
Expand Down
Loading

0 comments on commit d9738f1

Please sign in to comment.