diff --git a/src/PinkParrot/Configurations/InfrastructureDependencies.cs b/src/PinkParrot/Configurations/InfrastructureDependencies.cs index 2ef80d4ef..63a847407 100644 --- a/src/PinkParrot/Configurations/InfrastructureDependencies.cs +++ b/src/PinkParrot/Configurations/InfrastructureDependencies.cs @@ -10,9 +10,12 @@ using System.Net; using Autofac; using EventStore.ClientAPI; using EventStore.ClientAPI.SystemData; +using Microsoft.AspNetCore.Builder; +using MongoDB.Driver; using PinkParrot.Infrastructure.CQRS.Autofac; using PinkParrot.Infrastructure.CQRS.Commands; using PinkParrot.Infrastructure.CQRS.EventStore; +using PinkParrot.Read.Services.Implementations; namespace PinkParrot.Configurations { @@ -29,9 +32,21 @@ namespace PinkParrot.Configurations .KeepRetrying(), new IPEndPoint(IPAddress.Loopback, 1113)); + var mongoDbClient = new MongoClient("mongodb://localhost"); + var mongoDatabase = mongoDbClient.GetDatabase("PinkParrot"); + eventStore.ConnectAsync().Wait(); builder.RegisterInstance(new UserCredentials("admin", "changeit")) + .AsSelf() + .SingleInstance(); + + builder.RegisterInstance(mongoDatabase) + .As() + .SingleInstance(); + + builder.RegisterType() + .As() .SingleInstance(); builder.RegisterType() @@ -53,6 +68,18 @@ namespace PinkParrot.Configurations builder.RegisterType() .As() .SingleInstance(); + + builder.RegisterType() + .AsSelf() + .SingleInstance(); + } + } + + public static class InfrastructureDependencie + { + public static void UseAppEventBus(this IApplicationBuilder app) + { + app.ApplicationServices.GetService(typeof(EventStoreBus)); } } } diff --git a/src/PinkParrot/Configurations/ReadDependencies.cs b/src/PinkParrot/Configurations/ReadDependencies.cs index 31b0931bc..00af51b62 100644 --- a/src/PinkParrot/Configurations/ReadDependencies.cs +++ b/src/PinkParrot/Configurations/ReadDependencies.cs @@ -7,6 +7,9 @@ // ========================================================================== using Autofac; +using PinkParrot.Infrastructure.CQRS.Events; +using PinkParrot.Read.Repositories; +using PinkParrot.Read.Repositories.Implementations; using PinkParrot.Read.Services; using PinkParrot.Read.Services.Implementations; @@ -16,8 +19,17 @@ namespace PinkParrot.Configurations { protected override void Load(ContainerBuilder builder) { - builder.RegisterType() - .As() + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .As() .SingleInstance(); } } diff --git a/src/PinkParrot/Configurations/Serializers.cs b/src/PinkParrot/Configurations/Serializers.cs index 088d96654..0ae7c7c56 100644 --- a/src/PinkParrot/Configurations/Serializers.cs +++ b/src/PinkParrot/Configurations/Serializers.cs @@ -7,12 +7,15 @@ // ========================================================================== using System.Reflection; -using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using PinkParrot.Core.Schema; using PinkParrot.Infrastructure.CQRS.EventStore; using PinkParrot.Infrastructure.Json; +using IMvcBuilder = Microsoft.Extensions.DependencyInjection.IMvcBuilder; +using IServiceCollection = Microsoft.Extensions.DependencyInjection.IServiceCollection; +using MvcJsonMvcBuilderExtensions = Microsoft.Extensions.DependencyInjection.MvcJsonMvcBuilderExtensions; +using ServiceCollectionServiceExtensions = Microsoft.Extensions.DependencyInjection.ServiceCollectionServiceExtensions; namespace PinkParrot.Configurations { @@ -41,14 +44,14 @@ namespace PinkParrot.Configurations new ModelFieldFactory() .AddFactory(id => new NumberField(id)); - services.AddSingleton(t => CreateSettings()); - services.AddSingleton(fieldFactory); - services.AddSingleton(); + ServiceCollectionServiceExtensions.AddSingleton(services, t => CreateSettings()); + ServiceCollectionServiceExtensions.AddSingleton(services, fieldFactory); + ServiceCollectionServiceExtensions.AddSingleton(services); } public static void AddAppSerializers(this IMvcBuilder mvc) { - mvc.AddJsonOptions(options => + MvcJsonMvcBuilderExtensions.AddJsonOptions(mvc, options => { ConfigureJson(options.SerializerSettings); }); diff --git a/src/PinkParrot/Configurations/WriteDependencies.cs b/src/PinkParrot/Configurations/WriteDependencies.cs index 14eb4c9fc..3ed7f7bde 100644 --- a/src/PinkParrot/Configurations/WriteDependencies.cs +++ b/src/PinkParrot/Configurations/WriteDependencies.cs @@ -8,6 +8,7 @@ using Autofac; using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Pipeline.CommandHandlers; using PinkParrot.Write.Schema; namespace PinkParrot.Configurations @@ -16,11 +17,20 @@ namespace PinkParrot.Configurations { protected override void Load(ContainerBuilder builder) { + builder.RegisterType() + .As() + .SingleInstance(); + + builder.RegisterType() + .As() + .SingleInstance(); + builder.RegisterType() .As() .SingleInstance(); builder.RegisterType() + .AsSelf() .InstancePerDependency(); } } diff --git a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs new file mode 100644 index 000000000..6d753d575 --- /dev/null +++ b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithAggregateIdHandler.cs @@ -0,0 +1,50 @@ +// ========================================================================== +// EnrichWithAggregateIdHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Read.Services; + +// ReSharper disable InvertIf + +namespace PinkParrot.Pipeline.CommandHandlers +{ + public sealed class EnrichWithAggregateIdHandler : ICommandHandler + { + private readonly IModelSchemaProvider modelSchemaProvider; + private readonly IActionContextAccessor actionContextAccessor; + + public EnrichWithAggregateIdHandler(IModelSchemaProvider modelSchemaProvider, IActionContextAccessor actionContextAccessor) + { + this.modelSchemaProvider = modelSchemaProvider; + + this.actionContextAccessor = actionContextAccessor; + } + + public async Task HandleAsync(CommandContext context) + { + var aggregateCommand = context.Command as IAggregateCommand; + + if (aggregateCommand != null && aggregateCommand.AggregateId == Guid.Empty) + { + var routeValues = actionContextAccessor.ActionContext.RouteData.Values; + + if (routeValues.ContainsKey("name")) + { + var schemeName = routeValues["name"]; + + aggregateCommand.AggregateId = await modelSchemaProvider.FindSchemaIdByNameAsync(schemeName.ToString()); + } + } + + return false; + } + } +} diff --git a/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs new file mode 100644 index 000000000..6b2502bca --- /dev/null +++ b/src/PinkParrot/Pipeline/CommandHandlers/EnrichWithTenantIdHandler.cs @@ -0,0 +1,45 @@ +// ========================================================================== +// EnrichWithTenantIdHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using PinkParrot.Infrastructure.CQRS.Commands; +using PinkParrot.Read.Services; +using PinkParrot.Write; + +// ReSharper disable InvertIf + +namespace PinkParrot.Pipeline.CommandHandlers +{ + public sealed class EnrichWithTenantIdHandler : ICommandHandler + { + private readonly ITenantProvider tenantProvider; + private readonly IHttpContextAccessor httpContextAccessor; + + public EnrichWithTenantIdHandler(ITenantProvider tenantProvider, IHttpContextAccessor httpContextAccessor) + { + this.tenantProvider = tenantProvider; + + this.httpContextAccessor = httpContextAccessor; + } + + public async Task HandleAsync(CommandContext context) + { + var tenantCommand = context.Command as ITenantCommand; + + if (tenantCommand != null) + { + var domain = httpContextAccessor.HttpContext.Request.Host.ToString(); + + tenantCommand.TenantId = await tenantProvider.ProvideTenantIdByDomainAsync(domain); + } + + return false; + } + } +} diff --git a/src/PinkParrot/Pipeline/CommandHandlers/LogExceptionHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/LogExceptionHandler.cs new file mode 100644 index 000000000..ac952b256 --- /dev/null +++ b/src/PinkParrot/Pipeline/CommandHandlers/LogExceptionHandler.cs @@ -0,0 +1,40 @@ +// ========================================================================== +// LogExceptionHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PinkParrot.Infrastructure.CQRS.Commands; + +// ReSharper disable InvertIf + +namespace PinkParrot.Pipeline.CommandHandlers +{ + public sealed class LogExceptionHandler : ICommandHandler + { + private readonly ILogger logger; + + public LogExceptionHandler(ILogger logger) + { + this.logger = logger; + } + + public Task HandleAsync(CommandContext context) + { + var exception = context.Exception; + + if (exception != null) + { + var eventId = new EventId(9999, "CommandFailed"); + + logger.LogError(eventId, exception, "Handling {0} command failed", context.Command); + } + + return Task.FromResult(false); + } + } +} diff --git a/src/PinkParrot/Pipeline/CommandHandlers/LogExecutingHandler.cs b/src/PinkParrot/Pipeline/CommandHandlers/LogExecutingHandler.cs new file mode 100644 index 000000000..931d08413 --- /dev/null +++ b/src/PinkParrot/Pipeline/CommandHandlers/LogExecutingHandler.cs @@ -0,0 +1,31 @@ +// ========================================================================== +// LogExecutingHandler.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using PinkParrot.Infrastructure.CQRS.Commands; + +namespace PinkParrot.Pipeline.CommandHandlers +{ + public sealed class LogExecutingHandler : ICommandHandler + { + private readonly ILogger logger; + + public LogExecutingHandler(ILogger logger) + { + this.logger = logger; + } + + public Task HandleAsync(CommandContext context) + { + logger.LogError("Handling {0} command", context.Command); + + return Task.FromResult(false); + } + } +} diff --git a/src/PinkParrot/Startup.cs b/src/PinkParrot/Startup.cs index fcbb58dc0..07c535691 100644 --- a/src/PinkParrot/Startup.cs +++ b/src/PinkParrot/Startup.cs @@ -44,6 +44,7 @@ namespace PinkParrot app.UseMvc(); app.UseStaticFiles(); app.UseAppSwagger(); + app.UseAppEventBus(); if (env.IsDevelopment()) { diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs index 7df63ae08..eb2a0a56f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/CommonHeaders.cs @@ -12,6 +12,7 @@ namespace PinkParrot.Infrastructure.CQRS public const string AggregateId = "AggregateId"; public const string CommitId = "CommitId"; public const string Timestamp = "Timestamp"; + public const string TenantId = "TenantId"; public const string EventId = "EventId"; public const string EventType = "EventType"; public const string EventNumber = "EventNumber"; diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs index aaec4cacc..407343a36 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/DomainObject.cs @@ -58,7 +58,7 @@ namespace PinkParrot.Infrastructure.CQRS { Guard.NotNull(@event, nameof(@event)); - var envelopeToAdd = EnvelopeFactory.ForEvent(@event, id); + var envelopeToAdd = EnvelopeFactory.ForEvent(@event, this); uncomittedEvents.Add(envelopeToAdd); diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs index 093791a0c..b628d9cfc 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Envelope.cs @@ -5,7 +5,6 @@ // Copyright (c) PinkParrot Group // All rights reserved. // ========================================================================== - namespace PinkParrot.Infrastructure.CQRS { public class Envelope where TPayload : class diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs index fa7499dba..98b6bcbde 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeExtensions.cs @@ -62,6 +62,18 @@ namespace PinkParrot.Infrastructure.CQRS return envelope; } + public static Guid TenantId(this EnvelopeHeaders headers) + { + return headers[CommonHeaders.TenantId].ToGuid(CultureInfo.InvariantCulture); + } + + public static Envelope SetTenantId(this Envelope envelope, Guid value) where T : class + { + envelope.Headers.Set(CommonHeaders.TenantId, value); + + return envelope; + } + public static Instant Timestamp(this EnvelopeHeaders headers) { return headers[CommonHeaders.Timestamp].ToInstant(CultureInfo.InvariantCulture); diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs index dbf64ce45..d523d1587 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EnvelopeFactory.cs @@ -14,12 +14,24 @@ namespace PinkParrot.Infrastructure.CQRS { public static class EnvelopeFactory { - public static Envelope ForEvent(IEvent @event, Guid aggregateId) + public static Envelope ForEvent(IEvent @event, IAggregate aggregate) { - return new Envelope(@event) - .SetAggregateId(aggregateId) - .SetEventId(aggregateId) - .SetTimestamp(SystemClock.Instance.GetCurrentInstant()); + var eventId = Guid.NewGuid(); + + var envelope = + new Envelope(@event) + .SetAggregateId(aggregate.Id) + .SetEventId(eventId) + .SetTimestamp(SystemClock.Instance.GetCurrentInstant()); + + var tenantAggregate = aggregate as ITenantAggregate; + + if (tenantAggregate != null) + { + envelope = envelope.SetTenantId(tenantAggregate.TenantId); + } + + return envelope; } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs new file mode 100644 index 000000000..59b6aaf9a --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreBus.cs @@ -0,0 +1,128 @@ +// ========================================================================== +// EventStoreBus.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using EventStore.ClientAPI; +using EventStore.ClientAPI.SystemData; +using Microsoft.Extensions.Logging; +using PinkParrot.Infrastructure.CQRS.Events; + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public sealed class EventStoreBus + { + private readonly IEventStoreConnection connection; + private readonly UserCredentials credentials; + private readonly EventStoreFormatter formatter; + private readonly IEnumerable liveConsumers; + private readonly IEnumerable catchConsumers; + private readonly ILogger logger; + private readonly IStreamPositionStorage positions; + private EventStoreAllCatchUpSubscription catchSubscription; + + public EventStoreBus( + ILogger logger, + IEnumerable liveConsumers, + IEnumerable catchConsumers, + IStreamPositionStorage positions, + IEventStoreConnection connection, + UserCredentials credentials, + EventStoreFormatter formatter) + { + Guard.NotNull(logger, nameof(logger)); + Guard.NotNull(formatter, nameof(formatter)); + Guard.NotNull(positions, nameof(positions)); + Guard.NotNull(connection, nameof(connection)); + Guard.NotNull(credentials, nameof(credentials)); + Guard.NotNull(liveConsumers, nameof(liveConsumers)); + Guard.NotNull(catchConsumers, nameof(catchConsumers)); + + this.logger = logger; + this.formatter = formatter; + this.positions = positions; + this.connection = connection; + this.credentials = credentials; + this.liveConsumers = liveConsumers; + this.catchConsumers = catchConsumers; + + Subscribe(); + } + + private void Subscribe() + { + var position = positions.ReadPosition(); + + var now = DateTime.UtcNow; + + logger.LogInformation($"Subscribing from: {0}", position); + + var settings = + new CatchUpSubscriptionSettings( + int.MaxValue, 4096, + true, + true); + + catchSubscription = connection.SubscribeToAllFrom(position, settings, (s, resolvedEvent) => + { + var requireUpdate = false; + + Debug.WriteLine($"Last Position: {catchSubscription.LastProcessedPosition}"); + try + { + if (resolvedEvent.OriginalEvent.EventStreamId.StartsWith("$", StringComparison.OrdinalIgnoreCase)) + { + return; + } + + if (liveConsumers.Any() || catchConsumers.Any()) + { + requireUpdate = true; + + var @event = formatter.Parse(resolvedEvent); + + if (resolvedEvent.Event.Created > now) + { + Dispatch(liveConsumers, @event); + } + + Dispatch(catchConsumers, @event); + } + + requireUpdate = requireUpdate || catchSubscription.LastProcessedPosition.CommitPosition % 2 == 0; + } + finally + { + if (requireUpdate) + { + positions.WritePosition(catchSubscription.LastProcessedPosition); + } + } + }, userCredentials: credentials); + } + + private void Dispatch(IEnumerable consumers, Envelope @event) + { + foreach (var consumer in consumers) + { + try + { + consumer.On(@event); + } + catch (Exception ex) + { + var eventId = new EventId(10001, "EventConsumeFailed"); + + logger.LogError(eventId, ex, "'{0}' failed to handle event {1} ({2})", consumer, @event.Payload, @event.Headers.EventId()); + } + } + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs index d532c51bb..45149303f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreDomainObjectRepository.cs @@ -1,5 +1,5 @@ // ========================================================================== -// GetEventStoreDomainObjectRepository.cs +// EventStoreDomainObjectRepository.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group @@ -14,8 +14,8 @@ using System.Threading.Tasks; using EventStore.ClientAPI; using EventStore.ClientAPI.SystemData; using PinkParrot.Infrastructure.CQRS.Commands; -// ReSharper disable RedundantAssignment +// ReSharper disable RedundantAssignment // ReSharper disable ConvertIfStatementToSwitchStatement // ReSharper disable TooWideLocalVariableScope @@ -29,14 +29,14 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore private readonly IStreamNameResolver nameResolver; private readonly IDomainObjectFactory factory; private readonly UserCredentials credentials; - private readonly EventStoreParser formatter; + private readonly EventStoreFormatter formatter; public EventStoreDomainObjectRepository( IDomainObjectFactory factory, IStreamNameResolver nameResolver, IEventStoreConnection connection, UserCredentials credentials, - EventStoreParser formatter) + EventStoreFormatter formatter) { Guard.NotNull(factory, nameof(factory)); Guard.NotNull(formatter, nameof(formatter)); @@ -106,28 +106,28 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore var newEvents = domainObject.GetUncomittedEvents(); - var currVersion = domainObject.Version; - var prevVersion = currVersion - newEvents.Count; - var exptVersion = prevVersion == 0 ? ExpectedVersion.NoStream : prevVersion - 1; + var versionCurrent = domainObject.Version; + var versionPrevious = versionCurrent - newEvents.Count; + var versionExpected = versionPrevious == 0 ? ExpectedVersion.NoStream : versionPrevious - 1; var eventsToSave = newEvents.Select(x => formatter.ToEventData(x, commitId)).ToList(); - await InsertEventsAsync(streamName, exptVersion, eventsToSave); + await InsertEventsAsync(streamName, versionExpected, eventsToSave); domainObject.ClearUncommittedEvents(); } - private async Task InsertEventsAsync(string streamName, int exptVersion, IReadOnlyCollection eventsToSave) + private async Task InsertEventsAsync(string streamName, int expectedVersion, IReadOnlyCollection eventsToSave) { if (eventsToSave.Count > 0) { if (eventsToSave.Count < WritePageSize) { - await connection.AppendToStreamAsync(streamName, exptVersion, eventsToSave, credentials); + await connection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave, credentials); } else { - var transaction = await connection.StartTransactionAsync(streamName, exptVersion, credentials); + var transaction = await connection.StartTransactionAsync(streamName, expectedVersion, credentials); try { @@ -146,7 +146,7 @@ namespace PinkParrot.Infrastructure.CQRS.EventStore } else { - Debug.WriteLine(string.Format("No events to insert for: {0}", streamName), "GetEventStoreRepository"); + Debug.WriteLine($"No events to insert for: {streamName}", "GetEventStoreRepository"); } } } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs index 49be3cb8c..14b14752b 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/EventStoreFormatter.cs @@ -16,11 +16,11 @@ using PinkParrot.Infrastructure.CQRS.Events; namespace PinkParrot.Infrastructure.CQRS.EventStore { - public class EventStoreParser + public class EventStoreFormatter { private readonly JsonSerializerSettings serializerSettings; - public EventStoreParser(JsonSerializerSettings serializerSettings = null) + public EventStoreFormatter(JsonSerializerSettings serializerSettings = null) { this.serializerSettings = serializerSettings ?? new JsonSerializerSettings(); } diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs new file mode 100644 index 000000000..54e7130f0 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/EventStore/IStreamPositionStorage.cs @@ -0,0 +1,19 @@ +// ========================================================================== +// IStreamPositionStorage.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using EventStore.ClientAPI; + +namespace PinkParrot.Infrastructure.CQRS.EventStore +{ + public interface IStreamPositionStorage + { + Position? ReadPosition(); + + void WritePosition(Position position); + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ICatchEventConsumer.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ICatchEventConsumer.cs new file mode 100644 index 000000000..9a005d272 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ICatchEventConsumer.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// ICatchEventConsumer.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS.Events +{ + public interface ICatchEventConsumer : IEventConsumer + { + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs index 80b0736fa..c8e94f264 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/IEventConsumer.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // IEventConsumer.cs // PinkParrot Headless CMS // ========================================================================== @@ -11,4 +11,4 @@ namespace PinkParrot.Infrastructure.CQRS.Events { void On(Envelope @event); } -} +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ILiveEventConsumer.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ILiveEventConsumer.cs new file mode 100644 index 000000000..0ef2d5698 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/Events/ILiveEventConsumer.cs @@ -0,0 +1,13 @@ +// ========================================================================== +// ILiveEventConsumer.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== +namespace PinkParrot.Infrastructure.CQRS.Events +{ + public interface ILiveEventConsumer : IEventConsumer + { + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/ITenantAggregate.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/ITenantAggregate.cs new file mode 100644 index 000000000..702fd4c1e --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/CQRS/ITenantAggregate.cs @@ -0,0 +1,17 @@ +// ========================================================================== +// ITenantAggregate.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; + +namespace PinkParrot.Infrastructure.CQRS +{ + public interface ITenantAggregate : IAggregate + { + Guid TenantId { get; } + } +} diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs index c1a602008..64b8cf08f 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionContextDispatcher.cs @@ -25,7 +25,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightName) .Where(Helper.HasRightParameters) .Select(ActionContextDispatcherFactory.CreateActionHandler) - .ToDictionary(h => h.Item1, h => h.Item2); + .ToDictionary>, Type, Action>(h => h.Item1, h => h.Item2); } public static bool Dispatch(TTarget target, TIn input, TContext context) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs index d0b1ede37..ee20958f3 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/ActionDispatcher.cs @@ -25,7 +25,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightName) .Where(Helper.HasRightParameters) .Select(ActionDispatcherFactory.CreateActionHandler) - .ToDictionary(h => h.Item1, h => h.Item2); + .ToDictionary>, Type, Action>(h => h.Item1, h => h.Item2); } public static bool Dispatch(TTarget target, TIn item) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs index dded47901..6b6889029 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncContextDispatcher.cs @@ -26,7 +26,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightParameters) .Where(Helper.HasRightReturnType) .Select(FuncContextDispatcherFactory.CreateFuncHandler) - .ToDictionary(h => h.Item1, h => h.Item2); + .ToDictionary>, Type, Func>(h => h.Item1, h => h.Item2); } public static TOut Dispatch(TTarget target, TIn item, TContext context) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs index 4ed946528..027bcd24c 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/Dispatching/FuncDispatcher.cs @@ -26,7 +26,7 @@ namespace PinkParrot.Infrastructure.Dispatching .Where(Helper.HasRightParameters) .Where(Helper.HasRightReturnType) .Select(FuncDispatcherFactory.CreateFuncHandler) - .ToDictionary(h => h.Item1, h => h.Item2); + .ToDictionary>, Type, Func>(h => h.Item1, h => h.Item2); } public static TOut Dispatch(TTarget target, TIn item) diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs index 79b75d2d6..f24fc843c 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/DomainObjectNotFoundException.cs @@ -1,9 +1,9 @@ // ========================================================================== -// DomainObjectNotFoundException.cs -// Green Parrot Framework +// DomainObjectNotFoundException.cs +// PinkParrot Headless CMS // ========================================================================== -// Copyright (c) Sebastian Stehle -// All rights reserved. +// Copyright (c) PinkParrot Group +// All rights reserved. // ========================================================================== using System; diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/MongoDb/BaseMongoDbRepository.cs b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/MongoDb/BaseMongoDbRepository.cs new file mode 100644 index 000000000..c2e984ab3 --- /dev/null +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/MongoDb/BaseMongoDbRepository.cs @@ -0,0 +1,122 @@ +// ========================================================================== +// BaseMongoDbRepository.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Globalization; +using System.Threading.Tasks; +using MongoDB.Driver; + +namespace PinkParrot.Infrastructure.MongoDb +{ + public abstract class BaseMongoDbRepository + { + private const string CollectionFormat = "{0}Set"; + private readonly IMongoCollection mongoCollection; + private readonly IMongoDatabase mongoDatabase; + private readonly string typeName; + + protected string TypeName + { + get + { + return typeName; + } + } + + protected ProjectionDefinitionBuilder Projection + { + get + { + return Builders.Projection; + } + } + + protected SortDefinitionBuilder Sort + { + get + { + return Builders.Sort; + } + } + + protected UpdateDefinitionBuilder Update + { + get + { + return Builders.Update; + } + } + + protected FilterDefinitionBuilder Filter + { + get + { + return Builders.Filter; + } + } + + protected IndexKeysDefinitionBuilder IndexKeys + { + get + { + return Builders.IndexKeys; + } + } + + protected IMongoCollection Collection + { + get + { + return mongoCollection; + } + } + + protected IMongoDatabase Database + { + get + { + return mongoDatabase; + } + } + + protected BaseMongoDbRepository(IMongoDatabase database) + { + Guard.NotNull(database, nameof(database)); + + mongoDatabase = database; + mongoCollection = CreateCollection(); + + typeName = GetType().Name; + } + + protected virtual MongoCollectionSettings CollectionSettings() + { + return new MongoCollectionSettings(); + } + + protected virtual string CollectionName() + { + return string.Format(CultureInfo.InvariantCulture, CollectionFormat, typeof(TEntity).Name); + } + + private IMongoCollection CreateCollection() + { + var databaseCollection = mongoDatabase.GetCollection( + CollectionName(), + CollectionSettings() ?? new MongoCollectionSettings()); + + SetupCollectionAsync(databaseCollection).Wait(); + + return databaseCollection; + } + + protected virtual Task SetupCollectionAsync(IMongoCollection collection) + { + return Task.FromResult(true); + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json index 8823eff70..9b7cd192c 100644 --- a/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json +++ b/src/pinkparrot_infrastructure/PinkParrot.Infrastructure/project.json @@ -3,6 +3,8 @@ "dependencies": { "Autofac": "4.1.0", "EventStore.ClientAPI.DotNetCore": "1.0.0", + "Microsoft.Extensions.Logging": "1.0.0", + "MongoDB.Driver": "2.3.0-rc1", "NETStandard.Library": "1.6.0", "Newtonsoft.Json": "9.0.1", "NodaTime": "2.0.0-alpha20160729", diff --git a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs b/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs index 90aaf1143..04d9e3266 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Models/ModelSchemaRM.cs @@ -8,6 +8,7 @@ using System; using System.ComponentModel.DataAnnotations; +using MongoDB.Bson.Serialization.Attributes; using PinkParrot.Infrastructure; namespace PinkParrot.Read.Models @@ -15,12 +16,29 @@ namespace PinkParrot.Read.Models public sealed class ModelSchemaRM { [Hide] + [BsonId] public string Id { get; set; } [Required] + [BsonElement] + public Guid SchemaId { get; set; } + + [Required] + [BsonElement] public string Name { get; set; } [Required] - public Guid SchemaId { get; set; } + [BsonElement] + public DateTime Created { get; set; } + + [Required] + [BsonElement] + public DateTime Modified { get; set; } + + [BsonElement] + public string Label { get; set; } + + [BsonElement] + public string Hints { get; set; } } } diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs new file mode 100644 index 000000000..7b7e89876 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Repositories/Implementations/MongoModelSchemaRepository.cs @@ -0,0 +1,67 @@ +// ========================================================================== +// MongoModelSchemaRepository.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using MongoDB.Driver; +using PinkParrot.Events.Schema; +using PinkParrot.Infrastructure.CQRS; +using PinkParrot.Infrastructure.CQRS.Events; +using PinkParrot.Infrastructure.Dispatching; +using PinkParrot.Infrastructure.MongoDb; +using PinkParrot.Read.Models; + +namespace PinkParrot.Read.Repositories.Implementations +{ + public sealed class MongoModelSchemaRepository : BaseMongoDbRepository, IModelSchemaRepository, ICatchEventConsumer + { + public MongoModelSchemaRepository(IMongoDatabase database) + : base(database) + { + } + + protected override Task SetupCollectionAsync(IMongoCollection collection) + { + return Collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId)); + } + + public IQueryable QuerySchemas() + { + return Collection.AsQueryable(); + } + + public Task> QueryAllAsync() + { + return Collection.Find(s => true).ToListAsync(); + } + + public async void On(ModelSchemaCreated @event, EnvelopeHeaders headers) + { + var now = DateTime.UtcNow; + + var entity = new ModelSchemaRM + { + SchemaId = headers.AggregateId(), + Created = now, + Modified = now, + Name = @event.Properties.Name, + Hints = @event.Properties.Hints, + Label = @event.Properties.Label, + }; + + await Collection.InsertOneAsync(entity); + } + + public void On(Envelope @event) + { + this.DispatchAction(@event.Payload, @event.Headers); + } + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/MongoDbModelSchemaRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/MongoDbModelSchemaRepository.cs deleted file mode 100644 index a7c824245..000000000 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/MongoDbModelSchemaRepository.cs +++ /dev/null @@ -1,41 +0,0 @@ -// ========================================================================== -// MongoDbModelSchemaRepository.cs -// PinkParrot Headless CMS -// ========================================================================== -// Copyright (c) PinkParrot Group -// All rights reserved. -// ========================================================================== - -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using MongoDB.Driver; -using PinkParrot.Read.Models; -using PinkParrot.Read.Repositories.MongoDb.Utils; - -namespace PinkParrot.Read.Repositories.MongoDb -{ - public sealed class MongoDbModelSchemaRepository : BaseRepository, IModelSchemaRepository - { - public MongoDbModelSchemaRepository(IMongoDatabase database) - : base(database, "ModelSchemas") - { - CreateIndicesAsync().Wait(); - } - - private async Task CreateIndicesAsync() - { - await Collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId)); - } - - public IQueryable QuerySchemas() - { - return Collection.AsQueryable(); - } - - public Task> QueryAllAsync() - { - return Collection.Find(s => true).ToListAsync(); - } - } -} diff --git a/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/Utils/BaseRepository.cs b/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/Utils/BaseRepository.cs deleted file mode 100644 index 5c6ed0246..000000000 --- a/src/pinkparrot_read/PinkParrot.Read/Repositories/MongoDb/Utils/BaseRepository.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ========================================================================== -// BaseRepository.cs -// PinkParrot Headless CMS -// ========================================================================== -// Copyright (c) PinkParrot Group -// All rights reserved. -// ========================================================================== - -using MongoDB.Driver; -using PinkParrot.Infrastructure; - -namespace PinkParrot.Read.Repositories.MongoDb.Utils -{ - public abstract class BaseRepository - { - private readonly IMongoCollection collection; - private readonly IndexKeysDefinitionBuilder indexKeys = new IndexKeysDefinitionBuilder(); - - protected IMongoCollection Collection - { - get { return collection; } - } - - protected IndexKeysDefinitionBuilder IndexKeys - { - get { return indexKeys; } - } - - protected BaseRepository(IMongoDatabase database, string collectioName) - { - Guard.NotNull(database, nameof(database)); - Guard.NotNullOrEmpty(collectioName, nameof(collectioName)); - - collection = database.GetCollection(collectioName); - } - } -} diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs similarity index 87% rename from src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs rename to src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs index 0995e40d8..e058e1131 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/ISchemaProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/IModelSchemaProvider.cs @@ -1,5 +1,5 @@ // ========================================================================== -// ISchemaProvider.cs +// IModelSchemaProvider.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace PinkParrot.Read.Services { - public interface ISchemaProvider + public interface IModelSchemaProvider { Task FindSchemaIdByNameAsync(string name); } diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs new file mode 100644 index 000000000..dc60f64ea --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/ITenantProvider.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ITenantProvider.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace PinkParrot.Read.Services +{ + public interface ITenantProvider + { + Task ProvideTenantIdByDomainAsync(string domain); + } +} diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs similarity index 86% rename from src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs rename to src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs index 3adaf5d3e..2d936f338 100644 --- a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/SchemaProvider.cs +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/ModelSchemaProvider.cs @@ -1,5 +1,5 @@ // ========================================================================== -// SchemaProvider.cs +// ModelSchemaProvider.cs // PinkParrot Headless CMS // ========================================================================== // Copyright (c) PinkParrot Group @@ -11,7 +11,7 @@ using System.Threading.Tasks; namespace PinkParrot.Read.Services.Implementations { - public class SchemaProvider : ISchemaProvider + public class ModelSchemaProvider : IModelSchemaProvider { public Task FindSchemaIdByNameAsync(string name) { diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs new file mode 100644 index 000000000..3dcc7c641 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoPositions.cs @@ -0,0 +1,27 @@ +// ========================================================================== +// MongoPositions.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System.Runtime.Serialization; +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace PinkParrot.Read.Services.Implementations +{ + [DataContract] + public class MongoPosition + { + [BsonId] + public ObjectId Id { get; set; } + + [BsonElement] + public long CommitPosition { get; set; } + + [BsonElement] + public long PreparePosition { get; set; } + } +} \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs new file mode 100644 index 000000000..84ede0443 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/MongoStreamPositionsStorage.cs @@ -0,0 +1,60 @@ +// ========================================================================== +// MongoStreamPositionsStorage.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using EventStore.ClientAPI; +using MongoDB.Bson; +using PinkParrot.Infrastructure.CQRS.EventStore; +using PinkParrot.Infrastructure.MongoDb; +using IFindFluentExtensions = MongoDB.Driver.IFindFluentExtensions; +using IMongoDatabase = MongoDB.Driver.IMongoDatabase; + +//// ReSharper disable once ConvertIfStatementToNullCoalescingExpression + +namespace PinkParrot.Read.Services.Implementations +{ + public sealed class MongoStreamPositionsStorage : BaseMongoDbRepository, IStreamPositionStorage + { + private static readonly ObjectId Id = new ObjectId("507f1f77bcf86cd799439011"); + + public MongoStreamPositionsStorage(IMongoDatabase database) + : base(database) + { + } + + public Position? ReadPosition() + { + var document = IFindFluentExtensions.FirstOrDefault(Collection.Find(t => t.Id == Id)); + + return document != null ? new Position(document.CommitPosition, document.PreparePosition) : Position.Start; + } + + public void WritePosition(Position position) + { + var document = IFindFluentExtensions.FirstOrDefault(Collection.Find(t => t.Id == Id)); + + var isFound = document != null; + + if (document == null) + { + document = new MongoPosition { Id = Id }; + } + + document.CommitPosition = position.CommitPosition; + document.PreparePosition = position.PreparePosition; + + if (isFound) + { + Collection.ReplaceOne(t => t.Id == Id, document); + } + else + { + Collection.InsertOne(document); + } + } + } +} \ No newline at end of file diff --git a/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs new file mode 100644 index 000000000..1580581d1 --- /dev/null +++ b/src/pinkparrot_read/PinkParrot.Read/Services/Implementations/TenantProvider.cs @@ -0,0 +1,21 @@ +// ========================================================================== +// TenantProvider.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using System.Threading.Tasks; + +namespace PinkParrot.Read.Services.Implementations +{ + public sealed class TenantProvider : ITenantProvider + { + public Task ProvideTenantIdByDomainAsync(string domain) + { + return Task.FromResult(Guid.Empty); + } + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/ITenantCommand.cs b/src/pinkparrot_write/PinkParrot.Write/ITenantCommand.cs new file mode 100644 index 000000000..013d9e8a4 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/ITenantCommand.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// ITenantCommand.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS.Commands; + +namespace PinkParrot.Write +{ + public interface ITenantCommand : ICommand + { + Guid TenantId { get; set; } + } +} \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs index 9283ed7c7..3795a1a23 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/CreateModelSchema.cs @@ -7,11 +7,10 @@ // ========================================================================== using PinkParrot.Core.Schema; -using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class CreateModelSchema : AggregateCommand + public class CreateModelSchema : TenantCommand { public ModelSchemaProperties Properties { get; set; } } diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs index bd7eed81e..5a35849bb 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/Commands/DeleteModelSchema.cs @@ -6,13 +6,11 @@ // All rights reserved. // ========================================================================== -using System; using PinkParrot.Infrastructure.CQRS.Commands; namespace PinkParrot.Write.Schema.Commands { - public class DeleteModelSchema : IAggregateCommand + public class DeleteModelSchema : AggregateCommand { - public Guid AggregateId { get; set; } } } \ No newline at end of file diff --git a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs index 8afa0bcae..956dca543 100644 --- a/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs +++ b/src/pinkparrot_write/PinkParrot.Write/Schema/ModelSchemaDomainObject.cs @@ -16,9 +16,10 @@ using PinkParrot.Write.Schema.Commands; namespace PinkParrot.Write.Schema { - public class ModelSchemaDomainObject : DomainObject + public class ModelSchemaDomainObject : DomainObject, IAggregate { private readonly ModelFieldFactory fieldFactory; + private Guid tenantId; private bool isDeleted; private long totalFields; private ModelSchema schema; @@ -28,6 +29,11 @@ namespace PinkParrot.Write.Schema get { return schema; } } + public Guid TenantId + { + get { return tenantId; } + } + public bool IsDeleted { get { return isDeleted; } @@ -48,6 +54,8 @@ namespace PinkParrot.Write.Schema protected void Apply(ModelSchemaCreated @event) { + tenantId = @event.TenantId; + schema = ModelSchema.Create(@event.Properties); } @@ -106,6 +114,8 @@ namespace PinkParrot.Write.Schema { VerifyNotCreated(); + tenantId = command.TenantId; + schema = ModelSchema.Create(command.Properties); RaiseEvent(new ModelSchemaCreated { Properties = command.Properties }, true); diff --git a/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs b/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs new file mode 100644 index 000000000..66751a814 --- /dev/null +++ b/src/pinkparrot_write/PinkParrot.Write/TenantCommand.cs @@ -0,0 +1,18 @@ +// ========================================================================== +// TenantCommand.cs +// PinkParrot Headless CMS +// ========================================================================== +// Copyright (c) PinkParrot Group +// All rights reserved. +// ========================================================================== + +using System; +using PinkParrot.Infrastructure.CQRS.Commands; + +namespace PinkParrot.Write +{ + public abstract class TenantCommand : AggregateCommand + { + public Guid TenantId { get; set; } + } +} diff --git a/src/pinkparrot_write/PinkParrot.Write/project.json b/src/pinkparrot_write/PinkParrot.Write/project.json index 86f18c4ae..43b86a783 100644 --- a/src/pinkparrot_write/PinkParrot.Write/project.json +++ b/src/pinkparrot_write/PinkParrot.Write/project.json @@ -6,7 +6,8 @@ "NodaTime": "2.0.0-alpha20160729", "PinkParrot.Core": "1.0.0-*", "PinkParrot.Events": "1.0.0-*", - "PinkParrot.Infrastructure": "1.0.0-*" + "PinkParrot.Infrastructure": "1.0.0-*", + "PinkParrot.Read": "1.0.0-*" }, "frameworks": {