From 30cfbce9bf4007feb0a771986756d2f60b191d71 Mon Sep 17 00:00:00 2001 From: Rik De Peuter Date: Tue, 29 Oct 2024 18:04:47 +0100 Subject: [PATCH 1/5] feat: add sync from municipality registry --- RoadRegistry.sln | 15 ++++ .../Core/IMunicipalities.cs | 9 ++ .../Core/Municipalities.cs | 26 ++++++ .../Core/Municipality.cs | 24 +++++ .../MunicipalityEventWriter.cs | 25 ++++++ .../MunicipalityNisCode.cs | 37 ++++++++ .../OrganizationEventWriter.cs | 13 +-- .../RoadRegistryEventWriter.cs | 9 ++ .../WellKnownConnectionNames.cs | 2 + .../WellKnownSchemas.cs | 2 + .../20241029124520_Initial.Designer.cs | 48 ++++++++++ .../Migrations/20241029124520_Initial.cs | 46 ++++++++++ ...palityEventConsumerContextModelSnapshot.cs | 45 ++++++++++ .../MunicipalityEventConsumerContext.cs | 64 +++++++++++++ .../MunicipalityEventProjection.cs | 35 ++++++++ ...dRegistry.Sync.MunicipalityRegistry.csproj | 23 +++++ .../ef.sh | 1 + .../paket.references | 11 +++ .../Infrastructure/KafkaOptions.cs | 1 + .../Modules/MunicipalityConsumerModule.cs | 33 +++++++ .../Infrastructure/Program.cs | 26 +++--- .../Municipality/MunicipalityEventConsumer.cs | 84 +++++++++++++++++ .../MunicipalityEventTopicConsumer.cs | 90 +++++++++++++++++++ .../RoadRegistry.SyncHost.csproj | 1 + .../KafkaTopicConsumerByFile.cs | 0 .../StreetNameEventConsumer.cs | 0 ...ameEventProjectionContextEventProcessor.cs | 0 .../StreetName/StreetNameEventTopic.json | 8 ++ .../StreetNameEventTopicConsumer.cs | 0 .../StreetNameEventTopicConsumerByFile.cs | 0 .../StreetNameSnapshotConsumer.cs | 0 ...SnapshotProjectionContextEventProcessor.cs | 0 .../StreetNameSnapshotTopicConsumer.cs | 0 .../StreetNameSnapshotTopicConsumerByFile.cs | 0 .../appsettings.development.json | 11 ++- src/RoadRegistry.SyncHost/appsettings.json | 5 +- 36 files changed, 671 insertions(+), 23 deletions(-) create mode 100644 src/RoadRegistry.BackOffice/Core/IMunicipalities.cs create mode 100644 src/RoadRegistry.BackOffice/Core/Municipalities.cs create mode 100644 src/RoadRegistry.BackOffice/Core/Municipality.cs create mode 100644 src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs create mode 100644 src/RoadRegistry.BackOffice/MunicipalityNisCode.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/ef.sh create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/paket.references create mode 100644 src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs create mode 100644 src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs create mode 100644 src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs rename src/RoadRegistry.SyncHost/{ => StreetName}/KafkaTopicConsumerByFile.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameEventConsumer.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameEventProjectionContextEventProcessor.cs (100%) create mode 100644 src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameEventTopicConsumer.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameEventTopicConsumerByFile.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameSnapshotConsumer.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameSnapshotProjectionContextEventProcessor.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameSnapshotTopicConsumer.cs (100%) rename src/RoadRegistry.SyncHost/{ => StreetName}/StreetNameSnapshotTopicConsumerByFile.cs (100%) diff --git a/RoadRegistry.sln b/RoadRegistry.sln index 7e830ee48..06ded15a7 100644 --- a/RoadRegistry.sln +++ b/RoadRegistry.sln @@ -150,6 +150,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoadRegistry.Integration.Pr EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoadRegistry.Snapshot.Handlers.Sqs.Lambda.Console", "test\RoadRegistry.Snapshot.Handlers.Sqs.Lambda.Console\RoadRegistry.Snapshot.Handlers.Sqs.Lambda.Console.csproj", "{18024416-CD26-433B-B34B-2B248A57539C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoadRegistry.Sync.MunicipalityRegistry", "src\RoadRegistry.Sync.MunicipalityRegistry\RoadRegistry.Sync.MunicipalityRegistry.csproj", "{EF98CA87-B1AD-448D-A708-0E329FB532B2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -916,6 +918,18 @@ Global {18024416-CD26-433B-B34B-2B248A57539C}.Release|x64.Build.0 = Release|Any CPU {18024416-CD26-433B-B34B-2B248A57539C}.Release|x86.ActiveCfg = Release|Any CPU {18024416-CD26-433B-B34B-2B248A57539C}.Release|x86.Build.0 = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|x64.ActiveCfg = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|x64.Build.0 = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|x86.ActiveCfg = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Debug|x86.Build.0 = Debug|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|Any CPU.Build.0 = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|x64.ActiveCfg = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|x64.Build.0 = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|x86.ActiveCfg = Release|Any CPU + {EF98CA87-B1AD-448D-A708-0E329FB532B2}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -984,6 +998,7 @@ Global {2345054A-9FEC-4930-A278-49526BB73CF6} = {C2F8FF63-7A48-4179-A720-86206C42F496} {8BBCF59D-7A4F-48A3-B8B2-5BD0C5CE46A8} = {8EA18457-86E2-4D2D-AC9E-2552FDC9676F} {18024416-CD26-433B-B34B-2B248A57539C} = {8EA18457-86E2-4D2D-AC9E-2552FDC9676F} + {EF98CA87-B1AD-448D-A708-0E329FB532B2} = {C2F8FF63-7A48-4179-A720-86206C42F496} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {2EB87445-E263-4E1E-89CC-3839170028E5} diff --git a/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs b/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs new file mode 100644 index 000000000..eea4e862e --- /dev/null +++ b/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs @@ -0,0 +1,9 @@ +namespace RoadRegistry.BackOffice.Core; + +using System.Threading; +using System.Threading.Tasks; + +public interface IMunicipalities +{ + Task FindAsync(MunicipalityNisCode id, CancellationToken ct = default); +} diff --git a/src/RoadRegistry.BackOffice/Core/Municipalities.cs b/src/RoadRegistry.BackOffice/Core/Municipalities.cs new file mode 100644 index 000000000..fb944dc0c --- /dev/null +++ b/src/RoadRegistry.BackOffice/Core/Municipalities.cs @@ -0,0 +1,26 @@ +namespace RoadRegistry.BackOffice.Core; + +using Be.Vlaanderen.Basisregisters.EventHandling; +using Framework; +using Newtonsoft.Json; +using SqlStreamStore; +using System; + +public class Municipalities : EventSourcedEntityRepository, IMunicipalities +{ + public static readonly Func ToStreamName = instance => + new StreamName(instance.ToString()).WithPrefix("municipality-"); + + public Municipalities(EventSourcedEntityMap map, IStreamStore store, JsonSerializerSettings settings, EventMapping mapping) + : base(map, store, settings, mapping, + ToStreamName, + Municipality.Factory + ) + { + } + + protected override Municipality ConvertEntity(Municipality entity) + { + return entity.IsRemoved ? null : entity; + } +} diff --git a/src/RoadRegistry.BackOffice/Core/Municipality.cs b/src/RoadRegistry.BackOffice/Core/Municipality.cs new file mode 100644 index 000000000..5670de240 --- /dev/null +++ b/src/RoadRegistry.BackOffice/Core/Municipality.cs @@ -0,0 +1,24 @@ +namespace RoadRegistry.BackOffice.Core; + +using System; +using Framework; +using Messages; + +public class Municipality : EventSourcedEntity +{ + public static readonly Func Factory = () => new Municipality(); + + private Municipality() + { + On(e => + { + DutchName = e.DutchName; + Geometry = e.Geometry; + }); + } + + public string DutchName { get; private set; } + public MunicipalityGeometry Geometry { get; set; } + + public bool IsRemoved { get; private set; } +} diff --git a/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs b/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs new file mode 100644 index 000000000..3208bacb3 --- /dev/null +++ b/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs @@ -0,0 +1,25 @@ +namespace RoadRegistry.BackOffice; + +using System; +using System.Threading; +using System.Threading.Tasks; +using Framework; +using SqlStreamStore; + +public interface IMunicipalityEventWriter +{ + Task WriteAsync(StreamName streamName, int expectedVersion, Guid messageId, object[] events, CancellationToken cancellationToken); +} + +public class MunicipalityEventWriter : RoadRegistryEventWriter, IMunicipalityEventWriter +{ + public MunicipalityEventWriter(IStreamStore store, EventEnricher enricher) + : base(store, enricher) + { + } + + public Task WriteAsync(StreamName streamName, int expectedVersion, Guid messageId, object[] events, CancellationToken cancellationToken) + { + return AppendToStoreStream(streamName, messageId, expectedVersion, events, null, null, cancellationToken); + } +} diff --git a/src/RoadRegistry.BackOffice/MunicipalityNisCode.cs b/src/RoadRegistry.BackOffice/MunicipalityNisCode.cs new file mode 100644 index 000000000..28ae2553d --- /dev/null +++ b/src/RoadRegistry.BackOffice/MunicipalityNisCode.cs @@ -0,0 +1,37 @@ +namespace RoadRegistry.BackOffice; + +using System; +using Extensions; +using Framework; + +public readonly struct MunicipalityNisCode +{ + private readonly string _value; + + public MunicipalityNisCode(string value) + { + ArgumentNullException.ThrowIfNull(value); + + if (!AcceptsValue(value)) + { + throw new ArgumentException("The value is not a well known nis-code", nameof(value)); + } + + _value = value; + } + + public static bool AcceptsValue(string value) + { + return !string.IsNullOrEmpty(value) && !value.ContainsWhitespace() && value.Length == 5; + } + + public override string ToString() + { + return _value; + } + + public static implicit operator string(MunicipalityNisCode instance) + { + return instance.ToString(); + } +} diff --git a/src/RoadRegistry.BackOffice/OrganizationEventWriter.cs b/src/RoadRegistry.BackOffice/OrganizationEventWriter.cs index 0f32f0e6c..69fdb8e0e 100644 --- a/src/RoadRegistry.BackOffice/OrganizationEventWriter.cs +++ b/src/RoadRegistry.BackOffice/OrganizationEventWriter.cs @@ -1,12 +1,10 @@ namespace RoadRegistry.BackOffice; -using Be.Vlaanderen.Basisregisters.EventHandling; +using System.Threading; +using System.Threading.Tasks; using Framework; -using Messages; using SqlStreamStore; using SqlStreamStore.Streams; -using System.Threading; -using System.Threading.Tasks; public interface IOrganizationEventWriter { @@ -15,16 +13,13 @@ public interface IOrganizationEventWriter public class OrganizationEventWriter : RoadRegistryEventWriter, IOrganizationEventWriter { - private static readonly EventMapping EventMapping = - new(EventMapping.DiscoverEventNamesInAssembly(typeof(RoadNetworkEvents).Assembly)); - public OrganizationEventWriter(IStreamStore store, EventEnricher enricher) - : base(store, enricher, EventMapping) + : base(store, enricher) { } public Task WriteAsync(OrganizationId id, Event @event, CancellationToken cancellationToken) { - return AppendToStoreStream(OrganizationId.ToStreamName(id), @event, ExpectedVersion.Any, new[] { @event.Body }, cancellationToken); + return AppendToStoreStream(OrganizationId.ToStreamName(id), @event, ExpectedVersion.Any, [@event.Body], cancellationToken); } } diff --git a/src/RoadRegistry.BackOffice/RoadRegistryEventWriter.cs b/src/RoadRegistry.BackOffice/RoadRegistryEventWriter.cs index ffb82206c..3979cb684 100644 --- a/src/RoadRegistry.BackOffice/RoadRegistryEventWriter.cs +++ b/src/RoadRegistry.BackOffice/RoadRegistryEventWriter.cs @@ -9,6 +9,7 @@ namespace RoadRegistry.BackOffice; using Be.Vlaanderen.Basisregisters.Generators.Guid; using Be.Vlaanderen.Basisregisters.GrAr.Provenance; using Framework; +using Messages; using Newtonsoft.Json; using SqlStreamStore.Streams; using SqlStreamStore; @@ -19,10 +20,18 @@ public abstract class RoadRegistryEventWriter protected static readonly JsonSerializerSettings SerializerSettings = EventsJsonSerializerSettingsProvider.CreateSerializerSettings(); + private static readonly EventMapping EventMapping = + new(EventMapping.DiscoverEventNamesInAssembly(typeof(RoadNetworkEvents).Assembly)); + private readonly EventEnricher _enricher; private readonly IStreamStore _store; private readonly EventMapping _eventMapping; + protected RoadRegistryEventWriter(IStreamStore store, EventEnricher enricher) + : this(store, enricher, EventMapping) + { + } + protected RoadRegistryEventWriter(IStreamStore store, EventEnricher enricher, EventMapping eventMapping) { _store = store.ThrowIfNull(); diff --git a/src/RoadRegistry.BackOffice/WellKnownConnectionNames.cs b/src/RoadRegistry.BackOffice/WellKnownConnectionNames.cs index 64ca314a0..b04441c24 100644 --- a/src/RoadRegistry.BackOffice/WellKnownConnectionNames.cs +++ b/src/RoadRegistry.BackOffice/WellKnownConnectionNames.cs @@ -31,4 +31,6 @@ public static class WellKnownConnectionNames public const string StreetNameSnapshotConsumerAdmin = "StreetNameSnapshotConsumerAdmin"; public const string OrganizationConsumerProjections = "OrganizationConsumerProjections"; public const string OrganizationConsumerProjectionsAdmin = "OrganizationConsumerProjectionsAdmin"; + public const string MunicipalityEventConsumer = "MunicipalityEventConsumer"; + public const string MunicipalityEventConsumerAdmin = "MunicipalityEventConsumerAdmin"; } diff --git a/src/RoadRegistry.BackOffice/WellKnownSchemas.cs b/src/RoadRegistry.BackOffice/WellKnownSchemas.cs index 0261a303e..c3cdb342e 100644 --- a/src/RoadRegistry.BackOffice/WellKnownSchemas.cs +++ b/src/RoadRegistry.BackOffice/WellKnownSchemas.cs @@ -23,6 +23,7 @@ public static class WellKnownSchemas public const string StreetNameEventConsumerSchema = "RoadRegistryStreetNameEventConsumer"; public const string StreetNameSnapshotConsumerSchema = "RoadRegistryStreetNameSnapshotConsumer"; public const string OrganizationConsumerSchema = "RoadRegistryOrganizationConsumer"; + public const string MunicipalityEventConsumerSchema = "RoadRegistryMunicipalityEventConsumer"; public const string RoadNodeProducerSnapshotMetaSchema = "RoadRegistryRoadNodeProducerSnapshotMeta"; public const string RoadNodeProducerSnapshotSchema = "RoadRegistryRoadNodeProducerSnapshot"; @@ -50,6 +51,7 @@ public static class MigrationTables public const string StreetNameEventConsumer = "__EFMigrationsHistoryStreetNameEventConsumer"; public const string StreetNameSnapshotConsumer = "__EFMigrationsHistoryStreetNameSnapshotConsumer"; public const string OrganizationConsumer = "__EFMigrationsHistoryOrganizationConsumer"; + public const string MunicipalityEventConsumer = "__EFMigrationsHistoryMunicipalityEventConsumer"; public const string RoadNodeProducerSnapshot = "__EFMigrationsHistoryRoadNodeProducerSnapshot"; public const string RoadSegmentProducerSnapshot = "__EFMigrationsHistoryRoadSegmentProducerSnapshot"; diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs new file mode 100644 index 000000000..740711aaa --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs @@ -0,0 +1,48 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RoadRegistry.Sync.MunicipalityRegistry; + +#nullable disable + +namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations +{ + [DbContext(typeof(MunicipalityEventConsumerContext))] + [Migration("20241029124520_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.ProcessedMessage", b => + { + b.Property("IdempotenceKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("DateProcessed") + .HasColumnType("datetimeoffset"); + + b.HasKey("IdempotenceKey"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("IdempotenceKey")); + + b.HasIndex("DateProcessed"); + + b.ToTable("ProcessedMessages", "RoadRegistryMunicipalityEventConsumer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs new file mode 100644 index 000000000..89e7dfc30 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs @@ -0,0 +1,46 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.EnsureSchema( + name: "RoadRegistryMunicipalityEventConsumer"); + + migrationBuilder.CreateTable( + name: "ProcessedMessages", + schema: "RoadRegistryMunicipalityEventConsumer", + columns: table => new + { + IdempotenceKey = table.Column(type: "nvarchar(128)", maxLength: 128, nullable: false), + DateProcessed = table.Column(type: "datetimeoffset", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ProcessedMessages", x => x.IdempotenceKey) + .Annotation("SqlServer:Clustered", true); + }); + + migrationBuilder.CreateIndex( + name: "IX_ProcessedMessages_DateProcessed", + schema: "RoadRegistryMunicipalityEventConsumer", + table: "ProcessedMessages", + column: "DateProcessed"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ProcessedMessages", + schema: "RoadRegistryMunicipalityEventConsumer"); + } + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs new file mode 100644 index 000000000..12a080646 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs @@ -0,0 +1,45 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RoadRegistry.Sync.MunicipalityRegistry; + +#nullable disable + +namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations +{ + [DbContext(typeof(MunicipalityEventConsumerContext))] + partial class MunicipalityEventConsumerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.ProcessedMessage", b => + { + b.Property("IdempotenceKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("DateProcessed") + .HasColumnType("datetimeoffset"); + + b.HasKey("IdempotenceKey"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("IdempotenceKey")); + + b.HasIndex("DateProcessed"); + + b.ToTable("ProcessedMessages", "RoadRegistryMunicipalityEventConsumer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs new file mode 100644 index 000000000..654d17b14 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs @@ -0,0 +1,64 @@ +namespace RoadRegistry.Sync.MunicipalityRegistry; + +using System; +using BackOffice; +using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +public class MunicipalityEventConsumerContext : ConsumerDbContext +{ + private const string ConsumerSchema = WellKnownSchemas.MunicipalityEventConsumerSchema; + + public MunicipalityEventConsumerContext() + { + } + + // This needs to be DbContextOptions for Autofac! + public MunicipalityEventConsumerContext(DbContextOptions options) + : base(options) + { + } + + protected override void OnConfiguringOptionsBuilder(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseRoadRegistryInMemorySqlServer(); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ApplyConfiguration(new ProcessedMessageConfiguration(ConsumerSchema)); + } + + public static void ConfigureOptions(IServiceProvider sp, DbContextOptionsBuilder options) + { + options + .UseLoggerFactory(sp.GetService()) + .UseSqlServer( + sp.GetRequiredService().GetRequiredConnectionString(WellKnownConnectionNames.MunicipalityEventConsumer), + sqlOptions => sqlOptions + .EnableRetryOnFailure() + .MigrationsHistoryTable(MigrationTables.MunicipalityEventConsumer, WellKnownSchemas.MunicipalityEventConsumerSchema)); + } +} + +public class MunicipalityEventConsumerContextMigrationFactory : DbContextMigratorFactory +{ + public MunicipalityEventConsumerContextMigrationFactory() + : base(WellKnownConnectionNames.MunicipalityEventConsumerAdmin, new MigrationHistoryConfiguration + { + Schema = WellKnownSchemas.MunicipalityEventConsumerSchema, + Table = MigrationTables.MunicipalityEventConsumer + }) + { + } + + protected override MunicipalityEventConsumerContext CreateContext(DbContextOptions migrationContextOptions) + { + return new MunicipalityEventConsumerContext(migrationContextOptions); + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs new file mode 100644 index 000000000..e3034de72 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs @@ -0,0 +1,35 @@ +namespace RoadRegistry.Sync.MunicipalityRegistry; + +using System; +using System.Threading; +using System.Threading.Tasks; +using BackOffice.Core; +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; + +public class MunicipalityEventProjection : ConnectedProjection +{ + private readonly IMunicipalities _municipalities; + + public MunicipalityEventProjection(IMunicipalities municipalities) + { + _municipalities = municipalities.ThrowIfNull(); + + When(MunicipalityWasRegistered); + } + + private async Task MunicipalityWasRegistered(MunicipalityEventConsumerContext context, MunicipalityWasRegistered envelope, CancellationToken token) + { + //TODO-rik implement muni events + + // var municipality = await _municipalities.FindAsync(nisCode, cancellationToken); + // if (municipality is null) + // { + // + // } + // else + // { + // + // } + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj b/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj new file mode 100644 index 000000000..89a1288ee --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj @@ -0,0 +1,23 @@ + + + + + RoadRegistry.Sync.MunicipalityRegistry + AnyCPU;x64;x86 + disable + false + false + + + + + + + + + <_Parameter1>RoadRegistry.SyncHost + + + + + diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/ef.sh b/src/RoadRegistry.Sync.MunicipalityRegistry/ef.sh new file mode 100644 index 000000000..28af4d247 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/ef.sh @@ -0,0 +1 @@ +dotnet ef --startup-project ../RoadRegistry.SyncHost "$@" diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/paket.references b/src/RoadRegistry.Sync.MunicipalityRegistry/paket.references new file mode 100644 index 000000000..b2d25210e --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/paket.references @@ -0,0 +1,11 @@ +Be.Vlaanderen.Basisregisters.EventHandling +Be.Vlaanderen.Basisregisters.EventHandling.Autofac +Be.Vlaanderen.Basisregisters.GrAr.Contracts +Be.Vlaanderen.Basisregisters.GrAr.Provenance +Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.SqlServer +Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector +Be.Vlaanderen.Basisregisters.ProjectionHandling.Runner +Be.Vlaanderen.Basisregisters.Sqs +MediatR +Microsoft.EntityFrameworkCore.Design +Microsoft.Extensions.DependencyInjection.Abstractions diff --git a/src/RoadRegistry.SyncHost/Infrastructure/KafkaOptions.cs b/src/RoadRegistry.SyncHost/Infrastructure/KafkaOptions.cs index acf964beb..d080e0617 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/KafkaOptions.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/KafkaOptions.cs @@ -19,6 +19,7 @@ public class KafkaConsumers { public KafkaConsumer StreetNameEvent { get; private set; } public KafkaConsumer StreetNameSnapshot { get; private set; } + public KafkaConsumer MunicipalityEvent { get; private set; } } public class KafkaConsumer diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs new file mode 100644 index 000000000..b50e82eb0 --- /dev/null +++ b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs @@ -0,0 +1,33 @@ +namespace RoadRegistry.SyncHost.Infrastructure.Modules +{ + using BackOffice; + using Microsoft.Extensions.DependencyInjection; + using Sync.MunicipalityRegistry; + + public static class MunicipalityConsumerModule + { + public static IServiceCollection AddMunicipalityConsumerServices(this IServiceCollection services) + { + return services + .AddSingleton() + + .AddSingleton() + .AddSingleton(sp => + { + //var options = sp.GetRequiredService(); + //var jsonPath = options.Consumers.MunicipalityEvent.JsonPath; + // if (!string.IsNullOrEmpty(jsonPath)) + // { + // // for local testing purposes + // return new MunicipalityEventTopicConsumerByFile(sp.GetRequiredService>(), jsonPath, sp.GetRequiredService>()); + // } + + return sp.GetRequiredService(); + }) + .AddSingleton>(MunicipalityEventConsumerContext.ConfigureOptions) + .AddDbContext((sp, options) => sp.GetRequiredService>()(sp, options)) + .AddDbContextFactory((sp, options) => sp.GetRequiredService>()(sp, options)) + ; + } + } +} diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Program.cs b/src/RoadRegistry.SyncHost/Infrastructure/Program.cs index dd89377d4..2741397ae 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Program.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Program.cs @@ -14,6 +14,7 @@ namespace RoadRegistry.SyncHost.Infrastructure; using Sync.StreetNameRegistry; using System.Threading; using System.Threading.Tasks; +using Sync.MunicipalityRegistry; public class Program { @@ -37,16 +38,20 @@ public static async Task Main(string[] args) .AddEditorContext() .RegisterOptions() - .AddOrganizationConsumerServices() - .AddHostedService() + //TODO-rik temp disable + // .AddOrganizationConsumerServices() + // .AddHostedService() + // + // .AddStreetNameConsumerServices() + // .AddHostedService() + // .AddHostedService() + // + // .AddStreetNameProjectionServices() + // .AddHostedService() + // .AddHostedService() - .AddStreetNameConsumerServices() - .AddHostedService() - .AddHostedService() - - .AddStreetNameProjectionServices() - .AddHostedService() - .AddHostedService() + .AddMunicipalityConsumerServices() + .AddHostedService() .AddSingleton(new IDbContextMigratorFactory[] { @@ -54,7 +59,8 @@ public static async Task Main(string[] args) new StreetNameEventConsumerContextMigrationFactory(), new StreetNameEventProjectionContextMigrationFactory(), new StreetNameSnapshotConsumerContextMigrationFactory(), - new StreetNameSnapshotProjectionContextMigrationFactory() + new StreetNameSnapshotProjectionContextMigrationFactory(), + new MunicipalityEventConsumerContextMigrationFactory() }) ; }) diff --git a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs new file mode 100644 index 000000000..62644d1c6 --- /dev/null +++ b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs @@ -0,0 +1,84 @@ +namespace RoadRegistry.SyncHost; + +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Autofac; +using BackOffice; +using BackOffice.Core; +using BackOffice.Framework; +using BackOffice.Messages; +using Be.Vlaanderen.Basisregisters.EventHandling; +using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; +using Hosts; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using SqlStreamStore; +using Sync.MunicipalityRegistry; +using Resolve = Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector.Resolve; + +public class MunicipalityEventConsumer : RoadRegistryBackgroundService +{ + private readonly IDbContextFactory _municipalityEventConsumerDbContextFactory; + private readonly ILifetimeScope _container; + private readonly IStreamStore _store; + private readonly IMunicipalityEventWriter _eventWriter; + private readonly IMunicipalityEventTopicConsumer _consumer; + + private static readonly EventMapping EventMapping = + new(EventMapping.DiscoverEventNamesInAssembly(typeof(RoadNetworkEvents).Assembly)); + + private static readonly JsonSerializerSettings SerializerSettings = + EventsJsonSerializerSettingsProvider.CreateSerializerSettings(); + + public MunicipalityEventConsumer( + ILifetimeScope container, + IDbContextFactory municipalityEventConsumerDbContextFactory, + IStreamStore store, + IMunicipalityEventWriter eventWriter, + IMunicipalityEventTopicConsumer consumer, + ILogger logger + ) : base(logger) + { + _container = container.ThrowIfNull(); + _municipalityEventConsumerDbContextFactory = municipalityEventConsumerDbContextFactory.ThrowIfNull(); + _store = store.ThrowIfNull(); + _eventWriter = eventWriter.ThrowIfNull(); + _consumer = consumer.ThrowIfNull(); + } + + protected override async Task ExecutingAsync(CancellationToken cancellationToken) + { + await _consumer.ConsumeContinuously(async (message, dbContext) => + { + Logger.LogInformation("Processing {Type}", message.GetType().Name); + + var map = _container.Resolve(); + var municipalities = new Municipalities(map, _store, SerializerSettings, EventMapping); + + var projector = + new ConnectedProjector( + Resolve.WhenEqualToHandlerMessageType(new MunicipalityEventProjection(municipalities).Handlers)); + + await projector.ProjectAsync(dbContext, message, cancellationToken).ConfigureAwait(false); + + var messageId = Guid.NewGuid(); //TODO-rik van waar messageid halen? + + foreach (var entry in map.Entries) + { + var events = entry.Entity.TakeEvents(); + if (events.Any()) + { + await _eventWriter.WriteAsync(entry.Stream, entry.ExpectedVersion, messageId, events, cancellationToken); + } + } + + //CancellationToken.None to prevent halfway consumption + await dbContext.SaveChangesAsync(CancellationToken.None); + + Logger.LogInformation("Processed {Type}", message.GetType().Name); + }, cancellationToken); + } +} diff --git a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs new file mode 100644 index 000000000..2a0ea272b --- /dev/null +++ b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs @@ -0,0 +1,90 @@ +namespace RoadRegistry.SyncHost; + +using Be.Vlaanderen.Basisregisters.EventHandling; +using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka; +using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; +using Infrastructure; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Sync.MunicipalityRegistry; +using System; +using System.Configuration; +using System.Threading; +using System.Threading.Tasks; + +public interface IMunicipalityEventTopicConsumer +{ + Task ConsumeContinuously(Func messageHandler, CancellationToken cancellationToken); +} + +public class MunicipalityEventTopicConsumer : IMunicipalityEventTopicConsumer +{ + private readonly KafkaOptions _options; + private readonly IDbContextFactory _dbContextFactory; + private readonly ILoggerFactory _loggerFactory; + private readonly ILogger _logger; + + public MunicipalityEventTopicConsumer( + KafkaOptions options, + IDbContextFactory dbContextFactory, + ILoggerFactory loggerFactory + ) + { + _options = options.ThrowIfNull(); + _dbContextFactory = dbContextFactory.ThrowIfNull(); + _loggerFactory = loggerFactory.ThrowIfNull(); + _logger = loggerFactory.CreateLogger(); + } + + public async Task ConsumeContinuously(Func messageHandler, CancellationToken cancellationToken) + { + if (string.IsNullOrEmpty(_options.Consumers?.MunicipalityEvent?.Topic)) + { + _logger.LogError($"Configuration has no {nameof(KafkaConsumers.MunicipalityEvent)} Consumer with a Topic."); + return; + } + + var kafkaConsumerOptions = _options.Consumers.MunicipalityEvent; + var consumerGroupId = $"{nameof(RoadRegistry)}.{nameof(MunicipalityEventConsumer)}.{kafkaConsumerOptions.Topic}{kafkaConsumerOptions.GroupSuffix}"; + + var jsonSerializerSettings = EventsJsonSerializerSettingsProvider.CreateSerializerSettings(); + + var consumerOptions = new ConsumerOptions( + new BootstrapServers(_options.BootstrapServers), + new Topic(kafkaConsumerOptions.Topic), + new ConsumerGroupId(consumerGroupId), + jsonSerializerSettings, + new GrarContractsMessageSerializer(jsonSerializerSettings) + ); + if (!string.IsNullOrEmpty(_options.SaslUserName)) + { + consumerOptions.ConfigureSaslAuthentication(new SaslAuthentication(_options.SaslUserName, _options.SaslPassword)); + } + if (kafkaConsumerOptions.Offset is not null) + { + consumerOptions.ConfigureOffset(new Offset(kafkaConsumerOptions.Offset.Value)); + } + + _logger.LogInformation("Starting to consume Topic '{Topic}' with ConsumerGroupId '{ConsumerGroupId}'", consumerOptions.Topic, consumerOptions.ConsumerGroupId); + + while (!cancellationToken.IsCancellationRequested) + { + try + { + await new IdempotentConsumer(consumerOptions, _dbContextFactory, _loggerFactory) + .ConsumeContinuously(messageHandler, cancellationToken); + } + catch (ConfigurationErrorsException ex) + { + _logger.LogError(ex.Message); + return; + } + catch (Exception ex) + { + const int waitSeconds = 30; + _logger.LogCritical(ex, "Error consuming kafka events, trying again in {seconds} seconds", waitSeconds); + await Task.Delay(TimeSpan.FromSeconds(waitSeconds), cancellationToken); + } + } + } +} diff --git a/src/RoadRegistry.SyncHost/RoadRegistry.SyncHost.csproj b/src/RoadRegistry.SyncHost/RoadRegistry.SyncHost.csproj index e08004b9b..2af5a282b 100644 --- a/src/RoadRegistry.SyncHost/RoadRegistry.SyncHost.csproj +++ b/src/RoadRegistry.SyncHost/RoadRegistry.SyncHost.csproj @@ -20,6 +20,7 @@ + diff --git a/src/RoadRegistry.SyncHost/KafkaTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs similarity index 100% rename from src/RoadRegistry.SyncHost/KafkaTopicConsumerByFile.cs rename to src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameEventConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameEventConsumer.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameEventProjectionContextEventProcessor.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameEventProjectionContextEventProcessor.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json new file mode 100644 index 000000000..4be152726 --- /dev/null +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json @@ -0,0 +1,8 @@ +[ + { + "headers":{"IdempotenceKey": "1"}, + "offset": 1, + "key": "1", + "value": "{\"type\":\"StreetNameWasRenamed\",\"data\":\"{}\"}" + } +] \ No newline at end of file diff --git a/src/RoadRegistry.SyncHost/StreetNameEventTopicConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameEventTopicConsumer.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameEventTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameEventTopicConsumerByFile.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameSnapshotConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameSnapshotConsumer.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameSnapshotProjectionContextEventProcessor.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameSnapshotProjectionContextEventProcessor.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameSnapshotTopicConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameSnapshotTopicConsumer.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs diff --git a/src/RoadRegistry.SyncHost/StreetNameSnapshotTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs similarity index 100% rename from src/RoadRegistry.SyncHost/StreetNameSnapshotTopicConsumerByFile.cs rename to src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs diff --git a/src/RoadRegistry.SyncHost/appsettings.development.json b/src/RoadRegistry.SyncHost/appsettings.development.json index 6f074d92e..37c4884d6 100644 --- a/src/RoadRegistry.SyncHost/appsettings.development.json +++ b/src/RoadRegistry.SyncHost/appsettings.development.json @@ -44,9 +44,10 @@ "StreetNameProjections": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameProjectionsAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", - "StreetNameEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", - "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True" + "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "MunicipalityEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "MunicipalityEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True" }, "Kafka": { @@ -63,6 +64,12 @@ "GroupSuffix": "", "Offset": null, "JsonPath": "" + }, + "MunicipalityEvent": { + "Topic": "dev.municipality.migration", + "GroupSuffix": "", + "Offset": null, + "JsonPath": "" } } }, diff --git a/src/RoadRegistry.SyncHost/appsettings.json b/src/RoadRegistry.SyncHost/appsettings.json index d0d3f73cc..2ba4d5f03 100644 --- a/src/RoadRegistry.SyncHost/appsettings.json +++ b/src/RoadRegistry.SyncHost/appsettings.json @@ -33,9 +33,10 @@ "StreetNameProjections": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameProjectionsAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", - "StreetNameEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", - "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True" + "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "MunicipalityEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "MunicipalityEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True" }, "DistributedLock": { From 2d203d7fed96fc7f5d3a08e8fba913370e6d553a Mon Sep 17 00:00:00 2001 From: Rik De Peuter Date: Wed, 30 Oct 2024 16:52:33 +0100 Subject: [PATCH 2/5] handle municipality events + add tests --- .../Core/EventSourcedEntityRepository.cs | 9 ++ .../Core/IMunicipalities.cs | 9 -- .../Core/Municipalities.cs | 26 ---- .../Core/Municipality.cs | 24 ---- .../MunicipalityEventWriter.cs | 25 ---- ...nicipalityNisCode.cs => MunicipalityId.cs} | 11 +- ....cs => 20241030142232_Initial.Designer.cs} | 29 ++++- ...0_Initial.cs => 20241030142232_Initial.cs} | 28 +++++ ...palityEventConsumerContextModelSnapshot.cs | 45 ------- .../Models/Municipality.cs | 46 +++++++ .../MunicipalityEventConsumerContext.cs | 13 ++ .../MunicipalityEventProjection.cs | 112 +++++++++++++++--- ...dRegistry.Sync.MunicipalityRegistry.csproj | 2 +- src/RoadRegistry.SyncHost/.gitignore | 2 +- .../Modules/MunicipalityConsumerModule.cs | 16 +-- .../Modules/StreetNameConsumerModule.cs | 1 + .../Modules/StreetNameProjectionModule.cs | 1 + .../Infrastructure/Program.cs | 30 ++--- .../Municipality/MunicipalityEventConsumer.cs | 52 +------- .../MunicipalityEventTopicConsumer.cs | 14 +-- .../OrganizationConsumer.cs | 20 ++-- .../StreetName/KafkaTopicConsumerByFile.cs | 12 +- .../StreetName/StreetNameEventConsumer.cs | 22 ++-- ...ameEventProjectionContextEventProcessor.cs | 6 +- .../StreetNameEventTopicConsumer.cs | 14 +-- .../StreetNameEventTopicConsumerByFile.cs | 12 +- .../StreetName/StreetNameSnapshotConsumer.cs | 24 ++-- ...SnapshotProjectionContextEventProcessor.cs | 6 +- .../StreetNameSnapshotTopicConsumer.cs | 16 +-- .../StreetNameSnapshotTopicConsumerByFile.cs | 14 +-- .../appsettings.development.json | 1 + src/RoadRegistry.SyncHost/appsettings.json | 1 + .../ConnectedProjectionFixture.cs | 18 ++- ...MunicipalityEventProjectionClassFixture.cs | 14 +++ .../MunicipalityEventProjectionTestsBase.cs | 31 +++++ .../GivenMunicipality.cs | 33 ++++++ .../GivenMunicipality.cs | 34 ++++++ .../GivenMunicipality.cs | 35 ++++++ .../GivenMunicipality.cs | 33 ++++++ .../GivenMunicipality.cs | 35 ++++++ .../GivenMunicipality.cs | 34 ++++++ .../GivenMunicipality.cs | 26 ++++ .../GivenMunicipality.cs | 35 ++++++ .../Organization/OrganizationConsumerTests.cs | 1 + test/RoadRegistry.SyncHost.Tests/Startup.cs | 4 + .../InMemoryStreetNameEventTopicConsumer.cs | 1 + ...InMemoryStreetNameSnapshotTopicConsumer.cs | 1 + .../StreetNameEventConsumerTests.cs | 1 + .../StreetNameSnapshotConsumerTests.cs | 1 + .../BackOffice/SharedCustomizations.cs | 10 ++ 50 files changed, 670 insertions(+), 320 deletions(-) delete mode 100644 src/RoadRegistry.BackOffice/Core/IMunicipalities.cs delete mode 100644 src/RoadRegistry.BackOffice/Core/Municipalities.cs delete mode 100644 src/RoadRegistry.BackOffice/Core/Municipality.cs delete mode 100644 src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs rename src/RoadRegistry.BackOffice/{MunicipalityNisCode.cs => MunicipalityId.cs} (69%) rename src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/{20241029124520_Initial.Designer.cs => 20241030142232_Initial.Designer.cs} (61%) rename src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/{20241029124520_Initial.cs => 20241030142232_Initial.cs} (56%) delete mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Models/Municipality.cs rename src/RoadRegistry.SyncHost/{ => Organization}/OrganizationConsumer.cs (97%) rename test/RoadRegistry.SyncHost.Tests/{StreetName/Projections/Fixtures => }/ConnectedProjectionFixture.cs (62%) create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionClassFixture.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityBecameCurrent/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityGeometryWasCorrected/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityNisCodeWasCorrected/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToCurrent/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToRetired/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasDrawn/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRegistered/GivenMunicipality.cs create mode 100644 test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRetired/GivenMunicipality.cs diff --git a/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs b/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs index 361dd81a1..dbb7e8172 100644 --- a/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs +++ b/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs @@ -85,6 +85,15 @@ await message.GetJsonData(ct), return ConvertEntity(entity); } + public TEntity AddNew(TIdentifier id) + { + var entity = _entityFactory(); + var stream = _getStreamName(id); + _map.Attach(new EventSourcedEntityMapEntry(entity, stream, ExpectedVersion.NoStream)); + + return entity; + } + protected virtual TEntity ConvertEntity(TEntity entity) { return entity; diff --git a/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs b/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs deleted file mode 100644 index eea4e862e..000000000 --- a/src/RoadRegistry.BackOffice/Core/IMunicipalities.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace RoadRegistry.BackOffice.Core; - -using System.Threading; -using System.Threading.Tasks; - -public interface IMunicipalities -{ - Task FindAsync(MunicipalityNisCode id, CancellationToken ct = default); -} diff --git a/src/RoadRegistry.BackOffice/Core/Municipalities.cs b/src/RoadRegistry.BackOffice/Core/Municipalities.cs deleted file mode 100644 index fb944dc0c..000000000 --- a/src/RoadRegistry.BackOffice/Core/Municipalities.cs +++ /dev/null @@ -1,26 +0,0 @@ -namespace RoadRegistry.BackOffice.Core; - -using Be.Vlaanderen.Basisregisters.EventHandling; -using Framework; -using Newtonsoft.Json; -using SqlStreamStore; -using System; - -public class Municipalities : EventSourcedEntityRepository, IMunicipalities -{ - public static readonly Func ToStreamName = instance => - new StreamName(instance.ToString()).WithPrefix("municipality-"); - - public Municipalities(EventSourcedEntityMap map, IStreamStore store, JsonSerializerSettings settings, EventMapping mapping) - : base(map, store, settings, mapping, - ToStreamName, - Municipality.Factory - ) - { - } - - protected override Municipality ConvertEntity(Municipality entity) - { - return entity.IsRemoved ? null : entity; - } -} diff --git a/src/RoadRegistry.BackOffice/Core/Municipality.cs b/src/RoadRegistry.BackOffice/Core/Municipality.cs deleted file mode 100644 index 5670de240..000000000 --- a/src/RoadRegistry.BackOffice/Core/Municipality.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace RoadRegistry.BackOffice.Core; - -using System; -using Framework; -using Messages; - -public class Municipality : EventSourcedEntity -{ - public static readonly Func Factory = () => new Municipality(); - - private Municipality() - { - On(e => - { - DutchName = e.DutchName; - Geometry = e.Geometry; - }); - } - - public string DutchName { get; private set; } - public MunicipalityGeometry Geometry { get; set; } - - public bool IsRemoved { get; private set; } -} diff --git a/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs b/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs deleted file mode 100644 index 3208bacb3..000000000 --- a/src/RoadRegistry.BackOffice/MunicipalityEventWriter.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace RoadRegistry.BackOffice; - -using System; -using System.Threading; -using System.Threading.Tasks; -using Framework; -using SqlStreamStore; - -public interface IMunicipalityEventWriter -{ - Task WriteAsync(StreamName streamName, int expectedVersion, Guid messageId, object[] events, CancellationToken cancellationToken); -} - -public class MunicipalityEventWriter : RoadRegistryEventWriter, IMunicipalityEventWriter -{ - public MunicipalityEventWriter(IStreamStore store, EventEnricher enricher) - : base(store, enricher) - { - } - - public Task WriteAsync(StreamName streamName, int expectedVersion, Guid messageId, object[] events, CancellationToken cancellationToken) - { - return AppendToStoreStream(streamName, messageId, expectedVersion, events, null, null, cancellationToken); - } -} diff --git a/src/RoadRegistry.BackOffice/MunicipalityNisCode.cs b/src/RoadRegistry.BackOffice/MunicipalityId.cs similarity index 69% rename from src/RoadRegistry.BackOffice/MunicipalityNisCode.cs rename to src/RoadRegistry.BackOffice/MunicipalityId.cs index 28ae2553d..9cfdf085e 100644 --- a/src/RoadRegistry.BackOffice/MunicipalityNisCode.cs +++ b/src/RoadRegistry.BackOffice/MunicipalityId.cs @@ -2,19 +2,18 @@ namespace RoadRegistry.BackOffice; using System; using Extensions; -using Framework; -public readonly struct MunicipalityNisCode +public readonly struct MunicipalityId { private readonly string _value; - public MunicipalityNisCode(string value) + public MunicipalityId(string value) { ArgumentNullException.ThrowIfNull(value); if (!AcceptsValue(value)) { - throw new ArgumentException("The value is not a well known nis-code", nameof(value)); + throw new ArgumentException("The value is not a well known municipality identifier", nameof(value)); } _value = value; @@ -22,7 +21,7 @@ public MunicipalityNisCode(string value) public static bool AcceptsValue(string value) { - return !string.IsNullOrEmpty(value) && !value.ContainsWhitespace() && value.Length == 5; + return !string.IsNullOrEmpty(value) && !value.ContainsWhitespace(); } public override string ToString() @@ -30,7 +29,7 @@ public override string ToString() return _value; } - public static implicit operator string(MunicipalityNisCode instance) + public static implicit operator string(MunicipalityId instance) { return instance.ToString(); } diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.Designer.cs similarity index 61% rename from src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs rename to src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.Designer.cs index 740711aaa..0ea35686d 100644 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.Designer.cs +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.Designer.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; using RoadRegistry.Sync.MunicipalityRegistry; #nullable disable @@ -12,7 +13,7 @@ namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations { [DbContext(typeof(MunicipalityEventConsumerContext))] - [Migration("20241029124520_Initial")] + [Migration("20241030142232_Initial")] partial class Initial { /// @@ -42,6 +43,32 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.ToTable("ProcessedMessages", "RoadRegistryMunicipalityEventConsumer"); }); + + modelBuilder.Entity("RoadRegistry.Sync.MunicipalityRegistry.Models.Municipality", b => + { + b.Property("MunicipalityId") + .HasColumnType("nvarchar(450)"); + + b.Property("Geometry") + .HasColumnType("Geometry"); + + b.Property("NisCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("MunicipalityId"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("MunicipalityId"), false); + + b.HasIndex("NisCode"); + + SqlServerIndexBuilderExtensions.IsClustered(b.HasIndex("NisCode")); + + b.ToTable("Municipalities", "RoadRegistryMunicipalityEventConsumer"); + }); #pragma warning restore 612, 618 } } diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.cs similarity index 56% rename from src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs rename to src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.cs index 89e7dfc30..2d617a81c 100644 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241029124520_Initial.cs +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/20241030142232_Initial.cs @@ -1,5 +1,6 @@ using System; using Microsoft.EntityFrameworkCore.Migrations; +using NetTopologySuite.Geometries; #nullable disable @@ -14,6 +15,22 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.EnsureSchema( name: "RoadRegistryMunicipalityEventConsumer"); + migrationBuilder.CreateTable( + name: "Municipalities", + schema: "RoadRegistryMunicipalityEventConsumer", + columns: table => new + { + MunicipalityId = table.Column(type: "nvarchar(450)", nullable: false), + NisCode = table.Column(type: "nvarchar(450)", nullable: false), + Status = table.Column(type: "int", nullable: false), + Geometry = table.Column(type: "Geometry", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_Municipalities", x => x.MunicipalityId) + .Annotation("SqlServer:Clustered", false); + }); + migrationBuilder.CreateTable( name: "ProcessedMessages", schema: "RoadRegistryMunicipalityEventConsumer", @@ -28,6 +45,13 @@ protected override void Up(MigrationBuilder migrationBuilder) .Annotation("SqlServer:Clustered", true); }); + migrationBuilder.CreateIndex( + name: "IX_Municipalities_NisCode", + schema: "RoadRegistryMunicipalityEventConsumer", + table: "Municipalities", + column: "NisCode") + .Annotation("SqlServer:Clustered", true); + migrationBuilder.CreateIndex( name: "IX_ProcessedMessages_DateProcessed", schema: "RoadRegistryMunicipalityEventConsumer", @@ -38,6 +62,10 @@ protected override void Up(MigrationBuilder migrationBuilder) /// protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "Municipalities", + schema: "RoadRegistryMunicipalityEventConsumer"); + migrationBuilder.DropTable( name: "ProcessedMessages", schema: "RoadRegistryMunicipalityEventConsumer"); diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs deleted file mode 100644 index 12a080646..000000000 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs +++ /dev/null @@ -1,45 +0,0 @@ -// -using System; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Storage.ValueConversion; -using RoadRegistry.Sync.MunicipalityRegistry; - -#nullable disable - -namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations -{ - [DbContext(typeof(MunicipalityEventConsumerContext))] - partial class MunicipalityEventConsumerContextModelSnapshot : ModelSnapshot - { - protected override void BuildModel(ModelBuilder modelBuilder) - { -#pragma warning disable 612, 618 - modelBuilder - .HasAnnotation("ProductVersion", "8.0.3") - .HasAnnotation("Relational:MaxIdentifierLength", 128); - - SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); - - modelBuilder.Entity("Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.ProcessedMessage", b => - { - b.Property("IdempotenceKey") - .HasMaxLength(128) - .HasColumnType("nvarchar(128)"); - - b.Property("DateProcessed") - .HasColumnType("datetimeoffset"); - - b.HasKey("IdempotenceKey"); - - SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("IdempotenceKey")); - - b.HasIndex("DateProcessed"); - - b.ToTable("ProcessedMessages", "RoadRegistryMunicipalityEventConsumer"); - }); -#pragma warning restore 612, 618 - } - } -} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Models/Municipality.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Models/Municipality.cs new file mode 100644 index 000000000..fc8ee0ce1 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Models/Municipality.cs @@ -0,0 +1,46 @@ +namespace RoadRegistry.Sync.MunicipalityRegistry.Models; + +using BackOffice; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; +using NetTopologySuite.Geometries; + +public class Municipality +{ + public string MunicipalityId { get; set; } + public string NisCode { get; set; } + public MunicipalityStatus Status { get; set; } + public Geometry? Geometry { get; set; } +} + +public enum MunicipalityStatus +{ + Proposed = 0, + Current = 1, + Retired = 2 +} + +public class MunicipalityConfiguration : IEntityTypeConfiguration +{ + public const string TableName = "Municipalities"; + + public void Configure(EntityTypeBuilder b) + { + b.ToTable(TableName, WellKnownSchemas.MunicipalityEventConsumerSchema) + .HasKey(p => p.MunicipalityId) + .IsClustered(false); + + b.Property(p => p.MunicipalityId) + .ValueGeneratedNever() + .IsRequired(); + + b.Property(p => p.NisCode); + b.Property(p => p.Status); + b.Property(p => p.Geometry) + .HasColumnType("Geometry") + .IsRequired(false); + + b.HasIndex(p => p.NisCode) + .IsClustered(); + } +} diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs index 654d17b14..c7e551d60 100644 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventConsumerContext.cs @@ -4,14 +4,18 @@ namespace RoadRegistry.Sync.MunicipalityRegistry; using BackOffice; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Models; public class MunicipalityEventConsumerContext : ConsumerDbContext { private const string ConsumerSchema = WellKnownSchemas.MunicipalityEventConsumerSchema; + public DbSet Municipalities => Set(); + public MunicipalityEventConsumerContext() { } @@ -32,6 +36,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); modelBuilder.ApplyConfiguration(new ProcessedMessageConfiguration(ConsumerSchema)); + modelBuilder.ApplyConfiguration(new MunicipalityConfiguration()); } public static void ConfigureOptions(IServiceProvider sp, DbContextOptionsBuilder options) @@ -42,6 +47,7 @@ public static void ConfigureOptions(IServiceProvider sp, DbContextOptionsBuilder sp.GetRequiredService().GetRequiredConnectionString(WellKnownConnectionNames.MunicipalityEventConsumer), sqlOptions => sqlOptions .EnableRetryOnFailure() + .UseNetTopologySuite() .MigrationsHistoryTable(MigrationTables.MunicipalityEventConsumer, WellKnownSchemas.MunicipalityEventConsumerSchema)); } } @@ -61,4 +67,11 @@ protected override MunicipalityEventConsumerContext CreateContext(DbContextOptio { return new MunicipalityEventConsumerContext(migrationContextOptions); } + + protected override void ConfigureSqlServerOptions(SqlServerDbContextOptionsBuilder sqlServerOptions) + { + base.ConfigureSqlServerOptions(sqlServerOptions); + + sqlServerOptions.UseNetTopologySuite(); + } } diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs index e3034de72..60dc0a62e 100644 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/MunicipalityEventProjection.cs @@ -3,33 +3,113 @@ namespace RoadRegistry.Sync.MunicipalityRegistry; using System; using System.Threading; using System.Threading.Tasks; -using BackOffice.Core; using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; +using Models; +using NetTopologySuite.IO; +using Municipality = Models.Municipality; public class MunicipalityEventProjection : ConnectedProjection { - private readonly IMunicipalities _municipalities; + private readonly WKBReader _wkbReader = new WKBReader + { + HandleSRID = true, + IsStrict = false + }; + + public MunicipalityEventProjection() + { + When(Handle); + When(Handle); + When(Handle); + When(Handle); + When(Handle); + When(Handle); + When(Handle); + When(Handle); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityWasRegistered @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.NisCode = @event.NisCode; + municipality.Status = MunicipalityStatus.Proposed; + }, cancellationToken); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityGeometryWasCorrected @event, CancellationToken cancellationToken) + { + var geometry = _wkbReader.Read(Convert.FromHexString(@event.ExtendedWkbGeometry)); + + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Geometry = geometry; + }, cancellationToken); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityNisCodeWasCorrected @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.NisCode = @event.NisCode; + }, cancellationToken); + } - public MunicipalityEventProjection(IMunicipalities municipalities) + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityWasDrawn @event, CancellationToken cancellationToken) { - _municipalities = municipalities.ThrowIfNull(); + var geometry = _wkbReader.Read(Convert.FromHexString(@event.ExtendedWkbGeometry)); + + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Geometry = geometry; + }, cancellationToken); + } - When(MunicipalityWasRegistered); + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityBecameCurrent @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Status = MunicipalityStatus.Current; + }, cancellationToken); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityWasCorrectedToCurrent @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Status = MunicipalityStatus.Current; + }, cancellationToken); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityWasRetired @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Status = MunicipalityStatus.Retired; + }, cancellationToken); + } + + private async Task Handle(MunicipalityEventConsumerContext context, MunicipalityWasCorrectedToRetired @event, CancellationToken cancellationToken) + { + await Update(context, @event.MunicipalityId, municipality => + { + municipality.Status = MunicipalityStatus.Retired; + }, cancellationToken); } - private async Task MunicipalityWasRegistered(MunicipalityEventConsumerContext context, MunicipalityWasRegistered envelope, CancellationToken token) + private async Task Update(MunicipalityEventConsumerContext context, string municipalityId, Action configure, CancellationToken cancellationToken) { - //TODO-rik implement muni events + var municipality = await context.Municipalities.FindAsync([municipalityId], cancellationToken); + if (municipality is null) + { + municipality = new Municipality + { + MunicipalityId = municipalityId + }; + context.Municipalities.Add(municipality); + } - // var municipality = await _municipalities.FindAsync(nisCode, cancellationToken); - // if (municipality is null) - // { - // - // } - // else - // { - // - // } + configure(municipality); } } diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj b/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj index 89a1288ee..96753a80e 100644 --- a/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/RoadRegistry.Sync.MunicipalityRegistry.csproj @@ -4,7 +4,7 @@ RoadRegistry.Sync.MunicipalityRegistry AnyCPU;x64;x86 - disable + enable false false diff --git a/src/RoadRegistry.SyncHost/.gitignore b/src/RoadRegistry.SyncHost/.gitignore index f5332c2f6..31863d18f 100644 --- a/src/RoadRegistry.SyncHost/.gitignore +++ b/src/RoadRegistry.SyncHost/.gitignore @@ -1 +1 @@ -/StreetNameEventTopic.json +/*Topic.json diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs index b50e82eb0..90ea1ac61 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs @@ -2,6 +2,7 @@ namespace RoadRegistry.SyncHost.Infrastructure.Modules { using BackOffice; using Microsoft.Extensions.DependencyInjection; + using Municipality; using Sync.MunicipalityRegistry; public static class MunicipalityConsumerModule @@ -9,21 +10,8 @@ public static class MunicipalityConsumerModule public static IServiceCollection AddMunicipalityConsumerServices(this IServiceCollection services) { return services - .AddSingleton() - .AddSingleton() - .AddSingleton(sp => - { - //var options = sp.GetRequiredService(); - //var jsonPath = options.Consumers.MunicipalityEvent.JsonPath; - // if (!string.IsNullOrEmpty(jsonPath)) - // { - // // for local testing purposes - // return new MunicipalityEventTopicConsumerByFile(sp.GetRequiredService>(), jsonPath, sp.GetRequiredService>()); - // } - - return sp.GetRequiredService(); - }) + .AddSingleton(sp => sp.GetRequiredService()) .AddSingleton>(MunicipalityEventConsumerContext.ConfigureOptions) .AddDbContext((sp, options) => sp.GetRequiredService>()(sp, options)) .AddDbContextFactory((sp, options) => sp.GetRequiredService>()(sp, options)) diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameConsumerModule.cs b/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameConsumerModule.cs index 217cf6b8a..5e15d4866 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameConsumerModule.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameConsumerModule.cs @@ -4,6 +4,7 @@ namespace RoadRegistry.SyncHost.Infrastructure.Modules using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; + using StreetName; using Sync.StreetNameRegistry; public static class StreetNameConsumerModule diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameProjectionModule.cs b/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameProjectionModule.cs index 888fe4e4b..12866d856 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameProjectionModule.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Modules/StreetNameProjectionModule.cs @@ -4,6 +4,7 @@ namespace RoadRegistry.SyncHost.Infrastructure.Modules using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; using Hosts.Infrastructure.Extensions; using Microsoft.Extensions.DependencyInjection; + using StreetName; using Sync.StreetNameRegistry; public static class StreetNameProjectionModule diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Program.cs b/src/RoadRegistry.SyncHost/Infrastructure/Program.cs index 2741397ae..e9d3c21ad 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Program.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Program.cs @@ -1,5 +1,7 @@ namespace RoadRegistry.SyncHost.Infrastructure; +using System.Threading; +using System.Threading.Tasks; using Autofac; using BackOffice; using BackOffice.Extensions; @@ -10,11 +12,12 @@ namespace RoadRegistry.SyncHost.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Modules; +using Municipality; +using Organization; +using StreetName; +using Sync.MunicipalityRegistry; using Sync.OrganizationRegistry; using Sync.StreetNameRegistry; -using System.Threading; -using System.Threading.Tasks; -using Sync.MunicipalityRegistry; public class Program { @@ -38,17 +41,16 @@ public static async Task Main(string[] args) .AddEditorContext() .RegisterOptions() - //TODO-rik temp disable - // .AddOrganizationConsumerServices() - // .AddHostedService() - // - // .AddStreetNameConsumerServices() - // .AddHostedService() - // .AddHostedService() - // - // .AddStreetNameProjectionServices() - // .AddHostedService() - // .AddHostedService() + .AddOrganizationConsumerServices() + .AddHostedService() + + .AddStreetNameConsumerServices() + .AddHostedService() + .AddHostedService() + + .AddStreetNameProjectionServices() + .AddHostedService() + .AddHostedService() .AddMunicipalityConsumerServices() .AddHostedService() diff --git a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs index 62644d1c6..372b21a96 100644 --- a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs +++ b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventConsumer.cs @@ -1,80 +1,38 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.Municipality; using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Autofac; -using BackOffice; -using BackOffice.Core; -using BackOffice.Framework; -using BackOffice.Messages; -using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; using Hosts; -using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using SqlStreamStore; using Sync.MunicipalityRegistry; using Resolve = Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector.Resolve; public class MunicipalityEventConsumer : RoadRegistryBackgroundService { - private readonly IDbContextFactory _municipalityEventConsumerDbContextFactory; - private readonly ILifetimeScope _container; - private readonly IStreamStore _store; - private readonly IMunicipalityEventWriter _eventWriter; private readonly IMunicipalityEventTopicConsumer _consumer; - private static readonly EventMapping EventMapping = - new(EventMapping.DiscoverEventNamesInAssembly(typeof(RoadNetworkEvents).Assembly)); - - private static readonly JsonSerializerSettings SerializerSettings = - EventsJsonSerializerSettingsProvider.CreateSerializerSettings(); - public MunicipalityEventConsumer( - ILifetimeScope container, - IDbContextFactory municipalityEventConsumerDbContextFactory, - IStreamStore store, - IMunicipalityEventWriter eventWriter, IMunicipalityEventTopicConsumer consumer, ILogger logger ) : base(logger) { - _container = container.ThrowIfNull(); - _municipalityEventConsumerDbContextFactory = municipalityEventConsumerDbContextFactory.ThrowIfNull(); - _store = store.ThrowIfNull(); - _eventWriter = eventWriter.ThrowIfNull(); _consumer = consumer.ThrowIfNull(); } protected override async Task ExecutingAsync(CancellationToken cancellationToken) { + var projector = + new ConnectedProjector( + Resolve.WhenEqualToHandlerMessageType(new MunicipalityEventProjection().Handlers)); + await _consumer.ConsumeContinuously(async (message, dbContext) => { Logger.LogInformation("Processing {Type}", message.GetType().Name); - var map = _container.Resolve(); - var municipalities = new Municipalities(map, _store, SerializerSettings, EventMapping); - - var projector = - new ConnectedProjector( - Resolve.WhenEqualToHandlerMessageType(new MunicipalityEventProjection(municipalities).Handlers)); - await projector.ProjectAsync(dbContext, message, cancellationToken).ConfigureAwait(false); - var messageId = Guid.NewGuid(); //TODO-rik van waar messageid halen? - - foreach (var entry in map.Entries) - { - var events = entry.Entity.TakeEvents(); - if (events.Any()) - { - await _eventWriter.WriteAsync(entry.Stream, entry.ExpectedVersion, messageId, events, cancellationToken); - } - } - //CancellationToken.None to prevent halfway consumption await dbContext.SaveChangesAsync(CancellationToken.None); diff --git a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs index 2a0ea272b..08191838b 100644 --- a/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs +++ b/src/RoadRegistry.SyncHost/Municipality/MunicipalityEventTopicConsumer.cs @@ -1,16 +1,16 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.Municipality; +using System; +using System.Configuration; +using System.Threading; +using System.Threading.Tasks; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; -using Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Sync.MunicipalityRegistry; -using System; -using System.Configuration; -using System.Threading; -using System.Threading.Tasks; +using RoadRegistry.Sync.MunicipalityRegistry; +using RoadRegistry.SyncHost.Infrastructure; public interface IMunicipalityEventTopicConsumer { diff --git a/src/RoadRegistry.SyncHost/OrganizationConsumer.cs b/src/RoadRegistry.SyncHost/Organization/OrganizationConsumer.cs similarity index 97% rename from src/RoadRegistry.SyncHost/OrganizationConsumer.cs rename to src/RoadRegistry.SyncHost/Organization/OrganizationConsumer.cs index 0534f19c4..b0deb0e12 100644 --- a/src/RoadRegistry.SyncHost/OrganizationConsumer.cs +++ b/src/RoadRegistry.SyncHost/Organization/OrganizationConsumer.cs @@ -1,30 +1,28 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.Organization; +using System; +using System.Configuration; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Autofac; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Microsoft.IO; using Newtonsoft.Json; using RoadRegistry.BackOffice; -using RoadRegistry.BackOffice.Abstractions.Organizations; using RoadRegistry.BackOffice.Core; +using RoadRegistry.BackOffice.Extensions; using RoadRegistry.BackOffice.Framework; using RoadRegistry.BackOffice.Messages; using RoadRegistry.Editor.Schema; using RoadRegistry.Hosts; +using RoadRegistry.Sync.OrganizationRegistry; +using RoadRegistry.Sync.OrganizationRegistry.Exceptions; using RoadRegistry.Sync.OrganizationRegistry.Extensions; using SqlStreamStore; -using Sync.OrganizationRegistry; -using System; -using System.Configuration; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using BackOffice.Extensions; -using Sync.OrganizationRegistry.Exceptions; using Organization = Sync.OrganizationRegistry.Models.Organization; public class OrganizationConsumer : RoadRegistryBackgroundService diff --git a/src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs index af86c7426..ff5fc0239 100644 --- a/src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs +++ b/src/RoadRegistry.SyncHost/StreetName/KafkaTopicConsumerByFile.cs @@ -1,5 +1,10 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; +using System.Threading.Tasks; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; @@ -7,11 +12,6 @@ namespace RoadRegistry.SyncHost; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Threading; -using System.Threading.Tasks; internal abstract class KafkaTopicConsumerByFile where TDbContext : ConsumerDbContext diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs index 9974eb067..2f4fe8e4e 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventConsumer.cs @@ -1,25 +1,25 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; using System; using System.Linq; using System.Threading; using System.Threading.Tasks; -using BackOffice; -using BackOffice.Core; -using BackOffice.Extensions; -using BackOffice.Extracts.Dbase.RoadSegments; -using BackOffice.Framework; -using BackOffice.Messages; -using BackOffice.Uploads; using Be.Vlaanderen.Basisregisters.GrAr.Contracts.StreetNameRegistry; using Be.Vlaanderen.Basisregisters.Shaperon; -using Editor.Schema; -using Editor.Schema.Extensions; using FluentValidation; -using Hosts; using Microsoft.Extensions.Logging; using Microsoft.IO; using NetTopologySuite.Geometries; +using RoadRegistry.BackOffice; +using RoadRegistry.BackOffice.Core; +using RoadRegistry.BackOffice.Extensions; +using RoadRegistry.BackOffice.Extracts.Dbase.RoadSegments; +using RoadRegistry.BackOffice.Framework; +using RoadRegistry.BackOffice.Messages; +using RoadRegistry.BackOffice.Uploads; +using RoadRegistry.Editor.Schema; +using RoadRegistry.Editor.Schema.Extensions; +using RoadRegistry.Hosts; using SqlStreamStore; using ModifyRoadSegment = BackOffice.Uploads.ModifyRoadSegment; using RoadSegmentLaneAttribute = BackOffice.Uploads.RoadSegmentLaneAttribute; diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs index 32624614f..833239c6c 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventProjectionContextEventProcessor.cs @@ -1,11 +1,11 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; using Be.Vlaanderen.Basisregisters.ProjectionHandling.SqlStreamStore; -using Hosts; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using RoadRegistry.Hosts; +using RoadRegistry.Sync.StreetNameRegistry; using SqlStreamStore; -using Sync.StreetNameRegistry; public class StreetNameEventProjectionContextEventProcessor : DbContextEventProcessor { diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs index 6f2428ae6..6b2624cef 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumer.cs @@ -1,16 +1,16 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; +using System; +using System.Configuration; +using System.Threading; +using System.Threading.Tasks; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; -using Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Sync.StreetNameRegistry; -using System; -using System.Configuration; -using System.Threading; -using System.Threading.Tasks; +using RoadRegistry.Sync.StreetNameRegistry; +using RoadRegistry.SyncHost.Infrastructure; public interface IStreetNameEventTopicConsumer { diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs index 13216fd2d..7d3c9a490 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopicConsumerByFile.cs @@ -1,13 +1,13 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; -using Be.Vlaanderen.Basisregisters.EventHandling; -using Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using Sync.StreetNameRegistry; using System; using System.Threading; using System.Threading.Tasks; +using Be.Vlaanderen.Basisregisters.EventHandling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using RoadRegistry.Sync.StreetNameRegistry; +using RoadRegistry.SyncHost.Infrastructure; internal class StreetNameEventTopicConsumerByFile : KafkaTopicConsumerByFile, IStreetNameEventTopicConsumer { diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs index 042e83350..3ef2e2b93 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotConsumer.cs @@ -1,22 +1,22 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Autofac; -using BackOffice; -using BackOffice.Core; -using BackOffice.Framework; -using BackOffice.Messages; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.GrAr.Legacy; -using Hosts; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using RoadRegistry.BackOffice; +using RoadRegistry.BackOffice.Core; +using RoadRegistry.BackOffice.Framework; +using RoadRegistry.BackOffice.Messages; +using RoadRegistry.Hosts; +using RoadRegistry.StreetName; using SqlStreamStore; -using StreetName; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using StreetNameRecord = BackOffice.Messages.StreetNameRecord; public class StreetNameSnapshotConsumer : RoadRegistryBackgroundService diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs index 63e91561e..69be0e949 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotProjectionContextEventProcessor.cs @@ -1,11 +1,11 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; using Be.Vlaanderen.Basisregisters.ProjectionHandling.SqlStreamStore; -using Hosts; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; +using RoadRegistry.Hosts; +using RoadRegistry.Sync.StreetNameRegistry; using SqlStreamStore; -using Sync.StreetNameRegistry; public class StreetNameSnapshotProjectionContextEventProcessor : DbContextEventProcessor { diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs index af23a90d2..e956036bb 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumer.cs @@ -1,17 +1,17 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; +using System; +using System.Configuration; +using System.Threading; +using System.Threading.Tasks; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka; using Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer; -using Infrastructure; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; -using Sync.StreetNameRegistry; -using System; -using System.Configuration; -using System.Threading; -using System.Threading.Tasks; -using StreetName; +using RoadRegistry.StreetName; +using RoadRegistry.Sync.StreetNameRegistry; +using RoadRegistry.SyncHost.Infrastructure; public interface IStreetNameSnapshotTopicConsumer { diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs index 8b233af2e..5ff04a397 100644 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs +++ b/src/RoadRegistry.SyncHost/StreetName/StreetNameSnapshotTopicConsumerByFile.cs @@ -1,14 +1,14 @@ -namespace RoadRegistry.SyncHost; +namespace RoadRegistry.SyncHost.StreetName; -using Be.Vlaanderen.Basisregisters.EventHandling; -using Infrastructure; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using StreetName; -using Sync.StreetNameRegistry; using System; using System.Threading; using System.Threading.Tasks; +using Be.Vlaanderen.Basisregisters.EventHandling; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using RoadRegistry.StreetName; +using RoadRegistry.Sync.StreetNameRegistry; +using RoadRegistry.SyncHost.Infrastructure; internal class StreetNameSnapshotTopicConsumerByFile : KafkaTopicConsumerByFile, IStreetNameSnapshotTopicConsumer { diff --git a/src/RoadRegistry.SyncHost/appsettings.development.json b/src/RoadRegistry.SyncHost/appsettings.development.json index 37c4884d6..12f3c886a 100644 --- a/src/RoadRegistry.SyncHost/appsettings.development.json +++ b/src/RoadRegistry.SyncHost/appsettings.development.json @@ -44,6 +44,7 @@ "StreetNameProjections": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameProjectionsAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "StreetNameEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "MunicipalityEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", diff --git a/src/RoadRegistry.SyncHost/appsettings.json b/src/RoadRegistry.SyncHost/appsettings.json index 2ba4d5f03..f45f08995 100644 --- a/src/RoadRegistry.SyncHost/appsettings.json +++ b/src/RoadRegistry.SyncHost/appsettings.json @@ -33,6 +33,7 @@ "StreetNameProjections": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameProjectionsAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", + "StreetNameEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "StreetNameSnapshotConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "MunicipalityEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", diff --git a/test/RoadRegistry.SyncHost.Tests/StreetName/Projections/Fixtures/ConnectedProjectionFixture.cs b/test/RoadRegistry.SyncHost.Tests/ConnectedProjectionFixture.cs similarity index 62% rename from test/RoadRegistry.SyncHost.Tests/StreetName/Projections/Fixtures/ConnectedProjectionFixture.cs rename to test/RoadRegistry.SyncHost.Tests/ConnectedProjectionFixture.cs index f0cfc737e..6b34de7ad 100644 --- a/test/RoadRegistry.SyncHost.Tests/StreetName/Projections/Fixtures/ConnectedProjectionFixture.cs +++ b/test/RoadRegistry.SyncHost.Tests/ConnectedProjectionFixture.cs @@ -1,4 +1,4 @@ -namespace RoadRegistry.SyncHost.Tests.StreetName.Projections.Fixtures; +namespace RoadRegistry.SyncHost.Tests; using Be.Vlaanderen.Basisregisters.EventHandling; using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; @@ -7,40 +7,38 @@ namespace RoadRegistry.SyncHost.Tests.StreetName.Projections.Fixtures; public class ConnectedProjectionFixture where TProjection : ConnectedProjection, new() { - private readonly TConnection _connection; + public TConnection Connection { get; } private readonly ConnectedProjector _projector; public ConnectedProjectionFixture(TConnection connection, ConnectedProjectionHandlerResolver resolver) { - _connection = connection; + Connection = connection; _projector = new ConnectedProjector(resolver); } - public TProjection Projection { get; init; } - public Task ProjectEnvelopeAsync(TMessage message) where TMessage : IMessage { - return _projector.ProjectAsync(_connection, new Envelope(new Envelope(message, new Dictionary()))); + return _projector.ProjectAsync(Connection, new Envelope(new Envelope(message, new Dictionary()))); } public Task ProjectAsync(object message) { - return _projector.ProjectAsync(_connection, message, CancellationToken.None); + return _projector.ProjectAsync(Connection, message, CancellationToken.None); } public Task ProjectAsync(object message, CancellationToken cancellationToken) { - return _projector.ProjectAsync(_connection, message, cancellationToken); + return _projector.ProjectAsync(Connection, message, cancellationToken); } public Task ProjectAsync(IEnumerable messages) { - return _projector.ProjectAsync(_connection, messages); + return _projector.ProjectAsync(Connection, messages); } public Task ProjectAsync(IEnumerable messages, CancellationToken cancellationToken) { - return _projector.ProjectAsync(_connection, messages, cancellationToken); + return _projector.ProjectAsync(Connection, messages, cancellationToken); } } diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionClassFixture.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionClassFixture.cs new file mode 100644 index 000000000..38e9ed3af --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionClassFixture.cs @@ -0,0 +1,14 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections; + +using Be.Vlaanderen.Basisregisters.ProjectionHandling.Connector; +using Microsoft.EntityFrameworkCore; +using Sync.MunicipalityRegistry; + +public class MunicipalityEventProjectionClassFixture : ConnectedProjectionFixture +{ + public MunicipalityEventProjectionClassFixture(IDbContextFactory dbContextFactory) + : base(dbContextFactory.CreateDbContext(), + Resolve.WhenEqualToHandlerMessageType(new MunicipalityEventProjection().Handlers)) + { + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs new file mode 100644 index 000000000..fd26c4115 --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs @@ -0,0 +1,31 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections; + +using AutoFixture; +using BackOffice; +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using RoadRegistry.Tests.BackOffice; + +public abstract class MunicipalityEventProjectionTestsBase : IClassFixture +{ + protected readonly MunicipalityEventProjectionClassFixture Projector; + protected readonly Fixture Fixture; + + protected MunicipalityEventProjectionTestsBase(MunicipalityEventProjectionClassFixture projector) + { + Projector = projector; + Fixture = new Fixture(); + Fixture.CustomizeMunicipalityId(); + } + + protected async Task GivenRegisteredMunicipality() + { + var @event = new MunicipalityWasRegistered( + Fixture.Create(), + Fixture.Create(), + null); + + await Projector.ProjectAsync(@event); + + return @event; + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityBecameCurrent/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityBecameCurrent/GivenMunicipality.cs new file mode 100644 index 000000000..bb402ae0b --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityBecameCurrent/GivenMunicipality.cs @@ -0,0 +1,33 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityBecameCurrent; + +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityBecameCurrent( + registeredEvent.MunicipalityId, + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Current); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityGeometryWasCorrected/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityGeometryWasCorrected/GivenMunicipality.cs new file mode 100644 index 000000000..79c25d484 --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityGeometryWasCorrected/GivenMunicipality.cs @@ -0,0 +1,34 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityGeometryWasCorrected; + +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityGeometryWasCorrected( + registeredEvent.MunicipalityId, + "01030000208A7A000001000000F4040000001283C098B7F84000894160F7D403410052B81E29B4F84080ED7C3F28D603410076BE9F8AB3F840008941601CD603410040355EDCB2F840809BC420C5D5034100643BDFA1B0F840809BC42062D50341001283C040ACF840801283C095D5034100C0CAA12FABF84080ED7C3F9AD503410052B81E1FAAF840801283C08AD503410040355E0EAAF8400000000007D503410040355E0EAAF84080ED7C3F75D40341001283C008AAF84080ED7C3F10D4034100388941B6A9F840801283C08ED303410024068157A9F840008941609FD3034100AE47E14AA9F84000000000A4D30341009CC4209CA8F8400077BE9FD2D303410052B81E51A7F8400077BE9F3ED4034100388941B2A5F840809BC420DAD4034100C0CAA1C9A3F84080C876BE9DD503410076BE9F70A3F84080643BDFD0D5034100643BDF5DA2F8400000000096D50341002EB29D45A0F840803BDF4F10D5034100AE47E19C9DF8400077BE9F1CD6034100C876BEA99BF84000250681E6D60341003889412E99F8400077BE9F44D8034100000000F697F84080ED7C3F09D9034100AE47E1A490F8400077BE9F61D4034100EE7C3F6790F84080643BDF96D40341004A0C02DB8FF840801283C0FBD40341009CC420768FF840809BC4203FD50341004A0C02A18EF8400077BE9F9BD503410040355EFE8DF84000000000EDD503410040355EFC8CF84080ED7C3F5AD603410040355EDC8AF8400077BE9F3AD7034100643BDF8B89F84080C0CAA1C9D703410076BE9F6288F8400000000045D80341009CC420DA86F84080ED7C3FEDD8034100EE7C3FC785F84080ED7C3F6ED9034100C0CAA18784F84080643BDFF4D9034100AE47E17282F840809BC4202CDB0341004A0C028B80F840809BC4202EDC034100643BDFB17DF840801283C0EEDD034100AE47E1F87CF840801283C06FDE03410040355EE27CF84000000000C1DE0341001283C0C07CF840002506811DDF034100EE7C3FD57BF8400089416093DF034100C0CAA1CD7AF840809BC42017E0034100000000AA79F840801283C0B9E00341009CC420FC78F8400025068113E1034100EE7C3F7977F84080643BDF37E1034100AE47E17C76F840801283C064E1034100643BDFDD74F84080ED7C3FB3E10341001A2FDD4074F84000000000D5E1034100AE47E19876F840801283C023E50341008A4160E977F84000000000DCE60341008A4160EB78F8400077BE9FD5E70341004A0C02B979F840801283C091E8034100C876BEF179F84080ED7C3FBFE80341001283C03279F840002506813EE90341004A0C02FB77F8400077BE9F13EA034100643BDFDB74F84000000000E9EB0341008A41602B72F840801283C04AED034100DCF97E706EF840809BC420EBEE034100C0CAA1FD6AF840000000000CED0341001283C07C69F84080643BDFA2ED034100643BDF3F68F84080643BDF26EE0341003889413067F840809BC420BAEE034100AE47E15C66F8400000000073EF034100EE7C3F4D65F8400077BE9F60F0034100C0CAA13D64F840801283C037F103410040355E6A63F840809BC4201AF20341001283C05463F84000DBF97E97F2034100AE47E11E63F8400077BE9F4BF3034100C0CAA1F162F84080ED7C3FD7F30341009CC420A662F840801283C053F40341008A4160CB61F84080ED7C3F23F50341009CC4205A61F84000250681E7F5034100643BDFE560F84080643BDFD6F603410052B81EED5FF8400025068120F8034100643BDFC15FF8400077BE9FB5F80341006E1283A45FF840801283C074F90341008A4160EF5EF84080C876BE6DFA03410076BE9F765EF8400025068117FB034100388941945DF84080643BDF75FB0341009CC4202A5CF840809BC420F6FB03410052B81EED5AF84080643BDF90FC034100240681DD59F840008941603EFD034100DCF97EA058F8400089416006FE034100643BDFE357F84080ED7C3F3BFE03410040355E3657F840801283C042FE034100AE47E19E55F840000000004EFE034100DCF97EBC54F84080ED7C3F6CFE034100AE47E1A854F840801283C0C8FE034100C0CAA1CB54F84080643BDF28FF034100365EBAE954F8400089416074FF03410076BE9FC455F840801283C056000441008A41603D56F840801283C0FC000441006E12835E56F84080ED7C3F70010441008A41600D57F840008941602602044100C0CAA1BD56F84000DBF97E4C020441009CC4207256F840801283C06A0204410052B81EBD55F840002506819F0204410040355E5A54F84080ED7C3FC50204410076BE9FE852F84000894160E302044100643BDFA152F84000000000560404410040355E5E4DF840801283C05304044100C0CAA1654DF8400077BE9FD8030441008A4160474DF840000000006F030441008A4160F34CF84000894160F70204410040355EF04CF84080643BDFC402044100388941944CF84000000000270204410040355E244CF84080643BDF6401044100388941FC4AF840809BC420C3FF0341001283C0DA49F8408037894121FE03410076BE9F5648F840801283C0D7FB034100DCF97E2E47F8400000000015FA03410040355E9249F84000250681BFF90341001283C0664DF84000250681A8F903410076BE9FCA4FF8400000000081F9034100AE47E15450F840801283C045F90341004A0C02B150F84080643BDF0DF9034100C0CAA11351F8400000000038F80341001283C0BE51F8400077BE9F23F7034100643BDFF951F8400000000089F6034100EE7C3FC551F84000250681CDF5034100DCF97E3451F840005C8FC23CF5034100EE7C3FCB50F840801283C07AF403410076BE9F9051F84080643BDFBBF30341001283C0B852F840008941606CF2034100C876BE3553F8400025068147F103410040355E9E52F84080B6F3FD08F103410076BE9F2853F8400089416029F00341001283C09453F84080C420B0DAEE0341004A0C02A953F84000250681CFEE034100C0CAA1C553F8400077BE9FBFEE034100EE7C3F0D54F8400025068167EE034100643BDF3754F840801283C0DCED0341004A0C023F54F840809BC42019ED0341004A0C02FF53F840008941608EEC0341009CC420C653F8400000000032EC0341001283C07053F840801283C0E3EB034100643BDFD153F84080643BDF79EB0341004A0C02F553F8400077BE9F3FEB034100240681DB53F84000250681FCEA0341009CC4208653F84000894160CEEA034100DCF97E2253F84080ED7C3F8EEA034100643BDF3753F840801283C04AEA034100643BDF7753F8400089416015EA03410054E3A5A953F84080C0CAA1A3E90341004A0C02FF53F84000A4703DF5E80341001283C0B053F8400077BE9F8AE803410052B81ECD51F84080643BDFBFE603410076BE9F7E4EF840801283C0C6E3034100EE7C3F294EF840000000009CE3034100C876BEC54DF84080643BDF6DE3034100EE7C3F694DF84080ED7C3F4AE3034100AE47E1DA4BF840809BC42043E3034100EE7C3FF74AF84000250681DFE2034100AE47E15A4AF84000894160B1E203410076BE9FDA47F84080ED7C3FB1E10341009CC420BE46F8400025068126E10341006E1283DA45F84000894160BFE0034100C876BEEF44F8400025068106E00341003889414C44F840008941609FDF0341009CC4200C44F840809BC420F1DE0341001283C0F642F84000250681CDDD034100AE47E1C441F84000250681B4DD0341003889419A41F840809BC4203FDD03410040355EA83FF840801283C0E9DB03410052B81EE13EF8400089416074DB034100AE47E1D23DF840809BC420E6DA034100EE7C3F6F3CF84080643BDF17DA034100DCF97E443BF8400052B81E8DD903410052B81E6F3AF8400025068142D90341004A0C026F39F84000000000C6D8034100643BDF2739F8400077BE9FF0D7034100000000B638F84000000000AAD70341008A41603F38F840801283C01ED70341000000006A37F840809BC42018D6034100643BDF5D36F840801283C037D5034100AE47E13035F84080B6F3FD51D40341000000001633F840002506816CD3034100A4703DF232F8400077BE9F46D3034100643BDFF732F8400077BE9F83D2034100EE7C3F5932F840008941603BD10341008A41607331F840002506812CD003410052B81E0531F84000894160A5D0034100AE47E1A030F84080643BDFE0D003410040355E402FF840002506810AD10341001283C0922DF8400077BE9F4CD1034100240681052DF840002506815DD103410040355E3A2CF840801283C084D10341008A4160612BF84000250681BED103410052B81E432AF84080ED7C3F08D20341009CC4206A29F8400000000049D20341008A41607528F840809BC42095D203410076BE9F0628F84080643BDFC7D2034100AE47E1AE27F84080ED7C3F06D303410040355E5C27F84080643BDF57D303410040355ED426F84080ED7C3F31D4034100C0CAA17B26F84000250681EBD4034100643BDF4B26F840809BC420F2D4034100EE7C3FD125F84080ED7C3F03D5034100240681A525F84080643BDF09D5034100C876BE4524F840008941603FD5034100C876BEAF21F840809BC42098D5034100EE7C3F4520F840801283C08DD503410076BE9FC81FF840809BC4209FD503410052B81E471EF84000000000E2D503410040355E061EF84000894160F3D50341003889413E1DF84080643BDF6BD6034100C0CAA18B1BF840002506816AD7034100EE7C3FB71AF840005C8FC2EBD703410052B81E131AF8400077BE9F52D8034100EE7C3FC719F8400025068186D80341001283C02819F840002506813BD9034100EE7C3FE118F84080643BDF6CD9034100000000BC18F840008941607FD9034100AE47E11818F84080643BDFCAD9034100AE47E1B616F84000AE47E1C3D90341004A0C02DB15F84000250681C0D9034100EE7C3F3F14F84000000000B7D9034100C0CAA1E911F840801283C083D9034100C876BE050FF8400000000034D90341008A4160A90DF840000000000CD9034100EE7C3FE30AF84000250681BAD80341001C5A64A908F84080ED7C3F7BD80341008A4160E301F84080643BDFAFD70341000000008801F84000250681A2D70341008A4160FBFDF74080643BDF44D7034100DCF97EFCFBF74080643BDF3BD70341000000009CF6F740801283C0F7D403410024068145F4F74080ED7C3FF5D3034100388941C8F2F740000000005CD30341004A0C0265F1F74080643BDFB1D20341001283C044F0F740801283C002D2034100DCF97E30EFF740809BC42060D1034100C876BE85EDF7400025068118D00341004A0C02FFEBF74000000000F2CE0341002406811BEBF740002DB29DF0CD034100DCF97EE0EAF740801283C051CD0341004A0C02A9EAF7400077BE9F88CC0341004A0C028DEAF7400000000074CB034100A69BC452EAF74080ED7C3F70CB03410052B81ED5E7F740801283C046CB03410038894102E6F74080ED7C3F21CB03410052B81E9DE3F74080ED7C3F2DCB034100388941C2E1F7400089416063CB03410052B81E2DDFF74000250681D2CB0341008A416093DEF74080643BDFC6CB034100D24D6274D3F74000DBF97ED3CA0341004A0C023FD2F74080ED7C3FB7CA034100643BDF8FD1F74080ED7C3FBFCA03410076BE9F4CD0F740801283C0DFCA034100C876BE09CFF740801283C011CB034100DCF97E00CEF74080ED7C3F62CB034100643BDF37CDF74080ED7C3FB1CB034100C876BEC1CCF740801283C0E8CB034100C0CAA1F1C8F74000000000A0CD0341008A41605BC7F74080ED7C3F53CE03410076BE9F44C6F7400077BE9FCACE034100F853E31DC5F740809BC42052CF03410040355E84C3F74000894160DECE034100EE7C3F5BC0F74080643BDFBFCD03410076BE9F90BBF740801283C0A4CB034100E6D02287B7F740809BC420A4CA034100D022DBE5B5F740809BC42050CA0341009CC420C4B2F7400000000062C903410024068139B2F740809BC42047C90341000000009CB1F74080ED7C3F01C9034100EE7C3F85B0F74000250681C1C8034100DCF97E66AFF7400077BE9F9DC80341004A0C026BABF740801283C08DC803410052B81EC7A7F7400077BE9FA9C803410052B81E01A6F7400077BE9FB5C8034100643BDF8BA3F74000250681C1C8034100C876BEA39CF740801283C089C80341009CC4202495F740000000005AC80341009CC4206E93F740809BC42042C8034100643BDF1990F7400077BE9FCEC70341004A0C02878BF7400089416017C703410052B81EB189F740801283C0CFC60341004A0C021388F74080643BDFB7C60341008A41602F84F740801283C0BBC603410052B81EE380F7400077BE9FEBC6034100C876BE8D7EF7400025068107C70341004A0C02B17BF74080ED7C3F33C7034100EE7C3F7B7AF74080ED7C3F4BC7034100C0CAA1D379F74080643BDF6BC70341004A0C022D78F74000000000C4C7034100EE7C3FE175F74080ED7C3F4DC803410052B81E0D75F740801283C07AC8034100DCF97E7C74F740801283C099C8034100C0CAA12773F74080643BDFE2C803410040355E7872F74000DBF97E30C9034100C0CAA18771F740008941606EC9034100C876BE2D6CF74080ED7C3F90CA0341004A0C02D967F74080643BDF78CB034100240681AD66F740809BC4209FCB03410076BE9F6865F74000000000AFCB03410076BE9F2061F74080ED7C3FA2CB0341002406816D5BF7400089416092CB034100DCF97EF256F740801283C0DECB034100643BDFD355F74000000000D2CB03410052B81EA353F7400077BE9FA8CB034100C876BE4551F7400077BE9F42CB0341001283C01E4CF74000250681FAC9034100000000BE49F740008941605EC903410004560E0344F74080C420B05FC8034100DCF97EF843F7400077BE9F5CC8034100DCF97EF041F7400025068122C80341004A0C02993FF74000000000D3C7034100643BDFF53DF7400025068183C7034100000000063CF740801283C017C7034100AE47E1103BF74000250681E2C60341007E6ABCCC3AF74000250681D1C60341001283C03C3AF74000D34D62ADC603410040355E5838F740801283C0DEC503410040355E3235F7400077BE9FA0C4034100C0CAA1AD33F7400089416024C40341009CC420D631F74080643BDFB4C3034100388941D62FF740801283C065C30341005A643B6D2EF7400077BE9F38C30341004A0C02BD2BF740801A2FDDE8C20341004A0C02B729F740801283C0C0C20341008A4160AF28F740809BC420DEC2034100C0CAA18F23F7400077BE9FE4C20341002406814320F74080ED7C3F9DC20341000000003C1EF74080643BDF48C20341008A41609117F740809BC42038C0034100AE47E1540FF7400089416079BD034100000000140EF7400052B81EFFBC0341004A0C02830BF74000894160FBBB03410076BE9F3E05F7400025068190B9034100AE47E19600F74000000000F4B7034100EE7C3FBDFEF6400025068116B7034100240681B9FDF6400025068157B603410040355ED4FCF64080643BDF90B503410040355EFEFBF64080643BDF12B403410076BE9FFAFAF64000250681B3B203410024068115FAF64000250681F4B1034100EE7C3FF3F8F64080643BDF81B1034100AE47E130F1F64000894160E5AF034100DCF97E66EFF64080643BDFAFAF03410076BE9FE8EDF64000250681B7AF0341008A416007EAF64080643BDFC2B003410024068155E1F640809BC42079B3034100240681CBDEF64080643BDF8AB4034100C0CAA1E9DBF64080ED7C3FB5B5034100643BDF17DAF64080ED7C3F31B603410038894192D8F64000AE47E179B6034100C0CAA1F9D5F64000250681ABB603410076BE9FC0D2F6400077BE9FE8B60341008A416033D1F6400000000035B7034100DCF97EF2CFF6400025068194B703410076BE9FF2CDF640000000005FB803410076BE9F4ACDF64000894160D5B8034100DCF97E24CBF6400089416053BA03410076BE9F4ECAF64080643BDFDCBA0341004A0C02C9C8F640803F355E66BB034100AE47E1FAC5F640801283C0DCBB034100C876BE63B5F640801283C015BD0341004A0C029DB1F640801283C079BD034100DCF97ED6B0F64080643BDF9EBD034100000000A4B0F64000250681D4BD03410052B81E1FABF64080643BDFF9BD034100EE7C3F33AAF64080643BDF0CBE03410040355E38A9F64080643BDF0CBE034100000000D0A7F6400077BE9F09BE034100643BDFE1A6F64080643BDF0CBE034100643BDF4DA6F6400089416023BE0341008A4160EDA5F64000AE47E139BE034100000000C4A3F6400077BE9F98BF03410076BE9FF4A1F640000000009FBF0341001A2FDDF69EF64080E5D02282BF0341006CE7FB5B99F640801A2FDD7EBF03410040355E7C97F640000000006BBF0341001283C06E94F640000000000BBF034100643BDF7393F640000000000BBF0341001283C01E92F64080643BDF17BF034100EE7C3FC390F640809BC4201BBF0341004A0C02218FF64080C876BE14BF0341001283C0128EF64000D34D622EBF03410076BE9F5E8DF640801283C041BF0341001283C0908CF6400052B81E65BF034100EE7C3F678AF64080643BDFE5BF034100643BDF7788F640809BC42060C00341001283C0A085F64080643BDF1DC10341002406812B84F640008941606EC1034100DCF97E9C82F64000250681ABC10341001283C00081F640809BC420E2C1034100BE9F1ADF7FF64080ED7C3FF2C1034100365EBA497EF64080643BDFFBC1034100643BDF537CF64080643BDFFBC1034100643BDF3579F6400000000029C20341004A0C02B175F6400000000056C2034100C0CAA15574F640008941605EC2034100C876BE0D70F640809BC42026C1034100000000D26CF64000000000BBC00341009CC420506AF640801283C009C00341002406812B6AF64000894160FFBF03410076BE9FB469F64080643BDFD6BF0341009CC4203669F64000000000A6BF03410076BE9FF068F6400000000090BF0341002406812D68F640801283C047BF0341005C8FC2F567F6400025068137BF0341004A0C021F67F640809BC420E7BE034100240681E166F64000894160D4BE0341009CC420EA65F6400077BE9F9FBE034100C876BE9965F640809BC42092BE034100C0CAA1C564F640809BC4206ABE0341002406817764F64080ED7C3F5FBE0341003889417E63F6400077BE9F3BBE034100000000585FF64000000000BCBD0341008A41601F5EF6400089416096BD034100C876BE035DF640809BC42068BD03410040355E505CF6400077BE9F59BD034100F6285CED5BF6400077BE9F53BD0341009CC420325BF64080ED7C3F39BC0341005C8FC25D58F640809BC42040B9034100C876BE6D57F6400077BE9F13B8034100D022DBE356F64080C876BE54B7034100AE47E16256F64080643BDF16B7034100C0CAA1C155F64000000000AFB6034100DCF97ED254F6400077BE9F27B603410040355EB453F6400025068130B5034100643BDFD752F6400077BE9F84B40341002406814152F64000894160EEB3034100DCF97E1E51F64000250681E6B2034100C0CAA1EB4FF640809BC4206AB103410040355E4A4FF64080643BDF1DB00341003889415E4FF64000250681A9AF034100AE47E1A24FF64080ED7C3FE3AE0341009CC4203850F6400089416002AE0341002406811B51F64080ED7C3FB3AC03410052B81ED551F64000894160BBAB034100388941C251F6400089416033AB03410040355EE051F640000000001DAA034100B81E853B52F6400000000097A90341002406811953F640801283C053A90341004A0C029153F640803F355EBDA80341002406814555F64080ED7C3F40A7034100EE7C3FE556F6400052B81E00A60341002406813B59F640008941601CA4034100C0CAA1715BF6400077BE9F82A2034100000000DC5BF640801283C0F2A103410076BE9FB85CF64080ED7C3FFEA0034100000000945EF64000894160049F03410040355E0661F64080ED7C3FA99C034100C876BEF163F64000000000E8990341004A0C02C965F640809BC4200E98034100DCF97EFA67F640803F355E14960341008A4160336AF640809BC4205A940341009CC4203C6CF64000894160B8920341003889410A6DF640002506819F920341004A0C02CF6EF64000894160E490034100643BDFF770F64000894160A48E034100EE7C3FBD75F640809BC4204B8A034100AE47E1167AF6400077BE9F7F86034100643BDFD575F64000250681D4850341000000002C71F64080ED7C3F11850341002406811D72F640801283C075820341004A0C023B73F6400077BE9F897F034100D24D628673F640809BC4205F7F034100C0CAA18D73F64080ED7C3F667D034100C876BE5573F64000250681697A0341004A0C023D73F64000894160D6780341001283C00673F64080ED7C3F20780341004A0C020372F640809BC4205476034100DCF97EEA71F64080643BDFE1750341008A4160DB71F640000000000B750341002406814971F640008941603374034100EE7C3F1571F640002506818773034100388941CE70F640002506813673034100EE7C3F2F70F6400077BE9F8D72034100DCF97E1C70F640809BC4203672034100C0CAA1F96FF640008941609271034100240681836FF640002506811971034100000000646FF64000000000CE70034100AE47E1A26EF640809BC420F16F034100EE7C3F696EF64080643BDF2B6F034100643BDFFD6DF64000250681E46E034100DCF97EBA6DF64080ED7C3FA56E034100C876BEE16DF64000894160846E03410076BE9F846EF64000250681686E034100643BDFAB6EF6400077BE9F476E0341002406813B6EF6400077BE9F7D6D0341004A0C02276EF640008941601E6D03410040355E006EF640005C8FC2B06C0341001283C0D26DF64080ED7C3F476C034100C0CAA19F6DF640008941600C6C03410052B81E396DF64080643BDFDB6B0341001283C08A6CF64000250681566B034100DCF97E026CF64000250681AC6A0341009CC420DA6BF64080ED7C3F376A034100000000106CF64000894160CC690341004A0C02056DF64080643BDFA868034100000000D46DF64080ED7C3F1768034100240681716DF6400077BE9FF9670341001283C0F66BF6400000000083670341001283C03C6AF64000000000046703410040355EAC68F64080ED7C3F7B660341002406813767F64000250681FC65034100C0CAA19F66F640002506810466034100C0CAA14966F640002DB29D916503410040355EC065F6400089416006650341008A41600B65F6400077BE9F6264034100AE47E1FE64F64000894160E0630341002406812D63F64000000000CF62034100EE7C3FDF62F64000000000396203410076BE9F5462F64000250681EF610341001283C02661F640801283C00C610341001283C0FE5FF64080643BDF2160034100000000505FF64000250681535F03410040355E1C5EF640809BC420EA5D034100643BDF815DF64080643BDF7A5D0341001283C08A5CF640801283C0085D0341009CC420B85CF64080ED7C3FBD5C034100000000EC5AF640008941607D5B03410040355EAC58F64000000000F4590341009CC420F05CF64080ED7C3FBA570341001283C0D25BF640801283C09856034100AE47E1725BF64080C876BE8855034100000000B05BF64080ED7C3F7B55034100EE7C3FEB5BF640002506814C5503410040355E865CF64080643BDFE754034100DCF97EE05CF640809BC420BA540341009CC420785DF64080ED7C3F815403410052B81ED55DF640000000006D540341000000003C60F640008941604B54034100EE7C3F2960F6400077BE9F17540341009CC4206861F6400077BE9F055403410008AC1CCC63F64000000000D653034100C0CAA1AF67F640801283C050530341002406810B68F640002506814E530341001283C0426BF64080643BDF1853034100C0CAA1576DF64080ED7C3F2753034100AE47E1BA6FF6400077BE9F3B53034100240681B570F6400077BE9F4553034100240681A771F640002506816453034100F853E3C774F64000000000B3530341001283C0D078F640008941603054034100C0CAA18F79F640801283C01E540341001283C0CE7AF64080643BDF0D54034100C0CAA1D97BF640000000000554034100DCF97E207DF640002506812454034100643BDFE17DF6400025068143540341003889410280F6400077BE9FB65403410076BE9F8481F640000000000955034100AE47E1BA83F640801283C08255034100643BDF8B85F640801283C0DD550341000000008C86F64080643BDF0D56034100643BDFFD86F64080643BDF2B560341008A41600787F640801283C0AB5603410076BE9FF486F64080ED7C3F20570341002406812B87F64000000000495703410040355EAE87F64000250681C257034100C0CAA1F987F640000000001B580341001283C02688F64000AE47E18158034100DCF97E5C88F6400077BE9FA558034100643BDF438AF64080643BDFE55803410052B81E318DF640801283C03559034100C0CAA1F18DF64080ED7C3F37590341004A0C02618EF64080643BDF25590341009CC420B68FF640809BC420C858034100EE7C3F1590F64000894160B2580341004A0C027790F64080643BDFD8580341003889419092F64080643BDF46590341004A0C02A792F64000250681355903410076BE9F9098F640809BC4207A5A034100240681399AF64000250681CE5A034100EE7C3FC19AF64000894160325A034100643BDF6DA1F640803F355EA6590341004A0C0205A2F64080643BDF9A59034100240681B3A7F64080ED7C3F235903410052B81E2FACF64000000000B15803410040355E66AEF64080ED7C3FF356034100C0CAA1A3B1F64000250681795403410040355EF8B2F64080ED7C3F6D53034100DCF97E4AB6F64000250681CB500341001283C004B7F64080643BDF0F50034100DCF97EA0B7F640809BC420344F034100C0CAA1E5B8F64000250681024E034100DCF97ECABBF64080ED7C3F534B034100AE47E19EBDF640801283C0A649034100EE7C3FF7BFF64080643BDF014A034100C0CAA12FC2F64080643BDF4F4A034100EE7C3FDFC2F640000000001549034100DCF97E36C3F64000894160CB4803410052B81EB1C3F64000D34D629448034100EE7C3F19C5F64000000000224803410000000010C6F64080643BDFAE47034100AE47E11AC7F640809BC420F9460341004A0C0213C8F6400077BE9FA74603410024068169C8F64080643BDF6F46034100EE7C3FA3C8F640002506814F4603410040355E7ACAF640801283C01246034100000000F8CAF64000894160F8450341003889417ACBF640809BC420D045034100DCF97E6CCCF64080ED7C3F71450341009CC4200ACDF64000000000454503410040355E62CDF640000000002645034100000000E8CDF64080643BDFDD44034100388941AACEF6400077BE9F994403410000000044CFF64080643BDF7044034100C0CAA1F5CFF64080643BDF554403410076BE9F48D1F6400077BE9F2844034100000000B0D1F640000000000B440341001283C004D2F6400077BE9FD043034100643BDF57D2F6400077BE9FC0430341009CC420A2D2F64080378941C343034100EE7C3FB1D3F64000894160EA430341008A416023D4F64080ED7C3F1744034100643BDFABD4F640000000003044034100EE7C3F41D5F64000000000414403410076BE9FE0D5F640008941608F44034100C0CAA18BD6F64000000000E944034100C876BE05D7F640002506810D4503410000000084D7F64080643BDF3445034100AE47E1DED7F64000DBF97E3B4503410024068125D9F640008941603A45034100EE7C3F85D9F6400025068141450341009CC42068DAF6400077BE9F714503410040355E62DBF640806E128399450341004A0C0257DCF6400077BE9FA44503410054E3A5E5DCF640008941609C45034100C0CAA10FDDF64000250681B8450341004A0C0299DDF64000894160DE450341001283C066DEF64080ED7C3F0F4603410052B81E4DDFF6400077BE9F4346034100C0CAA11BE0F640008941605446034100EE7C3F33E3F64000250681994603410052B81E01E4F64000000000BB46034100000000E4E5F6400077BE9F1847034100C0CAA18FE6F64080ED7C3F5247034100C0CAA14BE7F640801283C09847034100EE7C3FD3E7F6400077BE9FC347034100240681F5E8F640809BC420064803410040355E72E9F6400089416031480341009CC42044EAF6400077BE9F7448034100C876BED5EAF64080643BDF90480341004A0C026FEBF64080ED7C3F9E48034100643BDFFFEBF640801283C08F480341008A416053EDF6400077BE9F594803410040355E3AEEF640000000002348034100C876BE8DEEF64080643BDFFB4703410040355EBCEEF64000000000FC47034100DCF97E52EFF64080ED7C3F3F48034100240681C3EFF64000894160604803410040355E0CF0F640801283C06648034100643BDF57F0F6400077BE9F65480341002406812DF1F6400089416043480341008A4160C3F1F640801283C05A4803410038894166F2F64080ED7C3F6A48034100643BDF49F3F640008941605B48034100AE47E1D6F3F6400089416050480341004A0C02A1F4F640809BC4207D4803410040355E46F5F64000894160B2480341009CC420DAF5F640801283C00449034100EE7C3F1DF6F640008941605049034100EE7C3F79F6F64000000000BD4903410052B81EB9F6F640803F355EE549034100AC1C5ACCF8F64000250681364A034100DCF97EE8FCF640806E1283E74A03410052B81EE9FFF640809BC420604A034100643BDFDD05F740002506817D490341009CC4208C0BF74080643BDFAB480341003889412A0DF740809BC4208E480341001283C0C00DF74000250681B2480341009CC420560EF7400077BE9FDA48034100EE7C3F0F10F74000250681F5480341008A4160C710F74080ED7C3FB848034100AE47E17A11F740801283C0CB48034100E4A59BEE11F74000250681BA480341001A2FDD4A12F740801283C05348034100EE7C3FAB12F740801283C05848034100A4703D4613F74080643BDF5B48034100C0CAA1BB13F7400077BE9F63480341009CC4206414F7400077BE9F95480341000000005815F74000D34D628848034100AE47E11616F74080643BDFCD48034100DCF97EA216F740809BC420C648034100C876BE5D17F7400077BE9FC348034100AE47E18E18F74080ED7C3FF7480341001283C0061AF740809BC4201A49034100C0CAA1731BF740801283C0B348034100643BDF811CF74080643BDF6548034100E6D022091DF74000D34D621248034100DCF97E621EF74080ED7C3FA5470341000000003020F740809BC4201847034100643BDF8D21F740809BC420A84603410076BE9FDC21F74080ED7C3F6B460341000000004C22F740809BC4202B46034100EE7C3FD322F74080ED7C3F26460341004A0C027F24F74080643BDF2946034100240681A925F740801283C01E460341001283C04A26F740801283C02646034100643BDF8727F740801A2FDDFC4503410008AC1CD228F740801283C0EA450341008A41604F29F7400077BE9FD045034100DCF97E462AF74000894160BB4503410076BE9FFC2AF740801283C0C44503410040355EC42BF74000D34D62ED450341001283C0542CF74080ED7C3F27460341002406810F2DF740809BC42022460341008A41601F2DF7400000000051460341004A0C02612DF7400025068183460341008A4160B32DF740809BC420844603410076BE9F242EF74080643BDF6246034100240681952EF740801283C07746034100C0CAA11F2FF740801283C0AC46034100388941622FF740809BC420E346034100AE47E1AA2FF74080ED7C3F344703410076BE9FFC2FF74080ED7C3F48470341004A0C02F130F7400089416053470341009CC4208631F740002506815E47034100C0CAA11B32F7400025068176470341000000008C32F7400000000073470341003889418233F740801283C04247034100CAA1450634F74080643BDF2F47034100DCF97ED234F740008941604B470341004A0C029F35F740008941604547034100643BDFCD35F740002506815347034100C876BE3936F74080ED7C3F8A470341000000009436F740809BC420804703410076BE9F5037F7400025068162470341009CC420EE37F740801283C06F47034100643BDFA938F7400089416091470341004A0C021739F74080643BDFD94703410040355E4A39F74080ED7C3FFB47034100C0CAA1DB39F7400077BE9F2148034100EE7C3F3B3BF74000250681AF480341009CC4205E3CF74000000000BC480341009CC420EA3CF740801283C0E2480341009CC4209E3DF740000000000F490341009CC420443EF740000000006449034100829543A93EF74000000000CC49034100240681013FF74000000000B9490341009CC4200040F7400077BE9FAD49034100DCF97E7640F740801283C0074A034100240681ED40F74080643BDF2B4A03410052B81E8143F74080ED7C3FFB4A0341001283C09447F740002506815E4C03410076BE9FA848F7400077BE9FD14C034100DCF97E084CF74080643BDF4C4E0341002406810D50F740801283C00D500341002406813150F740801283C00250034100DCF97EAC53F74000250681085103410040355E2A56F740809BC420A15103410052B81EF956F740809BC420A251034100240681B958F74000250681A251034100AE47E1725CF740801283C052510341004A0C02A962F740809BC420D750034100829543EB65F740002506819550034100C876BE3968F7400025068166500341002406817168F740000000007150034100AE47E1D668F740801283C05550034100B81E85EF6DF74080B6F3FD584F034100388941CE6EF74080643BDF294F034100000000086FF740002506812E4F0341004A0C022F6FF740809BC420714F034100DCF97E626FF7400077BE9F8D4F034100388941CA6FF740801283C0A24F0341008A41602370F74000894160CC4F03410052B81E4D70F74080B6F3FDE64F034100643BDF7D70F74080643BDFFB4F034100240681AB70F7400000000009500341001283C0F070F740809BC420F94F034100E6D0224571F74080ED7C3FEC4F034100EE7C3F1372F74080ED7C3FDF4F0341001283C03672F74080643BDFC14F0341001283C06E72F74000000000A44F0341004A0C02A972F74000250681A34F034100643BDFD374F7400077BE9FC24F03410076BE9F2476F74000250681D14F0341009CC4208A76F740801283C0F14F034100EE7C3F1177F740801283C070500341001283C0AC77F74080490C027A50034100AE47E11678F740803F355E605003410040355E8E78F7400077BE9F3E500341009CC420C078F74080643BDF3D50034100643BDFAD79F74000894160C35003410040355E2A7AF74000DBF97ECF500341009CC420D27AF7400077BE9F0051034100D022DB437BF740801283C0175103410040355EB67CF74080643BDFCF500341008A41605B7DF74000894160EC500341001283C0F47DF740801283C00051034100240681977EF74080643BDFFF50034100643BDFD17EF74000000000FB500341008A4160077FF74000000000F150034100AE47E1267FF74080ED7C3FE150034100C0CAA15D7FF74000000000B95003410076BE9FB47FF740002506816B500341001283C0CC7FF74080643BDF1450034100643BDF6F80F74080643BDFE24F034100DCF97EEA81F74000A4703D7B4F03410052B81E7182F74080B6F3FD644F03410040355E2683F740002506816A4F03410030DD24CA83F7400077BE9F7C4F034100000000B484F7400077BE9F8D4F0341008A41601B85F74000894160AB4F0341002406816B85F74080ED7C3FDD4F0341003889419E85F74080ED7C3FEB4F03410030DD243C86F740809BC420E54F034100388941EA87F74000250681DE4F0341001A2FDD6288F74000894160F84F034100EE7C3FA988F74000250681F14F03410040355E0C89F740801283C0F84F034100C0CAA13189F7400077BE9F0550034100643BDF4B89F740801283C0215003410040355E5489F740801283C04E5003410040355EA489F74080ED7C3F5850034100643BDFF389F74080643BDF725003410040355E3C8AF740809BC4208F50034100000000588BF74080643BDFC75003410040355E3C8CF740801283C0E450034100EE7C3F498DF74080643BDF8F50034100C0CAA1638DF74000A4703D7F50034100643BDFA38DF740002DB29D41500341001283C0F08DF74000894160C54F0341004A0C02418EF74080643BDFA14F034100240681678EF740008941608C4F0341009CC4206E8EF74080643BDF6A4F0341007E6ABC868EF740801283C04D4F0341004A0C02FD8EF7400077BE9F214F0341002406814B8FF74000250681FC4E0341007E6ABC728FF7400077BE9FD54E03410076BE9FB08FF74000250681A74E0341002406812990F74080ED7C3F904E03410076BE9F7091F74000894160634E03410076BE9FCC91F74080643BDF474E034100643BDF6992F74080643BDF214E0341001283C0B692F74080ED7C3FFF4D0341008A4160F392F74080ED7C3FEF4D034100DCF97E1E93F74000DBF97EE74D0341002406816B93F740809BC420E94D034100C0CAA11D94F740801283C0CB4D034100C0CAA11394F7400077BE9FA94D034100C876BE0194F740008941604E4D0341004A0C020794F74000250681294D034100EE7C3F6994F74000000000DE4C034100643BDFF994F740000000009F4C034100C0CAA11D95F74000000000AF4C0341004A0C024195F74080ED7C3FB14C034100643BDF9F95F74000250681A44C03410040355E0C96F74080643BDF894C034100240681C996F74080643BDF544C034100C0CAA1EB96F74080ED7C3F4F4C034100AC1C5A9497F740801283C0904C034100EE7C3FD197F74000000000A14C034100C0CAA1D998F740801283C0684C034100EE7C3F139AF74000894160224C0341001283C05C9BF74000894160D44B034100AE47E1869DF7400077BE9F534B0341004A0C02AF9DF74000250681444B034100000000C09DF7400077BE9F2D4B0341009CC420EC9DF7400077BE9FEA4A03410052B81E359EF74000000000774A034100DE24069D9EF74000894160CB49034100AE47E1FA9EF74080ED7C3F2E49034100C0CAA1459FF740809BC4208E480341001283C0549FF74080643BDF7B48034100EE7C3F9F9EF740809BC42024480341008A4160D39DF740008941604E470341009CC420569DF74080ED7C3FFE46034100C876BEF59CF74080643BDFBC460341009CC4203A9DF74000250681B1460341009CC420E89DF74080ED7C3F7E460341004A0C02779EF740809BC4203346034100DCF97E989EF740000000001246034100240681999EF7400000000013450341001283C0689EF74080ED7C3F96440341004A0C02199EF7400025068129440341004A0C02399EF740002506811A44034100C876BE499EF740002506810D440341001283C07E9EF7400077BE9FBD43034100388941A29EF740801283C07D43034100DCF97EA29EF740002506816343034100EE7C3FAD9EF74000000000594303410052B81ECD9EF74080643BDF534303410076BE9FF49EF740809BC42058430341001283C0309FF740002506815D43034100EE7C3F639FF74080ED7C3F57430341009CC420989FF740801283C04643034100240681BF9FF7400077BE9F2C4303410040355EDC9FF740008941601343034100C876BE0DA0F74080643BDFE3420341008A41602FA0F74000894160B2420341008A416057A0F740801283C063420341004A0C025FA0F74080643BDF2D42034100EE7C3F65A0F740809BC420E24103410000000058A0F74000894160964103410040355E64A0F74000A4703D6941034100C0CAA19BA0F740809BC4203741034100DCF97EEAA0F74000000000034103410076BE9F38A1F74000250681DB400341004A0C028DA1F74000250681C84003410052B81EE9A1F740809BC420BF400341008A41606BA2F7400077BE9FB4400341001283C020A3F74000250681B540034100643BDF29A3F74080643BDFA540034100EE7C3F63A4F74080ED7C3FF13F034100EE7C3F8FA5F74080C876BE403F03410040355E76A7F740809BC4202A3E03410040355E5AA8F740002506819E3D03410008AC1C34A9F74080643BDF133D0341001283C0AAA9F7400077BE9FF13C034100240681DFA9F740801A2FDDC23C034100E6D02279AAF74080ED7C3F6D3C0341001283C060ACF7400077BE9F5A3B0341001283C0D8ACF74080ED7C3F6F3B034100AE47E10AADF74000000000AB3B034100EE7C3F4BADF74000250681AB3B034100C0CAA179ADF74000250681A03B03410052B81E55AEF74000000000403B034100C876BE6DAFF74000D34D62B73A034100365EBA3DB1F74000000000E939034100EE7C3F3BB2F7400077BE9F85390341000000008CB2F74000894160433903410076BE9FA4B2F7400077BE9F3F39034100AC1C5ACCB2F74000A4703D33390341009CC420F0B2F7400077BE9F11390341008A416033B3F74080ED7C3FD63803410000000080B3F7400000000099380341002EB29D0DB4F740002506814F38034100EE7C3F7BB4F740008941602C380341004A0C02FFB4F740000000000638034100EE7C3F57B5F74000DBF97EE037034100DCF97E90B5F74000250681BA37034100388941AEB5F740002506819B37034100DCF97ECAB5F740008941605637034100EE7C3FD3B5F74080C0CAA1F9360341001283C026B6F74000250681C436034100EE7C3F55B6F74000894160983603410024068173B6F740809BC4206636034100DCF97EB2B6F7400077BE9F0E36034100643BDFABB6F74080643BDFF535034100643BDFBFB6F74000250681E23503410076BE9FC4B6F74000000000D135034100C876BEB1B6F74080643BDFB3350341001283C0AAB6F740801283C0A435034100643BDFB5B6F740806E128397350341001A2FDDE2B6F740809BC4208E3503410052B81E09B7F7400077BE9F7C3503410076BE9F4CB7F740008941603E35034100EE7C3FADB7F740801283C0153503410040355E2CB8F7400077BE9FC13403410040355EA2B8F74080ED7C3F9B340341009CC420A8B8F740000000008B34034100388941A2B8F740000000007D3403410052B81E99B8F7400077BE9F6B34034100EE7C3FC5B8F74080ED7C3F39340341001283C0E0B8F7400077BE9F213403410024068109B9F74080643BDF2D3403410000000044B9F7400077BE9F163403410076BE9F80B9F740809BC4202034034100240681A5B9F740801283C0203403410052B81EE5B9F740801283C02D34034100643BDFF9B9F740809BC4202C340341004A0C0225BAF740002506812034034100DCF97E6CBAF740002506811F3403410024068185BAF74080643BDF1834034100643BDFA7BAF74000250681E133034100240681EFBAF74080643BDFAE33034100388941E6BAF740801283C08F330341001283C0FCBAF740801283C082330341001283C042BBF74000250681833303410040355EACBBF74080ED7C3F8D33034100AE47E1EEBBF740801283C09D33034100EE7C3F45BCF7400077BE9FA43303410024068187BCF740806E1283A733034100C876BE11BDF74000250681C433034100643BDF57BDF74080ED7C3FC53303410040355EE4BDF7400077BE9FC333034100C876BE2DBEF74000894160B4330341001283C04CBEF74000894160A43303410024068181BEF740809BC4208633034100EE7C3FABBEF740000000008333034100E4A59BC8BEF74080ED7C3F7E3303410040355EE8BEF7400077BE9F61330341003889410EBFF740801283C059330341001283C03ABFF74080643BDF6333034100EE7C3F79BFF74080ED7C3F79330341009CC42098BFF740801283C07933034100C876BED5BFF740000000006D3303410024068147C0F740002506812D33034100388941EAC0F740801283C0D832034100C0CAA1C3C1F740809BC4206F320341001283C01EC2F740000000004D320341004A0C0295C2F74080ED7C3F3E32034100EE7C3FF7C2F74080ED7C3F2B3203410040355E3EC3F740801283C0123203410024068135C3F74000250681B131034100C0CAA145C3F740000000008931034100EE7C3F8FC3F740809BC4204831034100AE47E18AC5F74000894160D22F03410076BE9FB4C6F740803F355EFA2E03410052B81EF1C6F7400077BE9FF62E0341002406812FC7F74000000000232F034100A4703D76C7F740002506812E2F0341001283C084C8F740809BC4202C2F0341005A643B55C9F74000000000012F034100AE47E1EAC9F74080ED7C3FDF2E0341009CC420A8CAF740809BC420D22E034100DCF97E4ACBF74000000000AF2E034100EE7C3FE1CBF740008941608E2E0341008A416003CDF740809BC420432E034100EE7C3F71CDF740803789413E2E034100EE7C3F19CFF74080ED7C3F572E034100C0CAA141CFF74080643BDF4E2E03410040355E88CFF74080643BDF212E034100F6285C57D0F74000000000B92D03410038894186D0F74080ED7C3F832D034100AE47E192D0F740809BC4207B2D03410052B81EB5D0F74080ED7C3F7C2D0341008A416063D1F74000894160B02D0341009CC4200CD2F74080ED7C3FE12D034100DCF97E6CD2F74080ED7C3FEA2D034100000000B0D2F74000000000F52D034100240681DDD2F74080ED7C3F002E034100643BDFFBD2F74080ED7C3F0D2E034100DCF97E12D3F740002506811E2E0341004A0C0239D3F740801283C03B2E034100EE7C3F99D3F74080ED7C3F552E0341001283C0C0D3F740000000006A2E034100EE7C3F2FD4F7400077BE9F792E0341003889418AD4F7400077BE9FA02E0341009CC420B0D4F7400077BE9F9F2E034100643BDFD7D4F740801283C0A32E034100C0CAA1FBD4F740809BC420AA2E0341003889412ED5F740809BC420C22E03410052B81E71D5F740801A2FDDC82E034100C0CAA19BD5F74080ED7C3F692E03410092ED7CBBD5F74000000000432E034100643BDF75D6F740801A2FDDF92D034100240681CDD6F74000894160D02D0341008A4160F3D6F74000250681C82D034100C876BE49D7F74080643BDFBE2D0341002406812FD8F74000894160882D0341001283C0ECD8F74080ED7C3F5A2D034100AE47E102DAF740000000001B2D03410040355E98DAF74000D34D62F22C034100EE7C3F3DDBF74080643BDFCE2C0341004A0C0275DBF74000000000BB2C034100AE47E19EDBF74000894160A72C034100643BDFC3DBF740801283C08D2C03410052B81E4DDCF74000250681422C0341001283C0F6DCF7400077BE9FDE2B0341005A643B1FDDF740809BC420BC2B0341004A0C0233DDF740002506818A2B0341009CC4204EDDF740801283C0752B034100C876BE65DDF740801283C0702B034100AE47E1C6DDF74080ED7C3F6D2B034100C876BEE1DEF740002506811C2B03410052B81E11DFF740801283C0122B0341002EB29D2FDFF74000000000162B03410076BE9F54DFF74000894160242B034100EE7C3F89DFF7400077BE9F662B0341004A0C02ADDFF740806E12837D2B034100B6F3FDD8DFF74000250681832B03410040355E0CE0F740008941607E2B0341004A0C0229E0F740801283C06E2B03410040355E6AE0F74000D34D624F2B0341009CC420C4E0F740008941601E2B03410000000028E1F74000250681172B03410076BE9F68E1F74080ED7C3F0E2B034100DCF97EB0E1F74000AE47E1F92A034100DCF97EEEE1F7400077BE9FD92A034100C0CAA147E2F740008941608D2A034100EE7C3F6BE2F740002506816E2A03410076BE9FA0E2F7400077BE9F512A034100240681C7E2F740809BC420442A0341004A0C02FBE2F74000A4703D352A0341001283C038E3F74080ED7C3F2F2A034100C0CAA189E3F74080643BDF3C2A034100643BDF41E4F74080643BDF512A03410030DD247AE4F74000250681522A0341001283C0D8E4F740809BC420382A0341009CC42078E5F740809BC420FA29034100C0CAA1B1E5F74000000000DB29034100C0CAA1C5E5F740809BC420B729034100AE47E112E6F740809BC420BD29034100000000A0E6F74000250681E7290341001283C046E7F74000894160E729034100240681CBE8F740809BC420B52A034100240681F9EAF7400077BE9F992B03410024068107EDF740809BC4206C2C0341003889419EEEF74000250681F52C0341004A0C02FDF0F74080ED7C3FB72D0341003889417AF1F740801283C0CE2D0341001283C038F5F74000894160EF2D03410040355E48F8F74000250681312E034100DCF97E98FBF74000894160422E0341005A643B23FDF74000250681562E0341004A0C0297FEF74000250681732E03410052B81E0101F840801283C0742E034100240681B305F84000250681442E0341004A0C02B706F84080ED7C3F352E0341004A0C02F70BF840002506810B2E034100AE47E1260CF84080ED7C3FEE2D0341005C8FC2250EF840801283C0FA2D034100240681A310F84000250681132E0341001283C0AC10F840002506815A2E0341001283C03811F840801283C0CC2E0341004A0C027913F84000250681F230034100DCF97E5414F8400077BE9FCA31034100C0CAA19114F84080378941D1310341004A0C02AF14F840008941601C32034100EE7C3F7314F840801283C01D32034100EE7C3F7514F84080643BDF4E32034100388941BE14F84080ED7C3FCB3503410040355ED014F8400077BE9FDD3603410052B81E1915F840002506816339034100AE47E13215F8400077BE9F783B03410040355E6215F84080643BDF4D3D0341003889418E15F84000250681253F03410076BE9FBC15F840801283C0F2400341001C5A64E715F840008941609B430341003889410816F840002506819F4303410040355E0C16F840809BC420B443034100C0CAA1EF15F84000D34D62BC430341002406814D16F84080ED7C3FE3460341009CC4209216F8400077BE9FA6490341009CC420C616F84000000000354C0341001283C0E816F84080ED7C3F834D034100240681FB16F84000894160114E0341003889411A17F840809BC420544F0341006ABC743917F840003333336450034100EE7C3F3F18F840008941607E500341001283C0C417F84080643BDFF151034100DCF97EE017F84080ED7C3F4D520341001283C02618F84080643BDFB3520341001283C0B218F84080ED7C3F2F53034100240681E519F84080ED7C3F1C54034100388941CE19F84000894160555403410040355ECA19F840809BC4208654034100365EBAF519F84000000000BD540341009CC420041AF840801283C001550341009CC4209C19F840809BC420925503410040355EA419F84080ED7C3FCF550341008A4160EF19F8400077BE9F5956034100000000C41AF8400000000074570341004A0C02D51AF84000A4703DF257034100C876BEED1AF84080643BDFA1580341002406818F1BF84000000000CB59034100643BDF0B1CF840809BC420DE5A03410040355E941CF840809BC420005C034100DCF97E021DF840809BC420845C034100AE47E11A1DF84080ED7C3FC75C034100EE7C3FAD1CF84080ED7C3FF95E0341000AD7A3841CF840809BC4206A600341002406815F1CF84000894160E161034100240681111CF840000000004D620341008A4160B31BF840002506818D620341004A0C02DB1AF840801283C0FD6203410052B81E851AF840801283C02E630341009CC420881AF840002506819263034100DCF97E4A1AF84000894160B8630341001283C0321CF840809BC4209E64034100000000D41BF840801283C0DA640341008A4160911BF84000250681FB64034100643BDF191DF8400077BE9FAF6503410040355E881FF8400077BE9FDB660341000000008420F840002506814D67034100DCF97E3221F8400000000081670341000000001C23F840002506813368034100643BDF2328F840801283C00E6A034100388941C22CF84000000000B56B034100EE7C3F2B2DF84000000000F16B03410040355EEC2EF840801283C0416D03410040355E622FF84080643BDF8F6D0341006CE7FB0F31F840000000003A6E0341002406816F38F840000000002D7103410052B81EF53DF84080ED7C3F6173034100AE47E1CA46F840809BC420ED760341008A41604F4FF840801283C0567A034100240681D952F84080ED7C3FC37B034100DCF97EA056F84080ED7C3F487D034100DCF97EA05AF84080643BDFE07E034100240681C35CF84080643BDFBD7F03410040355E0A60F840002506810C81034100EE7C3FA560F840008941604A81034100DCF97E7661F840000000009F81034100EE7C3F4D63F840000000005D820341004A0C022764F84080643BDFAE820341003889414264F84000894160DC820341001283C03C64F84000000000208303410040355EF663F840809BC4207A840341004A0C02DD63F840809BC42022850341002406819D63F84080643BDFF6850341009CC420A863F84000894160CD86034100C876BEBD63F840806E1283EF8703410076BE9FD063F840801A2FDD098903410076BE9FAC63F84080ED7C3F81890341001283C09263F840809BC4209D89034100DCF97E5663F840801283C0AE89034100B81E85E763F84080ED7C3F358A034100EE7C3F2165F84080643BDF1F8B0341004A0C023966F84000894160F18B03410008AC1C6667F84080643BDFFE8C034100C0CAA13968F84080ED7C3F538D0341004A0C02D569F840809BC420648E0341002406814F6AF840809BC4204F8F034100DCF97EB06AF840809BC4205E90034100C876BE816AF84080643BDF8D9103410040355E9669F84080643BDFB6920341008A41608B69F84080ED7C3FEF930341000000007C69F840008941601697034100365EBA6569F84080643BDF069A0341009CC420B468F840806E1283169A03410040355E1068F8400077BE9F399A0341009CC4201267F840809BC420A69A034100C876BED568F84000250681AB9B034100AC1C5AD468F840809BC420ED9B0341004A0C02C368F84000000000069C034100643BDFAB67F840809BC420739C03410040355EAC69F840801283C07A9C0341003889416E72F84000000000E79B034100643BDF5579F84000000000589B034100240681797CF84080ED7C3F2B9B03410076BE9FC47FF84080ED7C3F729B034100EE7C3FED82F840809BC420E19B034100643BDFC385F840801283C0549C03410076BE9FDC86F84080643BDFBD9C0341002406815B89F84000DBF97E8C9D0341001283C0528DF840809BC420019F034100AE47E16A90F84000894160C3A0034100643BDF599CF8400089416098A70341000000009CA3F84080ED7C3FC6AB034100C0CAA183A2F84080ED7C3F31AC034100AE47E1C2A0F8400025068121AD0341005A643B19A5F84000894160F1B0034100643BDFE1A5F840809BC420C1B1034100DCF97E24A5F84080643BDF05B203410052B81EA1A3F840002506815EB203410024068175A2F84000894160A9B2034100643BDFCBA0F840803F355EAEB2034100B6F3FD789FF8400077BE9FC1B2034100C0CAA19B9EF84080ED7C3FDDB20341001283C0EE9DF840801283C005B30341008A4160F39CF840000000005BB3034100388941BA9BF84080490C02B4B3034100643BDF4B9FF84080ED7C3FC1B403410024068171A0F840806E12832AB5034100829543159EF8400089416036B7034100C876BEDD9BF8400025068128B9034100EE7C3F179AF840801283C0C8BA0341002406810398F8400000000052BA034100388941A696F840002506819CBB034100D022DB3394F8400077BE9FDBBD0341002406812594F8400077BE9F08BE034100DCF97E749BF84080ED7C3F19C0034100643BDF6B9BF8400000000033C0034100C0CAA1BF9CF84000250681AAC0034100C0CAA19B9DF8400025068106C103410040355E049FF84080643BDF77C1034100EE7C3FF9A5F840809BC420ACC3034100643BDFFBA6F840801283C0EEC2034100C0CAA1C3A8F840000000008BC303410040355EC6B0F840801283C00EC6034100DCF97E96B7F8400089416046C80341001283C0BCB6F84000894160DFC80341004A0C02C9B2F840809BC42020CB0341008A416053ADF840809BC4203ECE034100D022DBD1AEF8400025068131CF034100DCF97E62B1F84000894160DCD003410052B81E1DB5F8400000000037D30341001283C098B7F84000894160F7D40341", + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Proposed); + actual.Geometry.Should().NotBeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityNisCodeWasCorrected/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityNisCodeWasCorrected/GivenMunicipality.cs new file mode 100644 index 000000000..03e1f30df --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityNisCodeWasCorrected/GivenMunicipality.cs @@ -0,0 +1,35 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityNisCodeWasCorrected; + +using AutoFixture; +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityNisCodeWasCorrected( + registeredEvent.MunicipalityId, + Fixture.Create(), + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(@event.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Proposed); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToCurrent/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToCurrent/GivenMunicipality.cs new file mode 100644 index 000000000..6379ec04b --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToCurrent/GivenMunicipality.cs @@ -0,0 +1,33 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityWasCorrectedToCurrent; + +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityWasCorrectedToCurrent( + registeredEvent.MunicipalityId, + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Current); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToRetired/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToRetired/GivenMunicipality.cs new file mode 100644 index 000000000..b621f61ef --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasCorrectedToRetired/GivenMunicipality.cs @@ -0,0 +1,35 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityWasCorrectedToRetired; + +using AutoFixture; +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityWasCorrectedToRetired( + registeredEvent.MunicipalityId, + Fixture.Create(), + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Retired); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasDrawn/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasDrawn/GivenMunicipality.cs new file mode 100644 index 000000000..90365ca2d --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasDrawn/GivenMunicipality.cs @@ -0,0 +1,34 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityWasDrawn; + +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityWasDrawn( + registeredEvent.MunicipalityId, + "01030000208A7A000001000000F4040000001283C098B7F84000894160F7D403410052B81E29B4F84080ED7C3F28D603410076BE9F8AB3F840008941601CD603410040355EDCB2F840809BC420C5D5034100643BDFA1B0F840809BC42062D50341001283C040ACF840801283C095D5034100C0CAA12FABF84080ED7C3F9AD503410052B81E1FAAF840801283C08AD503410040355E0EAAF8400000000007D503410040355E0EAAF84080ED7C3F75D40341001283C008AAF84080ED7C3F10D4034100388941B6A9F840801283C08ED303410024068157A9F840008941609FD3034100AE47E14AA9F84000000000A4D30341009CC4209CA8F8400077BE9FD2D303410052B81E51A7F8400077BE9F3ED4034100388941B2A5F840809BC420DAD4034100C0CAA1C9A3F84080C876BE9DD503410076BE9F70A3F84080643BDFD0D5034100643BDF5DA2F8400000000096D50341002EB29D45A0F840803BDF4F10D5034100AE47E19C9DF8400077BE9F1CD6034100C876BEA99BF84000250681E6D60341003889412E99F8400077BE9F44D8034100000000F697F84080ED7C3F09D9034100AE47E1A490F8400077BE9F61D4034100EE7C3F6790F84080643BDF96D40341004A0C02DB8FF840801283C0FBD40341009CC420768FF840809BC4203FD50341004A0C02A18EF8400077BE9F9BD503410040355EFE8DF84000000000EDD503410040355EFC8CF84080ED7C3F5AD603410040355EDC8AF8400077BE9F3AD7034100643BDF8B89F84080C0CAA1C9D703410076BE9F6288F8400000000045D80341009CC420DA86F84080ED7C3FEDD8034100EE7C3FC785F84080ED7C3F6ED9034100C0CAA18784F84080643BDFF4D9034100AE47E17282F840809BC4202CDB0341004A0C028B80F840809BC4202EDC034100643BDFB17DF840801283C0EEDD034100AE47E1F87CF840801283C06FDE03410040355EE27CF84000000000C1DE0341001283C0C07CF840002506811DDF034100EE7C3FD57BF8400089416093DF034100C0CAA1CD7AF840809BC42017E0034100000000AA79F840801283C0B9E00341009CC420FC78F8400025068113E1034100EE7C3F7977F84080643BDF37E1034100AE47E17C76F840801283C064E1034100643BDFDD74F84080ED7C3FB3E10341001A2FDD4074F84000000000D5E1034100AE47E19876F840801283C023E50341008A4160E977F84000000000DCE60341008A4160EB78F8400077BE9FD5E70341004A0C02B979F840801283C091E8034100C876BEF179F84080ED7C3FBFE80341001283C03279F840002506813EE90341004A0C02FB77F8400077BE9F13EA034100643BDFDB74F84000000000E9EB0341008A41602B72F840801283C04AED034100DCF97E706EF840809BC420EBEE034100C0CAA1FD6AF840000000000CED0341001283C07C69F84080643BDFA2ED034100643BDF3F68F84080643BDF26EE0341003889413067F840809BC420BAEE034100AE47E15C66F8400000000073EF034100EE7C3F4D65F8400077BE9F60F0034100C0CAA13D64F840801283C037F103410040355E6A63F840809BC4201AF20341001283C05463F84000DBF97E97F2034100AE47E11E63F8400077BE9F4BF3034100C0CAA1F162F84080ED7C3FD7F30341009CC420A662F840801283C053F40341008A4160CB61F84080ED7C3F23F50341009CC4205A61F84000250681E7F5034100643BDFE560F84080643BDFD6F603410052B81EED5FF8400025068120F8034100643BDFC15FF8400077BE9FB5F80341006E1283A45FF840801283C074F90341008A4160EF5EF84080C876BE6DFA03410076BE9F765EF8400025068117FB034100388941945DF84080643BDF75FB0341009CC4202A5CF840809BC420F6FB03410052B81EED5AF84080643BDF90FC034100240681DD59F840008941603EFD034100DCF97EA058F8400089416006FE034100643BDFE357F84080ED7C3F3BFE03410040355E3657F840801283C042FE034100AE47E19E55F840000000004EFE034100DCF97EBC54F84080ED7C3F6CFE034100AE47E1A854F840801283C0C8FE034100C0CAA1CB54F84080643BDF28FF034100365EBAE954F8400089416074FF03410076BE9FC455F840801283C056000441008A41603D56F840801283C0FC000441006E12835E56F84080ED7C3F70010441008A41600D57F840008941602602044100C0CAA1BD56F84000DBF97E4C020441009CC4207256F840801283C06A0204410052B81EBD55F840002506819F0204410040355E5A54F84080ED7C3FC50204410076BE9FE852F84000894160E302044100643BDFA152F84000000000560404410040355E5E4DF840801283C05304044100C0CAA1654DF8400077BE9FD8030441008A4160474DF840000000006F030441008A4160F34CF84000894160F70204410040355EF04CF84080643BDFC402044100388941944CF84000000000270204410040355E244CF84080643BDF6401044100388941FC4AF840809BC420C3FF0341001283C0DA49F8408037894121FE03410076BE9F5648F840801283C0D7FB034100DCF97E2E47F8400000000015FA03410040355E9249F84000250681BFF90341001283C0664DF84000250681A8F903410076BE9FCA4FF8400000000081F9034100AE47E15450F840801283C045F90341004A0C02B150F84080643BDF0DF9034100C0CAA11351F8400000000038F80341001283C0BE51F8400077BE9F23F7034100643BDFF951F8400000000089F6034100EE7C3FC551F84000250681CDF5034100DCF97E3451F840005C8FC23CF5034100EE7C3FCB50F840801283C07AF403410076BE9F9051F84080643BDFBBF30341001283C0B852F840008941606CF2034100C876BE3553F8400025068147F103410040355E9E52F84080B6F3FD08F103410076BE9F2853F8400089416029F00341001283C09453F84080C420B0DAEE0341004A0C02A953F84000250681CFEE034100C0CAA1C553F8400077BE9FBFEE034100EE7C3F0D54F8400025068167EE034100643BDF3754F840801283C0DCED0341004A0C023F54F840809BC42019ED0341004A0C02FF53F840008941608EEC0341009CC420C653F8400000000032EC0341001283C07053F840801283C0E3EB034100643BDFD153F84080643BDF79EB0341004A0C02F553F8400077BE9F3FEB034100240681DB53F84000250681FCEA0341009CC4208653F84000894160CEEA034100DCF97E2253F84080ED7C3F8EEA034100643BDF3753F840801283C04AEA034100643BDF7753F8400089416015EA03410054E3A5A953F84080C0CAA1A3E90341004A0C02FF53F84000A4703DF5E80341001283C0B053F8400077BE9F8AE803410052B81ECD51F84080643BDFBFE603410076BE9F7E4EF840801283C0C6E3034100EE7C3F294EF840000000009CE3034100C876BEC54DF84080643BDF6DE3034100EE7C3F694DF84080ED7C3F4AE3034100AE47E1DA4BF840809BC42043E3034100EE7C3FF74AF84000250681DFE2034100AE47E15A4AF84000894160B1E203410076BE9FDA47F84080ED7C3FB1E10341009CC420BE46F8400025068126E10341006E1283DA45F84000894160BFE0034100C876BEEF44F8400025068106E00341003889414C44F840008941609FDF0341009CC4200C44F840809BC420F1DE0341001283C0F642F84000250681CDDD034100AE47E1C441F84000250681B4DD0341003889419A41F840809BC4203FDD03410040355EA83FF840801283C0E9DB03410052B81EE13EF8400089416074DB034100AE47E1D23DF840809BC420E6DA034100EE7C3F6F3CF84080643BDF17DA034100DCF97E443BF8400052B81E8DD903410052B81E6F3AF8400025068142D90341004A0C026F39F84000000000C6D8034100643BDF2739F8400077BE9FF0D7034100000000B638F84000000000AAD70341008A41603F38F840801283C01ED70341000000006A37F840809BC42018D6034100643BDF5D36F840801283C037D5034100AE47E13035F84080B6F3FD51D40341000000001633F840002506816CD3034100A4703DF232F8400077BE9F46D3034100643BDFF732F8400077BE9F83D2034100EE7C3F5932F840008941603BD10341008A41607331F840002506812CD003410052B81E0531F84000894160A5D0034100AE47E1A030F84080643BDFE0D003410040355E402FF840002506810AD10341001283C0922DF8400077BE9F4CD1034100240681052DF840002506815DD103410040355E3A2CF840801283C084D10341008A4160612BF84000250681BED103410052B81E432AF84080ED7C3F08D20341009CC4206A29F8400000000049D20341008A41607528F840809BC42095D203410076BE9F0628F84080643BDFC7D2034100AE47E1AE27F84080ED7C3F06D303410040355E5C27F84080643BDF57D303410040355ED426F84080ED7C3F31D4034100C0CAA17B26F84000250681EBD4034100643BDF4B26F840809BC420F2D4034100EE7C3FD125F84080ED7C3F03D5034100240681A525F84080643BDF09D5034100C876BE4524F840008941603FD5034100C876BEAF21F840809BC42098D5034100EE7C3F4520F840801283C08DD503410076BE9FC81FF840809BC4209FD503410052B81E471EF84000000000E2D503410040355E061EF84000894160F3D50341003889413E1DF84080643BDF6BD6034100C0CAA18B1BF840002506816AD7034100EE7C3FB71AF840005C8FC2EBD703410052B81E131AF8400077BE9F52D8034100EE7C3FC719F8400025068186D80341001283C02819F840002506813BD9034100EE7C3FE118F84080643BDF6CD9034100000000BC18F840008941607FD9034100AE47E11818F84080643BDFCAD9034100AE47E1B616F84000AE47E1C3D90341004A0C02DB15F84000250681C0D9034100EE7C3F3F14F84000000000B7D9034100C0CAA1E911F840801283C083D9034100C876BE050FF8400000000034D90341008A4160A90DF840000000000CD9034100EE7C3FE30AF84000250681BAD80341001C5A64A908F84080ED7C3F7BD80341008A4160E301F84080643BDFAFD70341000000008801F84000250681A2D70341008A4160FBFDF74080643BDF44D7034100DCF97EFCFBF74080643BDF3BD70341000000009CF6F740801283C0F7D403410024068145F4F74080ED7C3FF5D3034100388941C8F2F740000000005CD30341004A0C0265F1F74080643BDFB1D20341001283C044F0F740801283C002D2034100DCF97E30EFF740809BC42060D1034100C876BE85EDF7400025068118D00341004A0C02FFEBF74000000000F2CE0341002406811BEBF740002DB29DF0CD034100DCF97EE0EAF740801283C051CD0341004A0C02A9EAF7400077BE9F88CC0341004A0C028DEAF7400000000074CB034100A69BC452EAF74080ED7C3F70CB03410052B81ED5E7F740801283C046CB03410038894102E6F74080ED7C3F21CB03410052B81E9DE3F74080ED7C3F2DCB034100388941C2E1F7400089416063CB03410052B81E2DDFF74000250681D2CB0341008A416093DEF74080643BDFC6CB034100D24D6274D3F74000DBF97ED3CA0341004A0C023FD2F74080ED7C3FB7CA034100643BDF8FD1F74080ED7C3FBFCA03410076BE9F4CD0F740801283C0DFCA034100C876BE09CFF740801283C011CB034100DCF97E00CEF74080ED7C3F62CB034100643BDF37CDF74080ED7C3FB1CB034100C876BEC1CCF740801283C0E8CB034100C0CAA1F1C8F74000000000A0CD0341008A41605BC7F74080ED7C3F53CE03410076BE9F44C6F7400077BE9FCACE034100F853E31DC5F740809BC42052CF03410040355E84C3F74000894160DECE034100EE7C3F5BC0F74080643BDFBFCD03410076BE9F90BBF740801283C0A4CB034100E6D02287B7F740809BC420A4CA034100D022DBE5B5F740809BC42050CA0341009CC420C4B2F7400000000062C903410024068139B2F740809BC42047C90341000000009CB1F74080ED7C3F01C9034100EE7C3F85B0F74000250681C1C8034100DCF97E66AFF7400077BE9F9DC80341004A0C026BABF740801283C08DC803410052B81EC7A7F7400077BE9FA9C803410052B81E01A6F7400077BE9FB5C8034100643BDF8BA3F74000250681C1C8034100C876BEA39CF740801283C089C80341009CC4202495F740000000005AC80341009CC4206E93F740809BC42042C8034100643BDF1990F7400077BE9FCEC70341004A0C02878BF7400089416017C703410052B81EB189F740801283C0CFC60341004A0C021388F74080643BDFB7C60341008A41602F84F740801283C0BBC603410052B81EE380F7400077BE9FEBC6034100C876BE8D7EF7400025068107C70341004A0C02B17BF74080ED7C3F33C7034100EE7C3F7B7AF74080ED7C3F4BC7034100C0CAA1D379F74080643BDF6BC70341004A0C022D78F74000000000C4C7034100EE7C3FE175F74080ED7C3F4DC803410052B81E0D75F740801283C07AC8034100DCF97E7C74F740801283C099C8034100C0CAA12773F74080643BDFE2C803410040355E7872F74000DBF97E30C9034100C0CAA18771F740008941606EC9034100C876BE2D6CF74080ED7C3F90CA0341004A0C02D967F74080643BDF78CB034100240681AD66F740809BC4209FCB03410076BE9F6865F74000000000AFCB03410076BE9F2061F74080ED7C3FA2CB0341002406816D5BF7400089416092CB034100DCF97EF256F740801283C0DECB034100643BDFD355F74000000000D2CB03410052B81EA353F7400077BE9FA8CB034100C876BE4551F7400077BE9F42CB0341001283C01E4CF74000250681FAC9034100000000BE49F740008941605EC903410004560E0344F74080C420B05FC8034100DCF97EF843F7400077BE9F5CC8034100DCF97EF041F7400025068122C80341004A0C02993FF74000000000D3C7034100643BDFF53DF7400025068183C7034100000000063CF740801283C017C7034100AE47E1103BF74000250681E2C60341007E6ABCCC3AF74000250681D1C60341001283C03C3AF74000D34D62ADC603410040355E5838F740801283C0DEC503410040355E3235F7400077BE9FA0C4034100C0CAA1AD33F7400089416024C40341009CC420D631F74080643BDFB4C3034100388941D62FF740801283C065C30341005A643B6D2EF7400077BE9F38C30341004A0C02BD2BF740801A2FDDE8C20341004A0C02B729F740801283C0C0C20341008A4160AF28F740809BC420DEC2034100C0CAA18F23F7400077BE9FE4C20341002406814320F74080ED7C3F9DC20341000000003C1EF74080643BDF48C20341008A41609117F740809BC42038C0034100AE47E1540FF7400089416079BD034100000000140EF7400052B81EFFBC0341004A0C02830BF74000894160FBBB03410076BE9F3E05F7400025068190B9034100AE47E19600F74000000000F4B7034100EE7C3FBDFEF6400025068116B7034100240681B9FDF6400025068157B603410040355ED4FCF64080643BDF90B503410040355EFEFBF64080643BDF12B403410076BE9FFAFAF64000250681B3B203410024068115FAF64000250681F4B1034100EE7C3FF3F8F64080643BDF81B1034100AE47E130F1F64000894160E5AF034100DCF97E66EFF64080643BDFAFAF03410076BE9FE8EDF64000250681B7AF0341008A416007EAF64080643BDFC2B003410024068155E1F640809BC42079B3034100240681CBDEF64080643BDF8AB4034100C0CAA1E9DBF64080ED7C3FB5B5034100643BDF17DAF64080ED7C3F31B603410038894192D8F64000AE47E179B6034100C0CAA1F9D5F64000250681ABB603410076BE9FC0D2F6400077BE9FE8B60341008A416033D1F6400000000035B7034100DCF97EF2CFF6400025068194B703410076BE9FF2CDF640000000005FB803410076BE9F4ACDF64000894160D5B8034100DCF97E24CBF6400089416053BA03410076BE9F4ECAF64080643BDFDCBA0341004A0C02C9C8F640803F355E66BB034100AE47E1FAC5F640801283C0DCBB034100C876BE63B5F640801283C015BD0341004A0C029DB1F640801283C079BD034100DCF97ED6B0F64080643BDF9EBD034100000000A4B0F64000250681D4BD03410052B81E1FABF64080643BDFF9BD034100EE7C3F33AAF64080643BDF0CBE03410040355E38A9F64080643BDF0CBE034100000000D0A7F6400077BE9F09BE034100643BDFE1A6F64080643BDF0CBE034100643BDF4DA6F6400089416023BE0341008A4160EDA5F64000AE47E139BE034100000000C4A3F6400077BE9F98BF03410076BE9FF4A1F640000000009FBF0341001A2FDDF69EF64080E5D02282BF0341006CE7FB5B99F640801A2FDD7EBF03410040355E7C97F640000000006BBF0341001283C06E94F640000000000BBF034100643BDF7393F640000000000BBF0341001283C01E92F64080643BDF17BF034100EE7C3FC390F640809BC4201BBF0341004A0C02218FF64080C876BE14BF0341001283C0128EF64000D34D622EBF03410076BE9F5E8DF640801283C041BF0341001283C0908CF6400052B81E65BF034100EE7C3F678AF64080643BDFE5BF034100643BDF7788F640809BC42060C00341001283C0A085F64080643BDF1DC10341002406812B84F640008941606EC1034100DCF97E9C82F64000250681ABC10341001283C00081F640809BC420E2C1034100BE9F1ADF7FF64080ED7C3FF2C1034100365EBA497EF64080643BDFFBC1034100643BDF537CF64080643BDFFBC1034100643BDF3579F6400000000029C20341004A0C02B175F6400000000056C2034100C0CAA15574F640008941605EC2034100C876BE0D70F640809BC42026C1034100000000D26CF64000000000BBC00341009CC420506AF640801283C009C00341002406812B6AF64000894160FFBF03410076BE9FB469F64080643BDFD6BF0341009CC4203669F64000000000A6BF03410076BE9FF068F6400000000090BF0341002406812D68F640801283C047BF0341005C8FC2F567F6400025068137BF0341004A0C021F67F640809BC420E7BE034100240681E166F64000894160D4BE0341009CC420EA65F6400077BE9F9FBE034100C876BE9965F640809BC42092BE034100C0CAA1C564F640809BC4206ABE0341002406817764F64080ED7C3F5FBE0341003889417E63F6400077BE9F3BBE034100000000585FF64000000000BCBD0341008A41601F5EF6400089416096BD034100C876BE035DF640809BC42068BD03410040355E505CF6400077BE9F59BD034100F6285CED5BF6400077BE9F53BD0341009CC420325BF64080ED7C3F39BC0341005C8FC25D58F640809BC42040B9034100C876BE6D57F6400077BE9F13B8034100D022DBE356F64080C876BE54B7034100AE47E16256F64080643BDF16B7034100C0CAA1C155F64000000000AFB6034100DCF97ED254F6400077BE9F27B603410040355EB453F6400025068130B5034100643BDFD752F6400077BE9F84B40341002406814152F64000894160EEB3034100DCF97E1E51F64000250681E6B2034100C0CAA1EB4FF640809BC4206AB103410040355E4A4FF64080643BDF1DB00341003889415E4FF64000250681A9AF034100AE47E1A24FF64080ED7C3FE3AE0341009CC4203850F6400089416002AE0341002406811B51F64080ED7C3FB3AC03410052B81ED551F64000894160BBAB034100388941C251F6400089416033AB03410040355EE051F640000000001DAA034100B81E853B52F6400000000097A90341002406811953F640801283C053A90341004A0C029153F640803F355EBDA80341002406814555F64080ED7C3F40A7034100EE7C3FE556F6400052B81E00A60341002406813B59F640008941601CA4034100C0CAA1715BF6400077BE9F82A2034100000000DC5BF640801283C0F2A103410076BE9FB85CF64080ED7C3FFEA0034100000000945EF64000894160049F03410040355E0661F64080ED7C3FA99C034100C876BEF163F64000000000E8990341004A0C02C965F640809BC4200E98034100DCF97EFA67F640803F355E14960341008A4160336AF640809BC4205A940341009CC4203C6CF64000894160B8920341003889410A6DF640002506819F920341004A0C02CF6EF64000894160E490034100643BDFF770F64000894160A48E034100EE7C3FBD75F640809BC4204B8A034100AE47E1167AF6400077BE9F7F86034100643BDFD575F64000250681D4850341000000002C71F64080ED7C3F11850341002406811D72F640801283C075820341004A0C023B73F6400077BE9F897F034100D24D628673F640809BC4205F7F034100C0CAA18D73F64080ED7C3F667D034100C876BE5573F64000250681697A0341004A0C023D73F64000894160D6780341001283C00673F64080ED7C3F20780341004A0C020372F640809BC4205476034100DCF97EEA71F64080643BDFE1750341008A4160DB71F640000000000B750341002406814971F640008941603374034100EE7C3F1571F640002506818773034100388941CE70F640002506813673034100EE7C3F2F70F6400077BE9F8D72034100DCF97E1C70F640809BC4203672034100C0CAA1F96FF640008941609271034100240681836FF640002506811971034100000000646FF64000000000CE70034100AE47E1A26EF640809BC420F16F034100EE7C3F696EF64080643BDF2B6F034100643BDFFD6DF64000250681E46E034100DCF97EBA6DF64080ED7C3FA56E034100C876BEE16DF64000894160846E03410076BE9F846EF64000250681686E034100643BDFAB6EF6400077BE9F476E0341002406813B6EF6400077BE9F7D6D0341004A0C02276EF640008941601E6D03410040355E006EF640005C8FC2B06C0341001283C0D26DF64080ED7C3F476C034100C0CAA19F6DF640008941600C6C03410052B81E396DF64080643BDFDB6B0341001283C08A6CF64000250681566B034100DCF97E026CF64000250681AC6A0341009CC420DA6BF64080ED7C3F376A034100000000106CF64000894160CC690341004A0C02056DF64080643BDFA868034100000000D46DF64080ED7C3F1768034100240681716DF6400077BE9FF9670341001283C0F66BF6400000000083670341001283C03C6AF64000000000046703410040355EAC68F64080ED7C3F7B660341002406813767F64000250681FC65034100C0CAA19F66F640002506810466034100C0CAA14966F640002DB29D916503410040355EC065F6400089416006650341008A41600B65F6400077BE9F6264034100AE47E1FE64F64000894160E0630341002406812D63F64000000000CF62034100EE7C3FDF62F64000000000396203410076BE9F5462F64000250681EF610341001283C02661F640801283C00C610341001283C0FE5FF64080643BDF2160034100000000505FF64000250681535F03410040355E1C5EF640809BC420EA5D034100643BDF815DF64080643BDF7A5D0341001283C08A5CF640801283C0085D0341009CC420B85CF64080ED7C3FBD5C034100000000EC5AF640008941607D5B03410040355EAC58F64000000000F4590341009CC420F05CF64080ED7C3FBA570341001283C0D25BF640801283C09856034100AE47E1725BF64080C876BE8855034100000000B05BF64080ED7C3F7B55034100EE7C3FEB5BF640002506814C5503410040355E865CF64080643BDFE754034100DCF97EE05CF640809BC420BA540341009CC420785DF64080ED7C3F815403410052B81ED55DF640000000006D540341000000003C60F640008941604B54034100EE7C3F2960F6400077BE9F17540341009CC4206861F6400077BE9F055403410008AC1CCC63F64000000000D653034100C0CAA1AF67F640801283C050530341002406810B68F640002506814E530341001283C0426BF64080643BDF1853034100C0CAA1576DF64080ED7C3F2753034100AE47E1BA6FF6400077BE9F3B53034100240681B570F6400077BE9F4553034100240681A771F640002506816453034100F853E3C774F64000000000B3530341001283C0D078F640008941603054034100C0CAA18F79F640801283C01E540341001283C0CE7AF64080643BDF0D54034100C0CAA1D97BF640000000000554034100DCF97E207DF640002506812454034100643BDFE17DF6400025068143540341003889410280F6400077BE9FB65403410076BE9F8481F640000000000955034100AE47E1BA83F640801283C08255034100643BDF8B85F640801283C0DD550341000000008C86F64080643BDF0D56034100643BDFFD86F64080643BDF2B560341008A41600787F640801283C0AB5603410076BE9FF486F64080ED7C3F20570341002406812B87F64000000000495703410040355EAE87F64000250681C257034100C0CAA1F987F640000000001B580341001283C02688F64000AE47E18158034100DCF97E5C88F6400077BE9FA558034100643BDF438AF64080643BDFE55803410052B81E318DF640801283C03559034100C0CAA1F18DF64080ED7C3F37590341004A0C02618EF64080643BDF25590341009CC420B68FF640809BC420C858034100EE7C3F1590F64000894160B2580341004A0C027790F64080643BDFD8580341003889419092F64080643BDF46590341004A0C02A792F64000250681355903410076BE9F9098F640809BC4207A5A034100240681399AF64000250681CE5A034100EE7C3FC19AF64000894160325A034100643BDF6DA1F640803F355EA6590341004A0C0205A2F64080643BDF9A59034100240681B3A7F64080ED7C3F235903410052B81E2FACF64000000000B15803410040355E66AEF64080ED7C3FF356034100C0CAA1A3B1F64000250681795403410040355EF8B2F64080ED7C3F6D53034100DCF97E4AB6F64000250681CB500341001283C004B7F64080643BDF0F50034100DCF97EA0B7F640809BC420344F034100C0CAA1E5B8F64000250681024E034100DCF97ECABBF64080ED7C3F534B034100AE47E19EBDF640801283C0A649034100EE7C3FF7BFF64080643BDF014A034100C0CAA12FC2F64080643BDF4F4A034100EE7C3FDFC2F640000000001549034100DCF97E36C3F64000894160CB4803410052B81EB1C3F64000D34D629448034100EE7C3F19C5F64000000000224803410000000010C6F64080643BDFAE47034100AE47E11AC7F640809BC420F9460341004A0C0213C8F6400077BE9FA74603410024068169C8F64080643BDF6F46034100EE7C3FA3C8F640002506814F4603410040355E7ACAF640801283C01246034100000000F8CAF64000894160F8450341003889417ACBF640809BC420D045034100DCF97E6CCCF64080ED7C3F71450341009CC4200ACDF64000000000454503410040355E62CDF640000000002645034100000000E8CDF64080643BDFDD44034100388941AACEF6400077BE9F994403410000000044CFF64080643BDF7044034100C0CAA1F5CFF64080643BDF554403410076BE9F48D1F6400077BE9F2844034100000000B0D1F640000000000B440341001283C004D2F6400077BE9FD043034100643BDF57D2F6400077BE9FC0430341009CC420A2D2F64080378941C343034100EE7C3FB1D3F64000894160EA430341008A416023D4F64080ED7C3F1744034100643BDFABD4F640000000003044034100EE7C3F41D5F64000000000414403410076BE9FE0D5F640008941608F44034100C0CAA18BD6F64000000000E944034100C876BE05D7F640002506810D4503410000000084D7F64080643BDF3445034100AE47E1DED7F64000DBF97E3B4503410024068125D9F640008941603A45034100EE7C3F85D9F6400025068141450341009CC42068DAF6400077BE9F714503410040355E62DBF640806E128399450341004A0C0257DCF6400077BE9FA44503410054E3A5E5DCF640008941609C45034100C0CAA10FDDF64000250681B8450341004A0C0299DDF64000894160DE450341001283C066DEF64080ED7C3F0F4603410052B81E4DDFF6400077BE9F4346034100C0CAA11BE0F640008941605446034100EE7C3F33E3F64000250681994603410052B81E01E4F64000000000BB46034100000000E4E5F6400077BE9F1847034100C0CAA18FE6F64080ED7C3F5247034100C0CAA14BE7F640801283C09847034100EE7C3FD3E7F6400077BE9FC347034100240681F5E8F640809BC420064803410040355E72E9F6400089416031480341009CC42044EAF6400077BE9F7448034100C876BED5EAF64080643BDF90480341004A0C026FEBF64080ED7C3F9E48034100643BDFFFEBF640801283C08F480341008A416053EDF6400077BE9F594803410040355E3AEEF640000000002348034100C876BE8DEEF64080643BDFFB4703410040355EBCEEF64000000000FC47034100DCF97E52EFF64080ED7C3F3F48034100240681C3EFF64000894160604803410040355E0CF0F640801283C06648034100643BDF57F0F6400077BE9F65480341002406812DF1F6400089416043480341008A4160C3F1F640801283C05A4803410038894166F2F64080ED7C3F6A48034100643BDF49F3F640008941605B48034100AE47E1D6F3F6400089416050480341004A0C02A1F4F640809BC4207D4803410040355E46F5F64000894160B2480341009CC420DAF5F640801283C00449034100EE7C3F1DF6F640008941605049034100EE7C3F79F6F64000000000BD4903410052B81EB9F6F640803F355EE549034100AC1C5ACCF8F64000250681364A034100DCF97EE8FCF640806E1283E74A03410052B81EE9FFF640809BC420604A034100643BDFDD05F740002506817D490341009CC4208C0BF74080643BDFAB480341003889412A0DF740809BC4208E480341001283C0C00DF74000250681B2480341009CC420560EF7400077BE9FDA48034100EE7C3F0F10F74000250681F5480341008A4160C710F74080ED7C3FB848034100AE47E17A11F740801283C0CB48034100E4A59BEE11F74000250681BA480341001A2FDD4A12F740801283C05348034100EE7C3FAB12F740801283C05848034100A4703D4613F74080643BDF5B48034100C0CAA1BB13F7400077BE9F63480341009CC4206414F7400077BE9F95480341000000005815F74000D34D628848034100AE47E11616F74080643BDFCD48034100DCF97EA216F740809BC420C648034100C876BE5D17F7400077BE9FC348034100AE47E18E18F74080ED7C3FF7480341001283C0061AF740809BC4201A49034100C0CAA1731BF740801283C0B348034100643BDF811CF74080643BDF6548034100E6D022091DF74000D34D621248034100DCF97E621EF74080ED7C3FA5470341000000003020F740809BC4201847034100643BDF8D21F740809BC420A84603410076BE9FDC21F74080ED7C3F6B460341000000004C22F740809BC4202B46034100EE7C3FD322F74080ED7C3F26460341004A0C027F24F74080643BDF2946034100240681A925F740801283C01E460341001283C04A26F740801283C02646034100643BDF8727F740801A2FDDFC4503410008AC1CD228F740801283C0EA450341008A41604F29F7400077BE9FD045034100DCF97E462AF74000894160BB4503410076BE9FFC2AF740801283C0C44503410040355EC42BF74000D34D62ED450341001283C0542CF74080ED7C3F27460341002406810F2DF740809BC42022460341008A41601F2DF7400000000051460341004A0C02612DF7400025068183460341008A4160B32DF740809BC420844603410076BE9F242EF74080643BDF6246034100240681952EF740801283C07746034100C0CAA11F2FF740801283C0AC46034100388941622FF740809BC420E346034100AE47E1AA2FF74080ED7C3F344703410076BE9FFC2FF74080ED7C3F48470341004A0C02F130F7400089416053470341009CC4208631F740002506815E47034100C0CAA11B32F7400025068176470341000000008C32F7400000000073470341003889418233F740801283C04247034100CAA1450634F74080643BDF2F47034100DCF97ED234F740008941604B470341004A0C029F35F740008941604547034100643BDFCD35F740002506815347034100C876BE3936F74080ED7C3F8A470341000000009436F740809BC420804703410076BE9F5037F7400025068162470341009CC420EE37F740801283C06F47034100643BDFA938F7400089416091470341004A0C021739F74080643BDFD94703410040355E4A39F74080ED7C3FFB47034100C0CAA1DB39F7400077BE9F2148034100EE7C3F3B3BF74000250681AF480341009CC4205E3CF74000000000BC480341009CC420EA3CF740801283C0E2480341009CC4209E3DF740000000000F490341009CC420443EF740000000006449034100829543A93EF74000000000CC49034100240681013FF74000000000B9490341009CC4200040F7400077BE9FAD49034100DCF97E7640F740801283C0074A034100240681ED40F74080643BDF2B4A03410052B81E8143F74080ED7C3FFB4A0341001283C09447F740002506815E4C03410076BE9FA848F7400077BE9FD14C034100DCF97E084CF74080643BDF4C4E0341002406810D50F740801283C00D500341002406813150F740801283C00250034100DCF97EAC53F74000250681085103410040355E2A56F740809BC420A15103410052B81EF956F740809BC420A251034100240681B958F74000250681A251034100AE47E1725CF740801283C052510341004A0C02A962F740809BC420D750034100829543EB65F740002506819550034100C876BE3968F7400025068166500341002406817168F740000000007150034100AE47E1D668F740801283C05550034100B81E85EF6DF74080B6F3FD584F034100388941CE6EF74080643BDF294F034100000000086FF740002506812E4F0341004A0C022F6FF740809BC420714F034100DCF97E626FF7400077BE9F8D4F034100388941CA6FF740801283C0A24F0341008A41602370F74000894160CC4F03410052B81E4D70F74080B6F3FDE64F034100643BDF7D70F74080643BDFFB4F034100240681AB70F7400000000009500341001283C0F070F740809BC420F94F034100E6D0224571F74080ED7C3FEC4F034100EE7C3F1372F74080ED7C3FDF4F0341001283C03672F74080643BDFC14F0341001283C06E72F74000000000A44F0341004A0C02A972F74000250681A34F034100643BDFD374F7400077BE9FC24F03410076BE9F2476F74000250681D14F0341009CC4208A76F740801283C0F14F034100EE7C3F1177F740801283C070500341001283C0AC77F74080490C027A50034100AE47E11678F740803F355E605003410040355E8E78F7400077BE9F3E500341009CC420C078F74080643BDF3D50034100643BDFAD79F74000894160C35003410040355E2A7AF74000DBF97ECF500341009CC420D27AF7400077BE9F0051034100D022DB437BF740801283C0175103410040355EB67CF74080643BDFCF500341008A41605B7DF74000894160EC500341001283C0F47DF740801283C00051034100240681977EF74080643BDFFF50034100643BDFD17EF74000000000FB500341008A4160077FF74000000000F150034100AE47E1267FF74080ED7C3FE150034100C0CAA15D7FF74000000000B95003410076BE9FB47FF740002506816B500341001283C0CC7FF74080643BDF1450034100643BDF6F80F74080643BDFE24F034100DCF97EEA81F74000A4703D7B4F03410052B81E7182F74080B6F3FD644F03410040355E2683F740002506816A4F03410030DD24CA83F7400077BE9F7C4F034100000000B484F7400077BE9F8D4F0341008A41601B85F74000894160AB4F0341002406816B85F74080ED7C3FDD4F0341003889419E85F74080ED7C3FEB4F03410030DD243C86F740809BC420E54F034100388941EA87F74000250681DE4F0341001A2FDD6288F74000894160F84F034100EE7C3FA988F74000250681F14F03410040355E0C89F740801283C0F84F034100C0CAA13189F7400077BE9F0550034100643BDF4B89F740801283C0215003410040355E5489F740801283C04E5003410040355EA489F74080ED7C3F5850034100643BDFF389F74080643BDF725003410040355E3C8AF740809BC4208F50034100000000588BF74080643BDFC75003410040355E3C8CF740801283C0E450034100EE7C3F498DF74080643BDF8F50034100C0CAA1638DF74000A4703D7F50034100643BDFA38DF740002DB29D41500341001283C0F08DF74000894160C54F0341004A0C02418EF74080643BDFA14F034100240681678EF740008941608C4F0341009CC4206E8EF74080643BDF6A4F0341007E6ABC868EF740801283C04D4F0341004A0C02FD8EF7400077BE9F214F0341002406814B8FF74000250681FC4E0341007E6ABC728FF7400077BE9FD54E03410076BE9FB08FF74000250681A74E0341002406812990F74080ED7C3F904E03410076BE9F7091F74000894160634E03410076BE9FCC91F74080643BDF474E034100643BDF6992F74080643BDF214E0341001283C0B692F74080ED7C3FFF4D0341008A4160F392F74080ED7C3FEF4D034100DCF97E1E93F74000DBF97EE74D0341002406816B93F740809BC420E94D034100C0CAA11D94F740801283C0CB4D034100C0CAA11394F7400077BE9FA94D034100C876BE0194F740008941604E4D0341004A0C020794F74000250681294D034100EE7C3F6994F74000000000DE4C034100643BDFF994F740000000009F4C034100C0CAA11D95F74000000000AF4C0341004A0C024195F74080ED7C3FB14C034100643BDF9F95F74000250681A44C03410040355E0C96F74080643BDF894C034100240681C996F74080643BDF544C034100C0CAA1EB96F74080ED7C3F4F4C034100AC1C5A9497F740801283C0904C034100EE7C3FD197F74000000000A14C034100C0CAA1D998F740801283C0684C034100EE7C3F139AF74000894160224C0341001283C05C9BF74000894160D44B034100AE47E1869DF7400077BE9F534B0341004A0C02AF9DF74000250681444B034100000000C09DF7400077BE9F2D4B0341009CC420EC9DF7400077BE9FEA4A03410052B81E359EF74000000000774A034100DE24069D9EF74000894160CB49034100AE47E1FA9EF74080ED7C3F2E49034100C0CAA1459FF740809BC4208E480341001283C0549FF74080643BDF7B48034100EE7C3F9F9EF740809BC42024480341008A4160D39DF740008941604E470341009CC420569DF74080ED7C3FFE46034100C876BEF59CF74080643BDFBC460341009CC4203A9DF74000250681B1460341009CC420E89DF74080ED7C3F7E460341004A0C02779EF740809BC4203346034100DCF97E989EF740000000001246034100240681999EF7400000000013450341001283C0689EF74080ED7C3F96440341004A0C02199EF7400025068129440341004A0C02399EF740002506811A44034100C876BE499EF740002506810D440341001283C07E9EF7400077BE9FBD43034100388941A29EF740801283C07D43034100DCF97EA29EF740002506816343034100EE7C3FAD9EF74000000000594303410052B81ECD9EF74080643BDF534303410076BE9FF49EF740809BC42058430341001283C0309FF740002506815D43034100EE7C3F639FF74080ED7C3F57430341009CC420989FF740801283C04643034100240681BF9FF7400077BE9F2C4303410040355EDC9FF740008941601343034100C876BE0DA0F74080643BDFE3420341008A41602FA0F74000894160B2420341008A416057A0F740801283C063420341004A0C025FA0F74080643BDF2D42034100EE7C3F65A0F740809BC420E24103410000000058A0F74000894160964103410040355E64A0F74000A4703D6941034100C0CAA19BA0F740809BC4203741034100DCF97EEAA0F74000000000034103410076BE9F38A1F74000250681DB400341004A0C028DA1F74000250681C84003410052B81EE9A1F740809BC420BF400341008A41606BA2F7400077BE9FB4400341001283C020A3F74000250681B540034100643BDF29A3F74080643BDFA540034100EE7C3F63A4F74080ED7C3FF13F034100EE7C3F8FA5F74080C876BE403F03410040355E76A7F740809BC4202A3E03410040355E5AA8F740002506819E3D03410008AC1C34A9F74080643BDF133D0341001283C0AAA9F7400077BE9FF13C034100240681DFA9F740801A2FDDC23C034100E6D02279AAF74080ED7C3F6D3C0341001283C060ACF7400077BE9F5A3B0341001283C0D8ACF74080ED7C3F6F3B034100AE47E10AADF74000000000AB3B034100EE7C3F4BADF74000250681AB3B034100C0CAA179ADF74000250681A03B03410052B81E55AEF74000000000403B034100C876BE6DAFF74000D34D62B73A034100365EBA3DB1F74000000000E939034100EE7C3F3BB2F7400077BE9F85390341000000008CB2F74000894160433903410076BE9FA4B2F7400077BE9F3F39034100AC1C5ACCB2F74000A4703D33390341009CC420F0B2F7400077BE9F11390341008A416033B3F74080ED7C3FD63803410000000080B3F7400000000099380341002EB29D0DB4F740002506814F38034100EE7C3F7BB4F740008941602C380341004A0C02FFB4F740000000000638034100EE7C3F57B5F74000DBF97EE037034100DCF97E90B5F74000250681BA37034100388941AEB5F740002506819B37034100DCF97ECAB5F740008941605637034100EE7C3FD3B5F74080C0CAA1F9360341001283C026B6F74000250681C436034100EE7C3F55B6F74000894160983603410024068173B6F740809BC4206636034100DCF97EB2B6F7400077BE9F0E36034100643BDFABB6F74080643BDFF535034100643BDFBFB6F74000250681E23503410076BE9FC4B6F74000000000D135034100C876BEB1B6F74080643BDFB3350341001283C0AAB6F740801283C0A435034100643BDFB5B6F740806E128397350341001A2FDDE2B6F740809BC4208E3503410052B81E09B7F7400077BE9F7C3503410076BE9F4CB7F740008941603E35034100EE7C3FADB7F740801283C0153503410040355E2CB8F7400077BE9FC13403410040355EA2B8F74080ED7C3F9B340341009CC420A8B8F740000000008B34034100388941A2B8F740000000007D3403410052B81E99B8F7400077BE9F6B34034100EE7C3FC5B8F74080ED7C3F39340341001283C0E0B8F7400077BE9F213403410024068109B9F74080643BDF2D3403410000000044B9F7400077BE9F163403410076BE9F80B9F740809BC4202034034100240681A5B9F740801283C0203403410052B81EE5B9F740801283C02D34034100643BDFF9B9F740809BC4202C340341004A0C0225BAF740002506812034034100DCF97E6CBAF740002506811F3403410024068185BAF74080643BDF1834034100643BDFA7BAF74000250681E133034100240681EFBAF74080643BDFAE33034100388941E6BAF740801283C08F330341001283C0FCBAF740801283C082330341001283C042BBF74000250681833303410040355EACBBF74080ED7C3F8D33034100AE47E1EEBBF740801283C09D33034100EE7C3F45BCF7400077BE9FA43303410024068187BCF740806E1283A733034100C876BE11BDF74000250681C433034100643BDF57BDF74080ED7C3FC53303410040355EE4BDF7400077BE9FC333034100C876BE2DBEF74000894160B4330341001283C04CBEF74000894160A43303410024068181BEF740809BC4208633034100EE7C3FABBEF740000000008333034100E4A59BC8BEF74080ED7C3F7E3303410040355EE8BEF7400077BE9F61330341003889410EBFF740801283C059330341001283C03ABFF74080643BDF6333034100EE7C3F79BFF74080ED7C3F79330341009CC42098BFF740801283C07933034100C876BED5BFF740000000006D3303410024068147C0F740002506812D33034100388941EAC0F740801283C0D832034100C0CAA1C3C1F740809BC4206F320341001283C01EC2F740000000004D320341004A0C0295C2F74080ED7C3F3E32034100EE7C3FF7C2F74080ED7C3F2B3203410040355E3EC3F740801283C0123203410024068135C3F74000250681B131034100C0CAA145C3F740000000008931034100EE7C3F8FC3F740809BC4204831034100AE47E18AC5F74000894160D22F03410076BE9FB4C6F740803F355EFA2E03410052B81EF1C6F7400077BE9FF62E0341002406812FC7F74000000000232F034100A4703D76C7F740002506812E2F0341001283C084C8F740809BC4202C2F0341005A643B55C9F74000000000012F034100AE47E1EAC9F74080ED7C3FDF2E0341009CC420A8CAF740809BC420D22E034100DCF97E4ACBF74000000000AF2E034100EE7C3FE1CBF740008941608E2E0341008A416003CDF740809BC420432E034100EE7C3F71CDF740803789413E2E034100EE7C3F19CFF74080ED7C3F572E034100C0CAA141CFF74080643BDF4E2E03410040355E88CFF74080643BDF212E034100F6285C57D0F74000000000B92D03410038894186D0F74080ED7C3F832D034100AE47E192D0F740809BC4207B2D03410052B81EB5D0F74080ED7C3F7C2D0341008A416063D1F74000894160B02D0341009CC4200CD2F74080ED7C3FE12D034100DCF97E6CD2F74080ED7C3FEA2D034100000000B0D2F74000000000F52D034100240681DDD2F74080ED7C3F002E034100643BDFFBD2F74080ED7C3F0D2E034100DCF97E12D3F740002506811E2E0341004A0C0239D3F740801283C03B2E034100EE7C3F99D3F74080ED7C3F552E0341001283C0C0D3F740000000006A2E034100EE7C3F2FD4F7400077BE9F792E0341003889418AD4F7400077BE9FA02E0341009CC420B0D4F7400077BE9F9F2E034100643BDFD7D4F740801283C0A32E034100C0CAA1FBD4F740809BC420AA2E0341003889412ED5F740809BC420C22E03410052B81E71D5F740801A2FDDC82E034100C0CAA19BD5F74080ED7C3F692E03410092ED7CBBD5F74000000000432E034100643BDF75D6F740801A2FDDF92D034100240681CDD6F74000894160D02D0341008A4160F3D6F74000250681C82D034100C876BE49D7F74080643BDFBE2D0341002406812FD8F74000894160882D0341001283C0ECD8F74080ED7C3F5A2D034100AE47E102DAF740000000001B2D03410040355E98DAF74000D34D62F22C034100EE7C3F3DDBF74080643BDFCE2C0341004A0C0275DBF74000000000BB2C034100AE47E19EDBF74000894160A72C034100643BDFC3DBF740801283C08D2C03410052B81E4DDCF74000250681422C0341001283C0F6DCF7400077BE9FDE2B0341005A643B1FDDF740809BC420BC2B0341004A0C0233DDF740002506818A2B0341009CC4204EDDF740801283C0752B034100C876BE65DDF740801283C0702B034100AE47E1C6DDF74080ED7C3F6D2B034100C876BEE1DEF740002506811C2B03410052B81E11DFF740801283C0122B0341002EB29D2FDFF74000000000162B03410076BE9F54DFF74000894160242B034100EE7C3F89DFF7400077BE9F662B0341004A0C02ADDFF740806E12837D2B034100B6F3FDD8DFF74000250681832B03410040355E0CE0F740008941607E2B0341004A0C0229E0F740801283C06E2B03410040355E6AE0F74000D34D624F2B0341009CC420C4E0F740008941601E2B03410000000028E1F74000250681172B03410076BE9F68E1F74080ED7C3F0E2B034100DCF97EB0E1F74000AE47E1F92A034100DCF97EEEE1F7400077BE9FD92A034100C0CAA147E2F740008941608D2A034100EE7C3F6BE2F740002506816E2A03410076BE9FA0E2F7400077BE9F512A034100240681C7E2F740809BC420442A0341004A0C02FBE2F74000A4703D352A0341001283C038E3F74080ED7C3F2F2A034100C0CAA189E3F74080643BDF3C2A034100643BDF41E4F74080643BDF512A03410030DD247AE4F74000250681522A0341001283C0D8E4F740809BC420382A0341009CC42078E5F740809BC420FA29034100C0CAA1B1E5F74000000000DB29034100C0CAA1C5E5F740809BC420B729034100AE47E112E6F740809BC420BD29034100000000A0E6F74000250681E7290341001283C046E7F74000894160E729034100240681CBE8F740809BC420B52A034100240681F9EAF7400077BE9F992B03410024068107EDF740809BC4206C2C0341003889419EEEF74000250681F52C0341004A0C02FDF0F74080ED7C3FB72D0341003889417AF1F740801283C0CE2D0341001283C038F5F74000894160EF2D03410040355E48F8F74000250681312E034100DCF97E98FBF74000894160422E0341005A643B23FDF74000250681562E0341004A0C0297FEF74000250681732E03410052B81E0101F840801283C0742E034100240681B305F84000250681442E0341004A0C02B706F84080ED7C3F352E0341004A0C02F70BF840002506810B2E034100AE47E1260CF84080ED7C3FEE2D0341005C8FC2250EF840801283C0FA2D034100240681A310F84000250681132E0341001283C0AC10F840002506815A2E0341001283C03811F840801283C0CC2E0341004A0C027913F84000250681F230034100DCF97E5414F8400077BE9FCA31034100C0CAA19114F84080378941D1310341004A0C02AF14F840008941601C32034100EE7C3F7314F840801283C01D32034100EE7C3F7514F84080643BDF4E32034100388941BE14F84080ED7C3FCB3503410040355ED014F8400077BE9FDD3603410052B81E1915F840002506816339034100AE47E13215F8400077BE9F783B03410040355E6215F84080643BDF4D3D0341003889418E15F84000250681253F03410076BE9FBC15F840801283C0F2400341001C5A64E715F840008941609B430341003889410816F840002506819F4303410040355E0C16F840809BC420B443034100C0CAA1EF15F84000D34D62BC430341002406814D16F84080ED7C3FE3460341009CC4209216F8400077BE9FA6490341009CC420C616F84000000000354C0341001283C0E816F84080ED7C3F834D034100240681FB16F84000894160114E0341003889411A17F840809BC420544F0341006ABC743917F840003333336450034100EE7C3F3F18F840008941607E500341001283C0C417F84080643BDFF151034100DCF97EE017F84080ED7C3F4D520341001283C02618F84080643BDFB3520341001283C0B218F84080ED7C3F2F53034100240681E519F84080ED7C3F1C54034100388941CE19F84000894160555403410040355ECA19F840809BC4208654034100365EBAF519F84000000000BD540341009CC420041AF840801283C001550341009CC4209C19F840809BC420925503410040355EA419F84080ED7C3FCF550341008A4160EF19F8400077BE9F5956034100000000C41AF8400000000074570341004A0C02D51AF84000A4703DF257034100C876BEED1AF84080643BDFA1580341002406818F1BF84000000000CB59034100643BDF0B1CF840809BC420DE5A03410040355E941CF840809BC420005C034100DCF97E021DF840809BC420845C034100AE47E11A1DF84080ED7C3FC75C034100EE7C3FAD1CF84080ED7C3FF95E0341000AD7A3841CF840809BC4206A600341002406815F1CF84000894160E161034100240681111CF840000000004D620341008A4160B31BF840002506818D620341004A0C02DB1AF840801283C0FD6203410052B81E851AF840801283C02E630341009CC420881AF840002506819263034100DCF97E4A1AF84000894160B8630341001283C0321CF840809BC4209E64034100000000D41BF840801283C0DA640341008A4160911BF84000250681FB64034100643BDF191DF8400077BE9FAF6503410040355E881FF8400077BE9FDB660341000000008420F840002506814D67034100DCF97E3221F8400000000081670341000000001C23F840002506813368034100643BDF2328F840801283C00E6A034100388941C22CF84000000000B56B034100EE7C3F2B2DF84000000000F16B03410040355EEC2EF840801283C0416D03410040355E622FF84080643BDF8F6D0341006CE7FB0F31F840000000003A6E0341002406816F38F840000000002D7103410052B81EF53DF84080ED7C3F6173034100AE47E1CA46F840809BC420ED760341008A41604F4FF840801283C0567A034100240681D952F84080ED7C3FC37B034100DCF97EA056F84080ED7C3F487D034100DCF97EA05AF84080643BDFE07E034100240681C35CF84080643BDFBD7F03410040355E0A60F840002506810C81034100EE7C3FA560F840008941604A81034100DCF97E7661F840000000009F81034100EE7C3F4D63F840000000005D820341004A0C022764F84080643BDFAE820341003889414264F84000894160DC820341001283C03C64F84000000000208303410040355EF663F840809BC4207A840341004A0C02DD63F840809BC42022850341002406819D63F84080643BDFF6850341009CC420A863F84000894160CD86034100C876BEBD63F840806E1283EF8703410076BE9FD063F840801A2FDD098903410076BE9FAC63F84080ED7C3F81890341001283C09263F840809BC4209D89034100DCF97E5663F840801283C0AE89034100B81E85E763F84080ED7C3F358A034100EE7C3F2165F84080643BDF1F8B0341004A0C023966F84000894160F18B03410008AC1C6667F84080643BDFFE8C034100C0CAA13968F84080ED7C3F538D0341004A0C02D569F840809BC420648E0341002406814F6AF840809BC4204F8F034100DCF97EB06AF840809BC4205E90034100C876BE816AF84080643BDF8D9103410040355E9669F84080643BDFB6920341008A41608B69F84080ED7C3FEF930341000000007C69F840008941601697034100365EBA6569F84080643BDF069A0341009CC420B468F840806E1283169A03410040355E1068F8400077BE9F399A0341009CC4201267F840809BC420A69A034100C876BED568F84000250681AB9B034100AC1C5AD468F840809BC420ED9B0341004A0C02C368F84000000000069C034100643BDFAB67F840809BC420739C03410040355EAC69F840801283C07A9C0341003889416E72F84000000000E79B034100643BDF5579F84000000000589B034100240681797CF84080ED7C3F2B9B03410076BE9FC47FF84080ED7C3F729B034100EE7C3FED82F840809BC420E19B034100643BDFC385F840801283C0549C03410076BE9FDC86F84080643BDFBD9C0341002406815B89F84000DBF97E8C9D0341001283C0528DF840809BC420019F034100AE47E16A90F84000894160C3A0034100643BDF599CF8400089416098A70341000000009CA3F84080ED7C3FC6AB034100C0CAA183A2F84080ED7C3F31AC034100AE47E1C2A0F8400025068121AD0341005A643B19A5F84000894160F1B0034100643BDFE1A5F840809BC420C1B1034100DCF97E24A5F84080643BDF05B203410052B81EA1A3F840002506815EB203410024068175A2F84000894160A9B2034100643BDFCBA0F840803F355EAEB2034100B6F3FD789FF8400077BE9FC1B2034100C0CAA19B9EF84080ED7C3FDDB20341001283C0EE9DF840801283C005B30341008A4160F39CF840000000005BB3034100388941BA9BF84080490C02B4B3034100643BDF4B9FF84080ED7C3FC1B403410024068171A0F840806E12832AB5034100829543159EF8400089416036B7034100C876BEDD9BF8400025068128B9034100EE7C3F179AF840801283C0C8BA0341002406810398F8400000000052BA034100388941A696F840002506819CBB034100D022DB3394F8400077BE9FDBBD0341002406812594F8400077BE9F08BE034100DCF97E749BF84080ED7C3F19C0034100643BDF6B9BF8400000000033C0034100C0CAA1BF9CF84000250681AAC0034100C0CAA19B9DF8400025068106C103410040355E049FF84080643BDF77C1034100EE7C3FF9A5F840809BC420ACC3034100643BDFFBA6F840801283C0EEC2034100C0CAA1C3A8F840000000008BC303410040355EC6B0F840801283C00EC6034100DCF97E96B7F8400089416046C80341001283C0BCB6F84000894160DFC80341004A0C02C9B2F840809BC42020CB0341008A416053ADF840809BC4203ECE034100D022DBD1AEF8400025068131CF034100DCF97E62B1F84000894160DCD003410052B81E1DB5F8400000000037D30341001283C098B7F84000894160F7D40341", + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Proposed); + actual.Geometry.Should().NotBeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRegistered/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRegistered/GivenMunicipality.cs new file mode 100644 index 000000000..c04a8310a --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRegistered/GivenMunicipality.cs @@ -0,0 +1,26 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityWasRegistered; + +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenCreated() + { + var @event = await GivenRegisteredMunicipality(); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(@event.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Proposed); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRetired/GivenMunicipality.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRetired/GivenMunicipality.cs new file mode 100644 index 000000000..31626ee13 --- /dev/null +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/WhenMunicipalityWasRetired/GivenMunicipality.cs @@ -0,0 +1,35 @@ +namespace RoadRegistry.SyncHost.Tests.Municipality.Projections.WhenMunicipalityWasRetired; + +using AutoFixture; +using Be.Vlaanderen.Basisregisters.GrAr.Contracts.MunicipalityRegistry; +using FluentAssertions; +using Sync.MunicipalityRegistry.Models; + +public class GivenMunicipality : MunicipalityEventProjectionTestsBase +{ + public GivenMunicipality(MunicipalityEventProjectionClassFixture projector) + : base(projector) + { + } + + [Fact] + public async Task ThenUpdated() + { + var registeredEvent = await GivenRegisteredMunicipality(); + + var @event = new MunicipalityWasRetired( + registeredEvent.MunicipalityId, + Fixture.Create(), + null); + + await Projector.ProjectAsync(@event); + + var actual = await Projector.Connection.Municipalities.FindAsync([@event.MunicipalityId]); + + actual.Should().NotBeNull(); + actual!.MunicipalityId.Should().Be(@event.MunicipalityId); + actual.NisCode.Should().Be(registeredEvent.NisCode); + actual.Status.Should().Be(MunicipalityStatus.Retired); + actual.Geometry.Should().BeNull(); + } +} diff --git a/test/RoadRegistry.SyncHost.Tests/Organization/OrganizationConsumerTests.cs b/test/RoadRegistry.SyncHost.Tests/Organization/OrganizationConsumerTests.cs index c69b2e5be..38d12f1e2 100644 --- a/test/RoadRegistry.SyncHost.Tests/Organization/OrganizationConsumerTests.cs +++ b/test/RoadRegistry.SyncHost.Tests/Organization/OrganizationConsumerTests.cs @@ -20,6 +20,7 @@ namespace RoadRegistry.SyncHost.Tests.Organization using SqlStreamStore; using SqlStreamStore.Streams; using Sync.OrganizationRegistry; + using SyncHost.Organization; using Organization = Sync.OrganizationRegistry.Models.Organization; public class OrganizationConsumerTests diff --git a/test/RoadRegistry.SyncHost.Tests/Startup.cs b/test/RoadRegistry.SyncHost.Tests/Startup.cs index 8d6605881..8e8ba2a57 100644 --- a/test/RoadRegistry.SyncHost.Tests/Startup.cs +++ b/test/RoadRegistry.SyncHost.Tests/Startup.cs @@ -7,6 +7,7 @@ namespace RoadRegistry.SyncHost.Tests; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; +using Sync.MunicipalityRegistry; using Sync.StreetNameRegistry; public class Startup : TestStartup @@ -27,6 +28,9 @@ protected override void ConfigureServices(HostBuilderContext hostBuilderContext, .AddStreetNameProjectionServices() .AddInMemoryDbContextOptionsBuilder() .AddInMemoryDbContextOptionsBuilder() + + .AddMunicipalityConsumerServices() + .AddInMemoryDbContextOptionsBuilder() ; } } diff --git a/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameEventTopicConsumer.cs b/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameEventTopicConsumer.cs index ae47473ef..5019c288e 100644 --- a/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameEventTopicConsumer.cs +++ b/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameEventTopicConsumer.cs @@ -1,5 +1,6 @@ namespace RoadRegistry.SyncHost.Tests.StreetName; using Sync.StreetNameRegistry; +using SyncHost.StreetName; public class InMemoryStreetNameEventTopicConsumer : IStreetNameEventTopicConsumer { diff --git a/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameSnapshotTopicConsumer.cs b/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameSnapshotTopicConsumer.cs index 47e2073b1..e1e3b5656 100644 --- a/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameSnapshotTopicConsumer.cs +++ b/test/RoadRegistry.SyncHost.Tests/StreetName/InMemoryStreetNameSnapshotTopicConsumer.cs @@ -3,6 +3,7 @@ namespace RoadRegistry.SyncHost.Tests.StreetName; using Infrastructure; using RoadRegistry.StreetName; using Sync.StreetNameRegistry; +using SyncHost.StreetName; public class InMemoryStreetNameSnapshotTopicConsumer : IStreetNameSnapshotTopicConsumer { diff --git a/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameEventConsumerTests.cs b/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameEventConsumerTests.cs index acc8c11b4..185fdbbb0 100644 --- a/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameEventConsumerTests.cs +++ b/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameEventConsumerTests.cs @@ -22,6 +22,7 @@ namespace RoadRegistry.SyncHost.Tests.StreetName using SqlStreamStore; using SqlStreamStore.Streams; using Sync.StreetNameRegistry; + using SyncHost.StreetName; public class StreetNameEventConsumerTests { diff --git a/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameSnapshotConsumerTests.cs b/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameSnapshotConsumerTests.cs index bfcece13a..01f067acb 100644 --- a/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameSnapshotConsumerTests.cs +++ b/test/RoadRegistry.SyncHost.Tests/StreetName/StreetNameSnapshotConsumerTests.cs @@ -23,6 +23,7 @@ namespace RoadRegistry.SyncHost.Tests.StreetName using SqlStreamStore; using SqlStreamStore.Streams; using Sync.StreetNameRegistry; + using SyncHost.StreetName; public class StreetNameSnapshotConsumerTests { diff --git a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs index c5dfa5edf..2cbf0aace 100644 --- a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs +++ b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs @@ -319,6 +319,16 @@ public static void CustomizeOrganizationKboNumber(this IFixture fixture) )); } + public static void CustomizeMunicipalityId(this IFixture fixture) + { + fixture.Customize(composer => + composer.FromFactory(generator => + new MunicipalityId(new string( + (char)generator.Next(97, 123), // a-z + generator.Next(1, OrganizationId.MaxLength + 1)))) + ); + } + public static void CustomizeOriginProperties(this IFixture fixture) { fixture.Customize(customization => From 559ab0fc2b2f1450e2f9bb96806317682a5281e0 Mon Sep 17 00:00:00 2001 From: Rik De Peuter Date: Wed, 30 Oct 2024 17:01:44 +0100 Subject: [PATCH 3/5] cleanup --- .../Core/EventSourcedEntityRepository.cs | 9 --- ...palityEventConsumerContextModelSnapshot.cs | 72 +++++++++++++++++++ src/RoadRegistry.SyncHost/.gitignore | 2 +- .../StreetName/StreetNameEventTopic.json | 8 --- .../BackOffice/SharedCustomizations.cs | 12 ++-- 5 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs delete mode 100644 src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json diff --git a/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs b/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs index dbb7e8172..361dd81a1 100644 --- a/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs +++ b/src/RoadRegistry.BackOffice/Core/EventSourcedEntityRepository.cs @@ -85,15 +85,6 @@ await message.GetJsonData(ct), return ConvertEntity(entity); } - public TEntity AddNew(TIdentifier id) - { - var entity = _entityFactory(); - var stream = _getStreamName(id); - _map.Attach(new EventSourcedEntityMapEntry(entity, stream, ExpectedVersion.NoStream)); - - return entity; - } - protected virtual TEntity ConvertEntity(TEntity entity) { return entity; diff --git a/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs new file mode 100644 index 000000000..83f7c6ed3 --- /dev/null +++ b/src/RoadRegistry.Sync.MunicipalityRegistry/Migrations/MunicipalityEventConsumerContextModelSnapshot.cs @@ -0,0 +1,72 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NetTopologySuite.Geometries; +using RoadRegistry.Sync.MunicipalityRegistry; + +#nullable disable + +namespace RoadRegistry.Sync.MunicipalityRegistry.Migrations +{ + [DbContext(typeof(MunicipalityEventConsumerContext))] + partial class MunicipalityEventConsumerContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "8.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("Be.Vlaanderen.Basisregisters.MessageHandling.Kafka.Consumer.ProcessedMessage", b => + { + b.Property("IdempotenceKey") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("DateProcessed") + .HasColumnType("datetimeoffset"); + + b.HasKey("IdempotenceKey"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("IdempotenceKey")); + + b.HasIndex("DateProcessed"); + + b.ToTable("ProcessedMessages", "RoadRegistryMunicipalityEventConsumer"); + }); + + modelBuilder.Entity("RoadRegistry.Sync.MunicipalityRegistry.Models.Municipality", b => + { + b.Property("MunicipalityId") + .HasColumnType("nvarchar(450)"); + + b.Property("Geometry") + .HasColumnType("Geometry"); + + b.Property("NisCode") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("MunicipalityId"); + + SqlServerKeyBuilderExtensions.IsClustered(b.HasKey("MunicipalityId"), false); + + b.HasIndex("NisCode"); + + SqlServerIndexBuilderExtensions.IsClustered(b.HasIndex("NisCode")); + + b.ToTable("Municipalities", "RoadRegistryMunicipalityEventConsumer"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/RoadRegistry.SyncHost/.gitignore b/src/RoadRegistry.SyncHost/.gitignore index 31863d18f..842c06479 100644 --- a/src/RoadRegistry.SyncHost/.gitignore +++ b/src/RoadRegistry.SyncHost/.gitignore @@ -1 +1 @@ -/*Topic.json +*Topic.json diff --git a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json b/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json deleted file mode 100644 index 4be152726..000000000 --- a/src/RoadRegistry.SyncHost/StreetName/StreetNameEventTopic.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "headers":{"IdempotenceKey": "1"}, - "offset": 1, - "key": "1", - "value": "{\"type\":\"StreetNameWasRenamed\",\"data\":\"{}\"}" - } -] \ No newline at end of file diff --git a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs index 2cbf0aace..2b7029e36 100644 --- a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs +++ b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs @@ -308,7 +308,7 @@ public static void CustomizeOrganizationOvoCode(this IFixture fixture) fixture.Customize(composer => composer.FromFactory(generator => new OrganizationOvoCode(generator.Next(1, OrganizationOvoCode.MaxDigitsValue)) - )); + )); } public static void CustomizeOrganizationKboNumber(this IFixture fixture) @@ -316,17 +316,15 @@ public static void CustomizeOrganizationKboNumber(this IFixture fixture) fixture.Customize(composer => composer.FromFactory(generator => new OrganizationKboNumber($"{generator.Next(1, 99999):00000}{generator.Next(1, 99999):00000}") - )); + )); } public static void CustomizeMunicipalityId(this IFixture fixture) { fixture.Customize(composer => - composer.FromFactory(generator => - new MunicipalityId(new string( - (char)generator.Next(97, 123), // a-z - generator.Next(1, OrganizationId.MaxLength + 1)))) - ); + composer.FromFactory(_ => + new MunicipalityId(fixture.Create()) + )); } public static void CustomizeOriginProperties(this IFixture fixture) From 21ae292fb4be0fe8bfe0b40139c9bd1fac27be71 Mon Sep 17 00:00:00 2001 From: Rik De Peuter Date: Wed, 30 Oct 2024 17:08:59 +0100 Subject: [PATCH 4/5] cleanup --- .../appsettings.development.json | 18 +++++------------- src/RoadRegistry.SyncHost/appsettings.json | 4 ++++ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/RoadRegistry.SyncHost/appsettings.development.json b/src/RoadRegistry.SyncHost/appsettings.development.json index 12f3c886a..79ec07f91 100644 --- a/src/RoadRegistry.SyncHost/appsettings.development.json +++ b/src/RoadRegistry.SyncHost/appsettings.development.json @@ -1,7 +1,6 @@ { "FeatureToggles": { }, - "Serilog": { "MinimumLevel": { "Default": "Information", @@ -19,21 +18,17 @@ } ] }, - "BlobClientType": "S3BlobClient", - "S3": { "ServiceUrl": "http://localhost:9010", "AccessKey": "Q3AM3UQ867SPQQA43P2F", "SecretKey": "zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG" }, - "S3BlobClientOptions": { "Buckets": { "Uploads": "road-registry-uploads" } }, - "ConnectionStrings": { "EditorProjections": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "CommandHost": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", @@ -50,10 +45,14 @@ "MunicipalityEventConsumer": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True", "MunicipalityEventConsumerAdmin": "Data Source=tcp:localhost,21433;Initial Catalog=road-registry;Integrated Security=False;User ID=sa;Password=E@syP@ssw0rd;TrustServerCertificate=True" }, - "Kafka": { "BootstrapServers": "localhost:9092", "Consumers": { + "MunicipalityEvent": { + "Topic": "dev.municipality", + "GroupSuffix": "", + "Offset": null + }, "StreetNameEvent": { "Topic": "dev.streetname.migration", "GroupSuffix": "", @@ -65,16 +64,9 @@ "GroupSuffix": "", "Offset": null, "JsonPath": "" - }, - "MunicipalityEvent": { - "Topic": "dev.municipality.migration", - "GroupSuffix": "", - "Offset": null, - "JsonPath": "" } } }, - "OrganizationConsumerOptions": { "OrganizationRegistrySyncUrl": "https://api.wegwijs.vlaanderen.be" } diff --git a/src/RoadRegistry.SyncHost/appsettings.json b/src/RoadRegistry.SyncHost/appsettings.json index f45f08995..fe3683519 100644 --- a/src/RoadRegistry.SyncHost/appsettings.json +++ b/src/RoadRegistry.SyncHost/appsettings.json @@ -47,6 +47,10 @@ "Kafka": { "BootstrapServers": "", "Consumers": { + "MunicipalityEvent": { + "Topic": "", + "GroupSuffix": "" + }, "StreetNameEvent": { "Topic": "", "GroupSuffix": "" From 4ac1fcd031efa87fb609d37436c79d3a6e0c0a7e Mon Sep 17 00:00:00 2001 From: Rik De Peuter Date: Tue, 5 Nov 2024 09:32:24 +0100 Subject: [PATCH 5/5] cleanup --- src/RoadRegistry.BackOffice/MunicipalityId.cs | 36 ------------------- .../Modules/MunicipalityConsumerModule.cs | 3 +- .../MunicipalityEventProjectionTestsBase.cs | 3 +- .../BackOffice/SharedCustomizations.cs | 8 ----- 4 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 src/RoadRegistry.BackOffice/MunicipalityId.cs diff --git a/src/RoadRegistry.BackOffice/MunicipalityId.cs b/src/RoadRegistry.BackOffice/MunicipalityId.cs deleted file mode 100644 index 9cfdf085e..000000000 --- a/src/RoadRegistry.BackOffice/MunicipalityId.cs +++ /dev/null @@ -1,36 +0,0 @@ -namespace RoadRegistry.BackOffice; - -using System; -using Extensions; - -public readonly struct MunicipalityId -{ - private readonly string _value; - - public MunicipalityId(string value) - { - ArgumentNullException.ThrowIfNull(value); - - if (!AcceptsValue(value)) - { - throw new ArgumentException("The value is not a well known municipality identifier", nameof(value)); - } - - _value = value; - } - - public static bool AcceptsValue(string value) - { - return !string.IsNullOrEmpty(value) && !value.ContainsWhitespace(); - } - - public override string ToString() - { - return _value; - } - - public static implicit operator string(MunicipalityId instance) - { - return instance.ToString(); - } -} diff --git a/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs index 90ea1ac61..079e832bc 100644 --- a/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs +++ b/src/RoadRegistry.SyncHost/Infrastructure/Modules/MunicipalityConsumerModule.cs @@ -10,8 +10,7 @@ public static class MunicipalityConsumerModule public static IServiceCollection AddMunicipalityConsumerServices(this IServiceCollection services) { return services - .AddSingleton() - .AddSingleton(sp => sp.GetRequiredService()) + .AddSingleton() .AddSingleton>(MunicipalityEventConsumerContext.ConfigureOptions) .AddDbContext((sp, options) => sp.GetRequiredService>()(sp, options)) .AddDbContextFactory((sp, options) => sp.GetRequiredService>()(sp, options)) diff --git a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs index fd26c4115..fb4c83505 100644 --- a/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs +++ b/test/RoadRegistry.SyncHost.Tests/Municipality/Projections/MunicipalityEventProjectionTestsBase.cs @@ -14,13 +14,12 @@ protected MunicipalityEventProjectionTestsBase(MunicipalityEventProjectionClassF { Projector = projector; Fixture = new Fixture(); - Fixture.CustomizeMunicipalityId(); } protected async Task GivenRegisteredMunicipality() { var @event = new MunicipalityWasRegistered( - Fixture.Create(), + Fixture.Create(), Fixture.Create(), null); diff --git a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs index 2b7029e36..4c8d5fffe 100644 --- a/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs +++ b/test/RoadRegistry.Tests/BackOffice/SharedCustomizations.cs @@ -319,14 +319,6 @@ public static void CustomizeOrganizationKboNumber(this IFixture fixture) )); } - public static void CustomizeMunicipalityId(this IFixture fixture) - { - fixture.Customize(composer => - composer.FromFactory(_ => - new MunicipalityId(fixture.Create()) - )); - } - public static void CustomizeOriginProperties(this IFixture fixture) { fixture.Customize(customization =>