diff --git a/src/AddressRegistry.Api.Legacy/Address/AddressController.cs b/src/AddressRegistry.Api.Legacy/Address/AddressController.cs index 33a974fc4..b2eade150 100755 --- a/src/AddressRegistry.Api.Legacy/Address/AddressController.cs +++ b/src/AddressRegistry.Api.Legacy/Address/AddressController.cs @@ -142,6 +142,44 @@ public async Task Sync(CancellationToken cancellationToken = defa }; } + /// + /// Vraag een lijst met wijzigingen voor een adres op. + /// + /// De unieke identificator van een adres. + /// + /// + [HttpGet("sync/{objectId}")] + [Produces("text/xml")] + [ProducesResponseType(typeof(string), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] + [SwaggerResponseExample(StatusCodes.Status200OK, typeof(AddressSyndicationResponseExamples))] + [SwaggerResponseExample(StatusCodes.Status400BadRequest, typeof(BadRequestResponseExamples))] + [SwaggerResponseExample(StatusCodes.Status500InternalServerError, typeof(InternalServerErrorResponseExamples))] + public async Task Sync( + [FromRoute] int objectId, + CancellationToken cancellationToken = default) + { + var embedValue = Request.ExtractFilteringRequest()?.Filter?.Embed ?? new SyncEmbedValue(); + var filtering = new FilteringHeader(new AddressSyndicationPersistentLocalIdFilter + { + PersistentLocalId = objectId, + Embed = embedValue + }); + var sorting = Request.ExtractSortingRequest(); + var pagination = Request.ExtractPaginationRequest(); + + var result = + await _mediator.Send(new SyndicationByPersistentLocalIdRequest(filtering, sorting, pagination), cancellationToken); + + return new ContentResult + { + Content = result.Content, + ContentType = MediaTypeNames.Text.Xml, + StatusCode = StatusCodes.Status200OK + }; + } + /// /// Vraag een adres op. /// diff --git a/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationBaseHandler.cs b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationBaseHandler.cs new file mode 100644 index 000000000..6ca8fdbcd --- /dev/null +++ b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationBaseHandler.cs @@ -0,0 +1,74 @@ +namespace AddressRegistry.Api.Legacy.Address.Sync +{ + using System; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using System.Xml; + using Be.Vlaanderen.Basisregisters.Api.Search.Pagination; + using Be.Vlaanderen.Basisregisters.Api.Syndication; + using Be.Vlaanderen.Basisregisters.GrAr.Common; + using Infrastructure; + using Infrastructure.Options; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + using Microsoft.SyndicationFeed; + using Microsoft.SyndicationFeed.Atom; + + public class AddressSyndicationBaseHandler + { + private readonly IConfiguration _configuration; + private readonly IOptions _responseOptions; + + public AddressSyndicationBaseHandler(IConfiguration configuration, IOptions responseOptions) + { + _configuration = configuration; + _responseOptions = responseOptions; + } + + protected async Task BuildAtomFeed( + DateTimeOffset lastFeedUpdate, + PagedQueryable pagedAddresses) + { + var sw = new StringWriterWithEncoding(Encoding.UTF8); + + await using (var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings { Async = true, Indent = true, Encoding = sw.Encoding })) + { + var formatter = new AtomFormatter(null, xmlWriter.Settings) { UseCDATA = true }; + var writer = new AtomFeedWriter(xmlWriter, null, formatter); + var syndicationConfiguration = _configuration.GetSection("Syndication"); + var atomFeedConfig = AtomFeedConfigurationBuilder.CreateFrom(syndicationConfiguration, lastFeedUpdate); + + await writer.WriteDefaultMetadata(atomFeedConfig); + + var addresses = pagedAddresses.Items.ToList(); + + var nextFrom = addresses.Any() + ? addresses.Max(x => x.Position) + 1 + : (long?)null; + + var nextUri = BuildNextSyncUri(pagedAddresses.PaginationInfo.Limit, nextFrom, syndicationConfiguration["NextUri"]); + if (nextUri != null) + { + await writer.Write(new SyndicationLink(nextUri, "next")); + } + + foreach (var address in addresses) + { + await writer.WriteAddress(_responseOptions, formatter, syndicationConfiguration["Category"], address); + } + + xmlWriter.Flush(); + } + + return sw.ToString(); + } + + private static Uri BuildNextSyncUri(int limit, long? from, string nextUrlBase) + { + return from.HasValue + ? new Uri(string.Format(nextUrlBase, from, limit)) + : null; + } + } +} diff --git a/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationByPersistentLocalIdHandler.cs b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationByPersistentLocalIdHandler.cs new file mode 100644 index 000000000..ca0071a38 --- /dev/null +++ b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationByPersistentLocalIdHandler.cs @@ -0,0 +1,50 @@ +namespace AddressRegistry.Api.Legacy.Address.Sync +{ + using System; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using Be.Vlaanderen.Basisregisters.Api.Search.Filtering; + using Be.Vlaanderen.Basisregisters.Api.Search.Pagination; + using Be.Vlaanderen.Basisregisters.Api.Search.Sorting; + using Infrastructure.Options; + using MediatR; + using Microsoft.EntityFrameworkCore; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Options; + using Projections.Legacy; + + public record SyndicationByPersistentLocalIdRequest( + FilteringHeader Filtering, + SortingHeader Sorting, + IPaginationRequest Pagination) + : IRequest; + + public sealed class AddressSyndicationByPersistentLocalIdHandler : AddressSyndicationBaseHandler, IRequestHandler + { + private readonly LegacyContext _legacyContext; + + public AddressSyndicationByPersistentLocalIdHandler( + LegacyContext legacyContext, + IOptions responseOptions, + IConfiguration configuration) : base (configuration, responseOptions) + { + _legacyContext = legacyContext; + } + + public async Task Handle(SyndicationByPersistentLocalIdRequest request, CancellationToken cancellationToken) + { + var pagedAddresses = + new AddressSyndicationPersistentLocalIdQuery(_legacyContext, request.Filtering.Filter?.Embed) + .Fetch(request.Filtering, request.Sorting, request.Pagination); + + var lastUpdatedDateTime = pagedAddresses.Items + .ToList() + .Last() + .LastChangedOn + .ToDateTimeUtc(); + + return new SyndicationAtomContent(await BuildAtomFeed(lastUpdatedDateTime, pagedAddresses)); + } + } +} diff --git a/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationHandler.cs b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationHandler.cs index ade1d0d41..bbed8668c 100644 --- a/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationHandler.cs +++ b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationHandler.cs @@ -2,42 +2,35 @@ namespace AddressRegistry.Api.Legacy.Address.Sync { using System; using System.Linq; - using System.Text; using System.Threading; using System.Threading.Tasks; - using System.Xml; using Be.Vlaanderen.Basisregisters.Api.Search.Filtering; using Be.Vlaanderen.Basisregisters.Api.Search.Pagination; using Be.Vlaanderen.Basisregisters.Api.Search.Sorting; - using Be.Vlaanderen.Basisregisters.Api.Syndication; - using Be.Vlaanderen.Basisregisters.GrAr.Common; - using Infrastructure; using Infrastructure.Options; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; - using Microsoft.SyndicationFeed; - using Microsoft.SyndicationFeed.Atom; using Projections.Legacy; public sealed record SyndicationAtomContent(string Content); - public sealed record SyndicationRequest(FilteringHeader Filtering, SortingHeader Sorting, IPaginationRequest Pagination) : IRequest; + public record SyndicationRequest( + FilteringHeader Filtering, + SortingHeader Sorting, + IPaginationRequest Pagination) + : IRequest; - public sealed class AddressSyndicationHandler : IRequestHandler + public sealed class AddressSyndicationHandler : AddressSyndicationBaseHandler, IRequestHandler { private readonly LegacyContext _legacyContext; - private readonly IOptions _responseOptions; - private readonly IConfiguration _configuration; public AddressSyndicationHandler( LegacyContext legacyContext, IOptions responseOptions, - IConfiguration configuration) + IConfiguration configuration) : base (configuration, responseOptions) { _legacyContext = legacyContext; - _responseOptions = responseOptions; - _configuration = configuration; } public async Task Handle(SyndicationRequest request, CancellationToken cancellationToken) @@ -62,50 +55,5 @@ public async Task Handle(SyndicationRequest request, Can return new SyndicationAtomContent(await BuildAtomFeed(lastFeedUpdate, pagedAddresses)); } - - private async Task BuildAtomFeed( - DateTimeOffset lastFeedUpdate, - PagedQueryable pagedAddresses) - { - var sw = new StringWriterWithEncoding(Encoding.UTF8); - - await using (var xmlWriter = XmlWriter.Create(sw, new XmlWriterSettings { Async = true, Indent = true, Encoding = sw.Encoding })) - { - var formatter = new AtomFormatter(null, xmlWriter.Settings) { UseCDATA = true }; - var writer = new AtomFeedWriter(xmlWriter, null, formatter); - var syndicationConfiguration = _configuration.GetSection("Syndication"); - var atomFeedConfig = AtomFeedConfigurationBuilder.CreateFrom(syndicationConfiguration, lastFeedUpdate); - - await writer.WriteDefaultMetadata(atomFeedConfig); - - var addresses = pagedAddresses.Items.ToList(); - - var nextFrom = addresses.Any() - ? addresses.Max(x => x.Position) + 1 - : (long?)null; - - var nextUri = BuildNextSyncUri(pagedAddresses.PaginationInfo.Limit, nextFrom, syndicationConfiguration["NextUri"]); - if (nextUri != null) - { - await writer.Write(new SyndicationLink(nextUri, "next")); - } - - foreach (var address in addresses) - { - await writer.WriteAddress(_responseOptions, formatter, syndicationConfiguration["Category"], address); - } - - xmlWriter.Flush(); - } - - return sw.ToString(); - } - - private static Uri BuildNextSyncUri(int limit, long? from, string nextUrlBase) - { - return from.HasValue - ? new Uri(string.Format(nextUrlBase, from, limit)) - : null; - } } } diff --git a/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationPersistentLocalIdQuery.cs b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationPersistentLocalIdQuery.cs new file mode 100644 index 000000000..067036531 --- /dev/null +++ b/src/AddressRegistry.Api.Legacy/Address/Sync/AddressSyndicationPersistentLocalIdQuery.cs @@ -0,0 +1,137 @@ +namespace AddressRegistry.Api.Legacy.Address.Sync +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Linq.Expressions; + using AddressRegistry.Address; + using AddressRegistry.Projections.Legacy; + using AddressRegistry.Projections.Legacy.AddressSyndication; + using Be.Vlaanderen.Basisregisters.Api.Search; + using Be.Vlaanderen.Basisregisters.Api.Search.Filtering; + using Be.Vlaanderen.Basisregisters.Api.Search.Sorting; + using Be.Vlaanderen.Basisregisters.GrAr.Provenance; + using Microsoft.EntityFrameworkCore; + using NodaTime; + using StreetName; + + public class AddressSyndicationPersistentLocalIdQuery : Query + { + private readonly LegacyContext _context; + private readonly bool _embedEvent; + private readonly bool _embedObject; + + public AddressSyndicationPersistentLocalIdQuery(LegacyContext context, SyncEmbedValue? embed) + { + _context = context; + _embedEvent = embed?.Event ?? false; + _embedObject = embed?.Object ?? false; + } + + protected override ISorting Sorting => new AddressSyndicationPersistentLocalIdSorting(); + + protected override Expression> Transformation + { + get + { + if (_embedEvent && _embedObject) + return x => new AddressSyndicationQueryResult( + x.AddressId.Value, + x.Position, + x.StreetNamePersistentLocalId, + x.PersistentLocalId, + x.HouseNumber, + x.BoxNumber, + x.StreetNameId, + x.PostalCode, + x.PointPosition, + x.PositionMethod, + x.PositionSpecification, + x.ChangeType, + x.RecordCreatedAt, + x.LastChangedOn, + x.IsComplete, + x.IsOfficiallyAssigned, + x.Status, + x.Organisation, + x.Reason, + x.EventDataAsXml); + + if (_embedEvent) + return x => new AddressSyndicationQueryResult( + x.AddressId.Value, + x.Position, + x.StreetNamePersistentLocalId, + x.PersistentLocalId, + x.ChangeType, + x.RecordCreatedAt, + x.LastChangedOn, + x.IsComplete, + x.Organisation, + x.Reason, + x.EventDataAsXml); + + if (_embedObject) + return x => new AddressSyndicationQueryResult( + x.AddressId.Value, + x.Position, + x.StreetNamePersistentLocalId, + x.PersistentLocalId, + x.HouseNumber, + x.BoxNumber, + x.StreetNameId, + x.PostalCode, + x.PointPosition, + x.PositionMethod, + x.PositionSpecification, + x.ChangeType, + x.RecordCreatedAt, + x.LastChangedOn, + x.IsComplete, + x.IsOfficiallyAssigned, + x.Status, + x.Organisation, + x.Reason); + + return x => new AddressSyndicationQueryResult( + x.AddressId.Value, + x.Position, + x.StreetNamePersistentLocalId, + x.PersistentLocalId, + x.ChangeType, + x.RecordCreatedAt, + x.LastChangedOn, + x.IsComplete, + x.Organisation, + x.Reason); + } + } + + protected override IQueryable Filter(FilteringHeader filtering) + { + var addressSyndicationItems = _context + .AddressSyndication + .Where(x => x.PersistentLocalId == filtering.Filter.PersistentLocalId) + .OrderBy(x => x.Position) + .AsNoTracking(); + + return addressSyndicationItems; + } + } + + internal class AddressSyndicationPersistentLocalIdSorting : ISorting + { + public IEnumerable SortableFields { get; } = new[] + { + nameof(AddressSyndicationItem.Position) + }; + + public SortingHeader DefaultSortingHeader { get; } = new SortingHeader(nameof(AddressSyndicationItem.Position), SortOrder.Ascending); + } + + public class AddressSyndicationPersistentLocalIdFilter + { + public int? PersistentLocalId { get; set; } + public SyncEmbedValue Embed { get; set; } + } +}