From a9b0df141f4a710bf9b002cf1227fc444b2d6bb9 Mon Sep 17 00:00:00 2001 From: Nicholas Tsim Date: Fri, 15 Nov 2024 14:35:45 +0000 Subject: [PATCH 1/4] EES-5663 Remove minor versions from public API versioning Minor versions are not necessary as only major versions are important to end users. Simplifying the API versioning is also needed for future work to determine the latest API version during builds. --- infrastructure/templates/template.json | 2 +- .../Controllers/DataSetVersionsController.cs | 2 +- .../Controllers/DataSetsController.cs | 2 +- .../Controllers/PublicationsController.cs | 2 +- src/explore-education-statistics-frontend/.env | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/templates/template.json b/infrastructure/templates/template.json index 2262edd6e97..001148a1e01 100644 --- a/infrastructure/templates/template.json +++ b/infrastructure/templates/template.json @@ -2114,7 +2114,7 @@ "NEXT_CONFIG_MODE": "server", "NODE_ENV": "production", "PUBLIC_URL": "[concat(variables('publicAppUrl'), '/')]", - "PUBLIC_API_BASE_URL": "[concat('https://', parameters('publicApiUrl'),'/api/v1.0')]", + "PUBLIC_API_BASE_URL": "[concat('https://', parameters('publicApiUrl'), '/api/v1')]", "PUBLIC_API_DOCS_URL": "[concat('https://', parameters('publicApiDocsUrl'))]", "WEBSITE_NODE_DEFAULT_VERSION": "20.16.0", "WEBSITES_PORT": 3000 diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs index dc46e2e74fb..ed5f01adad9 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs @@ -10,7 +10,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers; -[ApiVersion(1.0)] +[ApiVersion("1")] [ApiController] [Route("api/v{version:apiVersion}/data-sets/{dataSetId:guid}/versions")] public class DataSetVersionsController( diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs index ecd16d7cf40..f76c8442638 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs @@ -11,7 +11,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers; -[ApiVersion(1.0)] +[ApiVersion("1")] [ApiController] [Route("api/v{version:apiVersion}/data-sets")] public class DataSetsController( diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs index 8ee6cdd0eb7..7f87b103164 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs @@ -10,7 +10,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers; -[ApiVersion(1.0)] +[ApiVersion("1")] [ApiController] [Route("api/v{version:apiVersion}/publications")] public class PublicationsController(IPublicationService publicationService, IDataSetService dataSetService) diff --git a/src/explore-education-statistics-frontend/.env b/src/explore-education-statistics-frontend/.env index a2390fb9898..12c3f953ce4 100644 --- a/src/explore-education-statistics-frontend/.env +++ b/src/explore-education-statistics-frontend/.env @@ -1,6 +1,6 @@ CONTENT_API_BASE_URL=http://localhost:5010/api DATA_API_BASE_URL=http://localhost:5000/api -PUBLIC_API_BASE_URL=http://localhost:5050/api/v1.0 +PUBLIC_API_BASE_URL=http://localhost:5050/api/v1 PUBLIC_API_DOCS_URL=https://dev.statistics.api.education.gov.uk/docs NOTIFICATION_API_BASE_URL=http://localhost:7073/api GA_TRACKING_ID= From 5c64ab662b4b989b840cb500337b51f70cc4224b Mon Sep 17 00:00:00 2001 From: Nicholas Tsim Date: Fri, 15 Nov 2024 14:55:15 +0000 Subject: [PATCH 2/4] EES-5663 Remove `version` path parameters from OpenAPI documents --- .../VersionedPathsDocumentFilterTests.cs | 130 ++++++++++++++++++ .../Swagger/SwaggerConfig.cs | 3 + .../Swagger/VersionedPathsDocumentFilter.cs | 32 +++++ 3 files changed, 165 insertions(+) create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Swagger/VersionedPathsDocumentFilterTests.cs create mode 100644 src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/VersionedPathsDocumentFilter.cs diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Swagger/VersionedPathsDocumentFilterTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Swagger/VersionedPathsDocumentFilterTests.cs new file mode 100644 index 00000000000..6f8ae622251 --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Swagger/VersionedPathsDocumentFilterTests.cs @@ -0,0 +1,130 @@ +using System.Text.Json; +using GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Swagger; +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Swagger; + +public class VersionedPathsDocumentFilterTests +{ + [Fact] + public void VersionsInlinedIntoPaths() + { + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Version = "1" }, + Paths = new OpenApiPaths + { + {"/v{version}/endpoint-1", new OpenApiPathItem()}, + {"/v{version}/endpoint-2/{id}", new OpenApiPathItem()} + } + }; + + var filter = new VersionedPathsDocumentFilter(); + var context = CreateDocumentFilterContext(); + + filter.Apply(document, context); + + Assert.Equal(2, document.Paths.Count); + Assert.Contains("/v1/endpoint-1", document.Paths.Keys); + Assert.Contains("/v1/endpoint-2/{id}", document.Paths.Keys); + } + + [Fact] + public void VersionParametersRemoved() + { + var document = new OpenApiDocument + { + Info = new OpenApiInfo { Version = "1" }, + Paths = new OpenApiPaths + { + { + "/v{version}/endpoint-1", + new OpenApiPathItem + { + Parameters = + [ + new OpenApiParameter { Name = "version" } + ], + Operations = + { + { + OperationType.Get, + new OpenApiOperation + { + Parameters = + [ + new OpenApiParameter { Name = "version" } + ] + } + } + + } + } + }, + { + "/v{version}/endpoint-2/{id}", + new OpenApiPathItem + { + Parameters = + [ + new OpenApiParameter { Name = "version" }, + new OpenApiParameter { Name = "id" } + ], + Operations = + { + { + OperationType.Get, + new OpenApiOperation + { + Parameters = + [ + new OpenApiParameter { Name = "version" }, + new OpenApiParameter { Name = "id" } + ] + } + } + + } + } + } + } + }; + + var filter = new VersionedPathsDocumentFilter(); + var context = CreateDocumentFilterContext(); + + filter.Apply(document, context); + + Assert.Equal(2, document.Paths.Count); + + var endpoint1Paths = document.Paths["/v1/endpoint-1"]; + + Assert.Empty(endpoint1Paths.Parameters); + Assert.Empty(endpoint1Paths.Operations[OperationType.Get].Parameters); + + var endpoint2Paths = document.Paths["/v1/endpoint-2/{id}"]; + + Assert.Single(endpoint2Paths.Parameters); + Assert.Equal("id", endpoint2Paths.Parameters[0].Name); + + Assert.Single(endpoint2Paths.Operations[OperationType.Get].Parameters); + Assert.Equal("id", endpoint2Paths.Parameters[0].Name); + } + + private static DocumentFilterContext CreateDocumentFilterContext() + { + var schemaGenerator = new SchemaGenerator( + new SchemaGeneratorOptions(), + new JsonSerializerDataContractResolver( + new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + } + ) + ); + var schemaRepository = new SchemaRepository(); + + return new DocumentFilterContext([], schemaGenerator, schemaRepository); + } +} diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/SwaggerConfig.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/SwaggerConfig.cs index 8d00439bc1e..babd1bdec18 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/SwaggerConfig.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/SwaggerConfig.cs @@ -16,8 +16,11 @@ public class SwaggerConfig( { public void Configure(SwaggerGenOptions options) { + options.DocumentFilter(); + options.OperationFilter(); options.OperationFilter(); + options.SchemaFilter(); options.SchemaFilter(); options.SchemaFilter(); diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/VersionedPathsDocumentFilter.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/VersionedPathsDocumentFilter.cs new file mode 100644 index 00000000000..3e0b85e334a --- /dev/null +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Swagger/VersionedPathsDocumentFilter.cs @@ -0,0 +1,32 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Swagger; + +public class VersionedPathsDocumentFilter : IDocumentFilter +{ + public void Apply(OpenApiDocument document, DocumentFilterContext context) + { + var newPaths = new OpenApiPaths(); + + foreach (var path in document.Paths) + { + var versionedPath = path.Key.Replace("{version}", document.Info.Version); + + newPaths[versionedPath] = path.Value; + + path.Value.Parameters = path.Value.Parameters + .Where(p => p.Name != "version") + .ToList(); + + foreach (var operation in path.Value.Operations.Values) + { + operation.Parameters = operation.Parameters + .Where(p => p.Name != "version") + .ToList(); + } + } + + document.Paths = newPaths; + } +} From 6c0e642cf06c467f33bf7e787fb8ebcb9bfcc752 Mon Sep 17 00:00:00 2001 From: Nicholas Tsim Date: Fri, 15 Nov 2024 15:22:12 +0000 Subject: [PATCH 3/4] EES-5663 Prefix OpenAPI document version paths with `v` --- .../Program.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Program.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Program.cs index 214a54bb4fa..81854448f17 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Program.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Program.cs @@ -39,7 +39,7 @@ app.UseSwagger(options => { - options.RouteTemplate = "/swagger/{documentName}/openapi.json"; + options.RouteTemplate = "/swagger/v{documentName}/openapi.json"; }); app.UseSwaggerUI(options => { @@ -48,7 +48,7 @@ foreach (var description in app.DescribeApiVersions()) { options.SwaggerEndpoint( - url: $"/swagger/{description.GroupName}/openapi.json", + url: $"/swagger/v{description.GroupName}/openapi.json", name: $"v{description.GroupName}"); } }); From 0c204af8b8fe14ce4c6e978ba1c32a6995ab38ee Mon Sep 17 00:00:00 2001 From: Nicholas Tsim Date: Mon, 18 Nov 2024 02:58:54 +0000 Subject: [PATCH 4/4] EES-5663 Remove `/api` prefix from public API routes --- infrastructure/templates/template.json | 2 +- .../Services/Public.Data/PublicDataApiClient.cs | 2 +- .../Controllers/DataSetVersionsControllerTests.cs | 2 +- .../Controllers/DataSetsControllerGetQueryTests.cs | 2 +- .../Controllers/DataSetsControllerPostQueryTests.cs | 2 +- .../Controllers/DataSetsControllerTests.cs | 2 +- .../Controllers/PublicationsControllerTests.cs | 2 +- .../Controllers/DataSetVersionsController.cs | 2 +- .../Controllers/DataSetsController.cs | 2 +- .../Controllers/PublicationsController.cs | 2 +- src/explore-education-statistics-frontend/.env | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/infrastructure/templates/template.json b/infrastructure/templates/template.json index 001148a1e01..615d5ebfcca 100644 --- a/infrastructure/templates/template.json +++ b/infrastructure/templates/template.json @@ -2114,7 +2114,7 @@ "NEXT_CONFIG_MODE": "server", "NODE_ENV": "production", "PUBLIC_URL": "[concat(variables('publicAppUrl'), '/')]", - "PUBLIC_API_BASE_URL": "[concat('https://', parameters('publicApiUrl'), '/api/v1')]", + "PUBLIC_API_BASE_URL": "[concat('https://', parameters('publicApiUrl'), '/v1')]", "PUBLIC_API_DOCS_URL": "[concat('https://', parameters('publicApiDocsUrl'))]", "WEBSITE_NODE_DEFAULT_VERSION": "20.16.0", "WEBSITES_PORT": 3000 diff --git a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/Public.Data/PublicDataApiClient.cs b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/Public.Data/PublicDataApiClient.cs index f30928a4d95..7d98fb8687b 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/Public.Data/PublicDataApiClient.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Admin/Services/Public.Data/PublicDataApiClient.cs @@ -35,7 +35,7 @@ public async Task> GetDataSetVersionCh { return await SendRequest( () => httpClient.GetAsync( - $"api/v1/data-sets/{dataSetId}/versions/{dataSetVersion}/changes", + $"v1/data-sets/{dataSetId}/versions/{dataSetVersion}/changes", cancellationToken ), cancellationToken diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetVersionsControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetVersionsControllerTests.cs index 8e64f7b9d08..ba8fae60144 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetVersionsControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetVersionsControllerTests.cs @@ -20,7 +20,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Contr public abstract class DataSetVersionsControllerTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private const string BaseUrl = "api/v1/data-sets"; + private const string BaseUrl = "v1/data-sets"; public class ListDataSetVersionsTests(TestApplicationFactory testApp) : DataSetVersionsControllerTests(testApp) { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerGetQueryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerGetQueryTests.cs index 0a1287e3c12..b3f4e040f08 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerGetQueryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerGetQueryTests.cs @@ -23,7 +23,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Contr public abstract class DataSetsControllerGetQueryTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private const string BaseUrl = "api/v1/data-sets"; + private const string BaseUrl = "v1/data-sets"; private readonly TestDataSetVersionPathResolver _dataSetVersionPathResolver = new() { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerPostQueryTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerPostQueryTests.cs index f1547e63fe4..15cc2c59ed3 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerPostQueryTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerPostQueryTests.cs @@ -25,7 +25,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Contr public abstract class DataSetsControllerPostQueryTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private const string BaseUrl = "api/v1/data-sets"; + private const string BaseUrl = "v1/data-sets"; private readonly TestDataSetVersionPathResolver _dataSetVersionPathResolver = new() { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerTests.cs index 974d7a84797..4b9cb85467a 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/DataSetsControllerTests.cs @@ -25,7 +25,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Contr public abstract class DataSetsControllerTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private const string BaseUrl = "api/v1/data-sets"; + private const string BaseUrl = "v1/data-sets"; public class GetDataSetTests(TestApplicationFactory testApp) : DataSetsControllerTests(testApp) { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/PublicationsControllerTests.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/PublicationsControllerTests.cs index bc99ad83d1a..0101cd1e8d7 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/PublicationsControllerTests.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests/Controllers/PublicationsControllerTests.cs @@ -21,7 +21,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Tests.Contr public abstract class PublicationsControllerTests(TestApplicationFactory testApp) : IntegrationTestFixture(testApp) { - private const string BaseUrl = "api/v1/publications"; + private const string BaseUrl = "v1/publications"; public class ListPublicationsTests(TestApplicationFactory testApp) : PublicationsControllerTests(testApp) { diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs index ed5f01adad9..dcd5a61e06f 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetVersionsController.cs @@ -12,7 +12,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers [ApiVersion("1")] [ApiController] -[Route("api/v{version:apiVersion}/data-sets/{dataSetId:guid}/versions")] +[Route("v{version:apiVersion}/data-sets/{dataSetId:guid}/versions")] public class DataSetVersionsController( IDataSetService dataSetService, IDataSetVersionChangeService dataSetVersionChangeService) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs index f76c8442638..de99e4dd527 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/DataSetsController.cs @@ -13,7 +13,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers [ApiVersion("1")] [ApiController] -[Route("api/v{version:apiVersion}/data-sets")] +[Route("v{version:apiVersion}/data-sets")] public class DataSetsController( IDataSetService dataSetService, IDataSetQueryService dataSetQueryService) diff --git a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs index 7f87b103164..74d901d6163 100644 --- a/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs +++ b/src/GovUk.Education.ExploreEducationStatistics.Public.Data.Api/Controllers/PublicationsController.cs @@ -12,7 +12,7 @@ namespace GovUk.Education.ExploreEducationStatistics.Public.Data.Api.Controllers [ApiVersion("1")] [ApiController] -[Route("api/v{version:apiVersion}/publications")] +[Route("v{version:apiVersion}/publications")] public class PublicationsController(IPublicationService publicationService, IDataSetService dataSetService) : ControllerBase { diff --git a/src/explore-education-statistics-frontend/.env b/src/explore-education-statistics-frontend/.env index 12c3f953ce4..95c495d7605 100644 --- a/src/explore-education-statistics-frontend/.env +++ b/src/explore-education-statistics-frontend/.env @@ -1,6 +1,6 @@ CONTENT_API_BASE_URL=http://localhost:5010/api DATA_API_BASE_URL=http://localhost:5000/api -PUBLIC_API_BASE_URL=http://localhost:5050/api/v1 +PUBLIC_API_BASE_URL=http://localhost:5050/v1 PUBLIC_API_DOCS_URL=https://dev.statistics.api.education.gov.uk/docs NOTIFICATION_API_BASE_URL=http://localhost:7073/api GA_TRACKING_ID=