mirror of https://github.com/Squidex/squidex.git
52 changed files with 1050 additions and 114 deletions
@ -0,0 +1,58 @@ |
|||||
|
// ==========================================================================
|
||||
|
// InfrastructureDependencies.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Net; |
||||
|
using Autofac; |
||||
|
using EventStore.ClientAPI; |
||||
|
using EventStore.ClientAPI.SystemData; |
||||
|
using PinkParrot.Infrastructure.CQRS.Autofac; |
||||
|
using PinkParrot.Infrastructure.CQRS.Commands; |
||||
|
using PinkParrot.Infrastructure.CQRS.EventStore; |
||||
|
|
||||
|
namespace PinkParrot.Configurations |
||||
|
{ |
||||
|
public class InfrastructureDependencies : Module |
||||
|
{ |
||||
|
protected override void Load(ContainerBuilder builder) |
||||
|
{ |
||||
|
var eventStore = |
||||
|
EventStoreConnection.Create( |
||||
|
ConnectionSettings.Create() |
||||
|
.UseConsoleLogger() |
||||
|
.UseDebugLogger() |
||||
|
.KeepReconnecting() |
||||
|
.KeepRetrying(), |
||||
|
new IPEndPoint(IPAddress.Loopback, 1113)); |
||||
|
|
||||
|
eventStore.ConnectAsync().Wait(); |
||||
|
|
||||
|
builder.RegisterInstance(new UserCredentials("admin", "changeit")) |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<AutofacDomainObjectFactory>() |
||||
|
.As<IDomainObjectFactory>() |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<DefaultNameResolver>() |
||||
|
.As<IStreamNameResolver>() |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterInstance(eventStore) |
||||
|
.As<IEventStoreConnection>() |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<EventStoreDomainObjectRepository>() |
||||
|
.As<IDomainObjectRepository>() |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<InMemoryCommandBus>() |
||||
|
.As<ICommandBus>() |
||||
|
.SingleInstance(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ReadDependencies.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Autofac; |
||||
|
using PinkParrot.Read.Services; |
||||
|
using PinkParrot.Read.Services.Implementations; |
||||
|
|
||||
|
namespace PinkParrot.Configurations |
||||
|
{ |
||||
|
public sealed class ReadDependencies : Module |
||||
|
{ |
||||
|
protected override void Load(ContainerBuilder builder) |
||||
|
{ |
||||
|
builder.RegisterType<SchemaProvider>() |
||||
|
.As<ISchemaProvider>() |
||||
|
.SingleInstance(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Serializers.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
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; |
||||
|
|
||||
|
namespace PinkParrot.Configurations |
||||
|
{ |
||||
|
public static class Serializers |
||||
|
{ |
||||
|
private static JsonSerializerSettings ConfigureJson(JsonSerializerSettings settings) |
||||
|
{ |
||||
|
settings.Binder = new TypeNameSerializationBinder().Map(typeof(ModelSchema).GetTypeInfo().Assembly); |
||||
|
settings.ContractResolver = new CamelCasePropertyNamesContractResolver(); |
||||
|
settings.Converters.Add(new PropertiesBagConverter()); |
||||
|
settings.DateFormatHandling = DateFormatHandling.IsoDateFormat; |
||||
|
settings.DateParseHandling = DateParseHandling.DateTime; |
||||
|
settings.TypeNameHandling = TypeNameHandling.Auto; |
||||
|
|
||||
|
return settings; |
||||
|
} |
||||
|
|
||||
|
private static JsonSerializerSettings CreateSettings() |
||||
|
{ |
||||
|
return ConfigureJson(new JsonSerializerSettings()); |
||||
|
} |
||||
|
|
||||
|
public static void AddEventFormatter(this IServiceCollection services) |
||||
|
{ |
||||
|
var fieldFactory = |
||||
|
new ModelFieldFactory() |
||||
|
.AddFactory<NumberFieldProperties>(id => new NumberField(id)); |
||||
|
|
||||
|
services.AddSingleton(t => CreateSettings()); |
||||
|
services.AddSingleton(fieldFactory); |
||||
|
services.AddSingleton<EventStoreParser>(); |
||||
|
} |
||||
|
|
||||
|
public static void AddAppSerializers(this IMvcBuilder mvc) |
||||
|
{ |
||||
|
mvc.AddJsonOptions(options => |
||||
|
{ |
||||
|
ConfigureJson(options.SerializerSettings); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
// ==========================================================================
|
||||
|
// Swagger.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.IO; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.PlatformAbstractions; |
||||
|
using PinkParrot.Pipeline.Swagger; |
||||
|
using Swashbuckle.Swagger.Model; |
||||
|
|
||||
|
namespace PinkParrot.Configurations |
||||
|
{ |
||||
|
public static class Swagger |
||||
|
{ |
||||
|
public static void AddAppSwagger(this IServiceCollection services) |
||||
|
{ |
||||
|
services.AddSwaggerGen(options => |
||||
|
{ |
||||
|
options.SingleApiVersion(new Info { Title = "Pink Parrot", Version = "v1" }); |
||||
|
options.OperationFilter<HidePropertyFilter>(); |
||||
|
options.OperationFilter<CamelCaseParameterFilter>(); |
||||
|
options.SchemaFilter<HidePropertyFilter>(); |
||||
|
options.SchemaFilter<RemoveReadonlyFilter>(); |
||||
|
options.IncludeXmlComments(GetXmlCommentsPath(PlatformServices.Default.Application)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public static void UseAppSwagger(this IApplicationBuilder app) |
||||
|
{ |
||||
|
app.UseSwagger(); |
||||
|
app.UseSwaggerUi(); |
||||
|
} |
||||
|
|
||||
|
private static string GetXmlCommentsPath(ApplicationEnvironment appEnvironment) |
||||
|
{ |
||||
|
return Path.Combine(appEnvironment.ApplicationBasePath, "PinkParrot.xml"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
// ==========================================================================
|
||||
|
// WriteDependencies.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Autofac; |
||||
|
using PinkParrot.Infrastructure.CQRS.Commands; |
||||
|
using PinkParrot.Write.Schema; |
||||
|
|
||||
|
namespace PinkParrot.Configurations |
||||
|
{ |
||||
|
public class WriteDependencies : Module |
||||
|
{ |
||||
|
protected override void Load(ContainerBuilder builder) |
||||
|
{ |
||||
|
builder.RegisterType<ModelSchemaCommandHandler>() |
||||
|
.As<ICommandHandler>() |
||||
|
.SingleInstance(); |
||||
|
|
||||
|
builder.RegisterType<ModelSchemaDomainObject>() |
||||
|
.InstancePerDependency(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EntityCreated.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.ComponentModel.DataAnnotations; |
||||
|
|
||||
|
namespace PinkParrot.Modules.Api |
||||
|
{ |
||||
|
public class EntityCreated |
||||
|
{ |
||||
|
[Required] |
||||
|
public Guid Id { get; set; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CamelCaseParameterFilter.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Swashbuckle.Swagger.Model; |
||||
|
using Swashbuckle.SwaggerGen.Generator; |
||||
|
|
||||
|
namespace PinkParrot.Pipeline.Swagger |
||||
|
{ |
||||
|
public sealed class CamelCaseParameterFilter : IOperationFilter |
||||
|
{ |
||||
|
public void Apply(Operation operation, OperationFilterContext context) |
||||
|
{ |
||||
|
if (operation.Parameters == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var parameter in operation.Parameters) |
||||
|
{ |
||||
|
parameter.Name = char.ToLowerInvariant(parameter.Name[0]) + parameter.Name.Substring(1); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,65 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HidePropertyFilter.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Linq; |
||||
|
using System.Reflection; |
||||
|
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; |
||||
|
using PinkParrot.Infrastructure; |
||||
|
using Swashbuckle.Swagger.Model; |
||||
|
using Swashbuckle.SwaggerGen.Generator; |
||||
|
|
||||
|
namespace PinkParrot.Pipeline.Swagger |
||||
|
{ |
||||
|
public class HidePropertyFilter : ISchemaFilter, IOperationFilter |
||||
|
{ |
||||
|
public void Apply(Schema model, SchemaFilterContext context) |
||||
|
{ |
||||
|
foreach (var property in context.JsonContract.UnderlyingType.GetProperties()) |
||||
|
{ |
||||
|
var attribute = property.GetCustomAttribute<HideAttribute>(); |
||||
|
|
||||
|
if (attribute != null) |
||||
|
{ |
||||
|
model.Properties.Remove(property.Name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Apply(Operation operation, OperationFilterContext context) |
||||
|
{ |
||||
|
if (context?.ApiDescription.ParameterDescriptions == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (operation.Parameters == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var parameterDescription in context.ApiDescription.ParameterDescriptions) |
||||
|
{ |
||||
|
var metadata = parameterDescription.ModelMetadata as DefaultModelMetadata; |
||||
|
|
||||
|
var hasAttribute = metadata?.Attributes?.Attributes.OfType<HideAttribute>().Any(); |
||||
|
|
||||
|
if (hasAttribute != true) |
||||
|
{ |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
var parameter = operation.Parameters.FirstOrDefault(p => p.Name == parameterDescription.Name); |
||||
|
|
||||
|
if (parameter != null) |
||||
|
{ |
||||
|
operation.Parameters.Remove(parameter); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,36 @@ |
|||||
|
// ==========================================================================
|
||||
|
// RemoveReadonlyFilter.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using Swashbuckle.Swagger.Model; |
||||
|
using Swashbuckle.SwaggerGen.Generator; |
||||
|
|
||||
|
namespace PinkParrot.Pipeline.Swagger |
||||
|
{ |
||||
|
public class RemoveReadonlyFilter : ISchemaFilter |
||||
|
{ |
||||
|
public void Apply(Schema model, SchemaFilterContext context) |
||||
|
{ |
||||
|
Apply(model); |
||||
|
} |
||||
|
|
||||
|
private static void Apply(Schema model) |
||||
|
{ |
||||
|
model.ReadOnly = null; |
||||
|
|
||||
|
if (model.Properties == null) |
||||
|
{ |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
foreach (var property in model.Properties) |
||||
|
{ |
||||
|
Apply(property.Value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,13 +1,17 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// GetEventStoreDomainObjectRepository.cs
|
// IAggregateCommand.cs
|
||||
// PinkParrot Headless CMS
|
// PinkParrot Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) PinkParrot Group
|
// Copyright (c) PinkParrot Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
namespace PinkParrot.Infrastructure.CQRS.GetEventStore |
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.CQRS.Commands |
||||
{ |
{ |
||||
public class GetEventStoreDomainObjectRepository |
public interface IAggregateCommand : ICommand |
||||
{ |
{ |
||||
|
Guid AggregateId { get; set; } |
||||
} |
} |
||||
} |
} |
||||
@ -0,0 +1,33 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DefaultNameResolver.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Globalization; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.CQRS.EventStore |
||||
|
{ |
||||
|
public sealed class DefaultNameResolver : IStreamNameResolver |
||||
|
{ |
||||
|
private readonly string prefix; |
||||
|
|
||||
|
public DefaultNameResolver() |
||||
|
: this(string.Empty) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public DefaultNameResolver(string prefix) |
||||
|
{ |
||||
|
this.prefix = prefix; |
||||
|
} |
||||
|
|
||||
|
public string GetStreamName(Type aggregateType, Guid id) |
||||
|
{ |
||||
|
return string.Format(CultureInfo.InvariantCulture, "{0}{1}-{2}", prefix, char.ToLower(aggregateType.Name[0]) + aggregateType.Name.Substring(1), id); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,153 @@ |
|||||
|
// ==========================================================================
|
||||
|
// GetEventStoreDomainObjectRepository.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Diagnostics; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
using EventStore.ClientAPI; |
||||
|
using EventStore.ClientAPI.SystemData; |
||||
|
using PinkParrot.Infrastructure.CQRS.Commands; |
||||
|
// ReSharper disable RedundantAssignment
|
||||
|
|
||||
|
// ReSharper disable ConvertIfStatementToSwitchStatement
|
||||
|
// ReSharper disable TooWideLocalVariableScope
|
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.CQRS.EventStore |
||||
|
{ |
||||
|
public sealed class EventStoreDomainObjectRepository : IDomainObjectRepository |
||||
|
{ |
||||
|
private const int WritePageSize = 500; |
||||
|
private const int ReadPageSize = 500; |
||||
|
private readonly IEventStoreConnection connection; |
||||
|
private readonly IStreamNameResolver nameResolver; |
||||
|
private readonly IDomainObjectFactory factory; |
||||
|
private readonly UserCredentials credentials; |
||||
|
private readonly EventStoreParser formatter; |
||||
|
|
||||
|
public EventStoreDomainObjectRepository( |
||||
|
IDomainObjectFactory factory, |
||||
|
IStreamNameResolver nameResolver, |
||||
|
IEventStoreConnection connection, |
||||
|
UserCredentials credentials, |
||||
|
EventStoreParser formatter) |
||||
|
{ |
||||
|
Guard.NotNull(factory, nameof(factory)); |
||||
|
Guard.NotNull(formatter, nameof(formatter)); |
||||
|
Guard.NotNull(connection, nameof(connection)); |
||||
|
Guard.NotNull(credentials, nameof(credentials)); |
||||
|
Guard.NotNull(nameResolver, nameof(nameResolver)); |
||||
|
|
||||
|
this.factory = factory; |
||||
|
this.formatter = formatter; |
||||
|
this.connection = connection; |
||||
|
this.credentials = credentials; |
||||
|
this.nameResolver = nameResolver; |
||||
|
} |
||||
|
|
||||
|
public async Task<TDomainObject> GetByIdAsync<TDomainObject>(Guid id, int version = 0) where TDomainObject : class, IAggregate |
||||
|
{ |
||||
|
Guard.GreaterThan(version, 0, nameof(version)); |
||||
|
|
||||
|
var streamName = nameResolver.GetStreamName(typeof(TDomainObject), id); |
||||
|
|
||||
|
var domainObject = (TDomainObject)factory.CreateNew(typeof(TDomainObject), id); |
||||
|
|
||||
|
var sliceStart = 0; |
||||
|
var sliceCount = 0; |
||||
|
|
||||
|
StreamEventsSlice currentSlice; |
||||
|
do |
||||
|
{ |
||||
|
sliceCount = sliceStart + ReadPageSize <= version ? ReadPageSize : version - sliceStart + 1; |
||||
|
|
||||
|
currentSlice = await connection.ReadStreamEventsForwardAsync(streamName, sliceStart, sliceCount, false, credentials); |
||||
|
|
||||
|
if (currentSlice.Status == SliceReadStatus.StreamNotFound) |
||||
|
{ |
||||
|
throw new DomainObjectNotFoundException(id.ToString(), typeof(TDomainObject)); |
||||
|
} |
||||
|
|
||||
|
if (currentSlice.Status == SliceReadStatus.StreamDeleted) |
||||
|
{ |
||||
|
throw new DomainObjectDeletedException(id.ToString(), typeof(TDomainObject)); |
||||
|
} |
||||
|
|
||||
|
sliceStart = currentSlice.NextEventNumber; |
||||
|
|
||||
|
foreach (var resolved in currentSlice.Events) |
||||
|
{ |
||||
|
var envelope = formatter.Parse(resolved); |
||||
|
|
||||
|
domainObject.ApplyEvent(envelope); |
||||
|
} |
||||
|
} |
||||
|
while (version >= currentSlice.NextEventNumber && !currentSlice.IsEndOfStream); |
||||
|
|
||||
|
if (domainObject.Version != version && version < int.MaxValue) |
||||
|
{ |
||||
|
throw new DomainObjectVersionException(id.ToString(), typeof(TDomainObject), domainObject.Version, version); |
||||
|
} |
||||
|
|
||||
|
return domainObject; |
||||
|
} |
||||
|
|
||||
|
public async Task SaveAsync(IAggregate domainObject, Guid commitId) |
||||
|
{ |
||||
|
Guard.NotNull(domainObject, nameof(domainObject)); |
||||
|
|
||||
|
var streamName = nameResolver.GetStreamName(domainObject.GetType(), domainObject.Id); |
||||
|
|
||||
|
var newEvents = domainObject.GetUncomittedEvents(); |
||||
|
|
||||
|
var currVersion = domainObject.Version; |
||||
|
var prevVersion = currVersion - newEvents.Count; |
||||
|
var exptVersion = prevVersion == 0 ? ExpectedVersion.NoStream : prevVersion - 1; |
||||
|
|
||||
|
var eventsToSave = newEvents.Select(x => formatter.ToEventData(x, commitId)).ToList(); |
||||
|
|
||||
|
await InsertEventsAsync(streamName, exptVersion, eventsToSave); |
||||
|
|
||||
|
domainObject.ClearUncommittedEvents(); |
||||
|
} |
||||
|
|
||||
|
private async Task InsertEventsAsync(string streamName, int exptVersion, IReadOnlyCollection<EventData> eventsToSave) |
||||
|
{ |
||||
|
if (eventsToSave.Count > 0) |
||||
|
{ |
||||
|
if (eventsToSave.Count < WritePageSize) |
||||
|
{ |
||||
|
await connection.AppendToStreamAsync(streamName, exptVersion, eventsToSave, credentials); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var transaction = await connection.StartTransactionAsync(streamName, exptVersion, credentials); |
||||
|
|
||||
|
try |
||||
|
{ |
||||
|
for (var p = 0; p < eventsToSave.Count; p += WritePageSize) |
||||
|
{ |
||||
|
await transaction.WriteAsync(eventsToSave.Skip(p).Take(WritePageSize)); |
||||
|
} |
||||
|
|
||||
|
await transaction.CommitAsync(); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
transaction.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Debug.WriteLine(string.Format("No events to insert for: {0}", streamName), "GetEventStoreRepository"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,66 @@ |
|||||
|
// ==========================================================================
|
||||
|
// EventStoreFormatter.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
using EventStore.ClientAPI; |
||||
|
using Newtonsoft.Json; |
||||
|
using PinkParrot.Infrastructure.CQRS.Events; |
||||
|
|
||||
|
// ReSharper disable InconsistentNaming
|
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.CQRS.EventStore |
||||
|
{ |
||||
|
public class EventStoreParser |
||||
|
{ |
||||
|
private readonly JsonSerializerSettings serializerSettings; |
||||
|
|
||||
|
public EventStoreParser(JsonSerializerSettings serializerSettings = null) |
||||
|
{ |
||||
|
this.serializerSettings = serializerSettings ?? new JsonSerializerSettings(); |
||||
|
} |
||||
|
|
||||
|
public Envelope<IEvent> Parse(ResolvedEvent @event) |
||||
|
{ |
||||
|
var headers = ReadJson<EnvelopeHeaders>(@event.Event.Metadata); |
||||
|
|
||||
|
var eventType = Type.GetType(headers.Properties[CommonHeaders.EventType].ToString()); |
||||
|
var eventData = ReadJson<IEvent>(@event.Event.Data, eventType); |
||||
|
|
||||
|
var envelope = new Envelope<IEvent>(eventData, headers); |
||||
|
|
||||
|
envelope.Headers.Set(CommonHeaders.Timestamp, DateTime.SpecifyKind(@event.Event.Created, DateTimeKind.Utc)); |
||||
|
envelope.Headers.Set(CommonHeaders.EventNumber, @event.OriginalEventNumber); |
||||
|
|
||||
|
return envelope; |
||||
|
} |
||||
|
|
||||
|
public EventData ToEventData(Envelope<IEvent> envelope, Guid commitId) |
||||
|
{ |
||||
|
var eventType = envelope.Payload.GetType(); |
||||
|
|
||||
|
envelope.Headers.Set(CommonHeaders.CommitId, commitId); |
||||
|
envelope.Headers.Set(CommonHeaders.EventType, eventType.AssemblyQualifiedName); |
||||
|
|
||||
|
var headers = WriteJson(envelope.Headers); |
||||
|
var content = WriteJson(envelope.Payload); |
||||
|
|
||||
|
return new EventData(envelope.Headers.EventId(), eventType.Name, true, content, headers); |
||||
|
} |
||||
|
|
||||
|
private T ReadJson<T>(byte[] data, Type type = null) |
||||
|
{ |
||||
|
return (T)JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data), type ?? typeof(T), serializerSettings); |
||||
|
} |
||||
|
|
||||
|
private byte[] WriteJson(object value) |
||||
|
{ |
||||
|
return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value, serializerSettings)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// IStreamNameResolver.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure.CQRS.EventStore |
||||
|
{ |
||||
|
public interface IStreamNameResolver |
||||
|
{ |
||||
|
string GetStreamName(Type aggregateType, Guid id); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainObjectDeletedException.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure |
||||
|
{ |
||||
|
public class DomainObjectDeletedException : DomainObjectException |
||||
|
{ |
||||
|
public DomainObjectDeletedException(string id, Type type) |
||||
|
: base(FormatMessage(id, type), id, type) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private static string FormatMessage(string id, Type type) |
||||
|
{ |
||||
|
return $"Domain object \'{id}\' (type {type}) not deleted."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainObjectException.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure |
||||
|
{ |
||||
|
public class DomainObjectException : Exception |
||||
|
{ |
||||
|
private readonly string id; |
||||
|
private readonly string typeName; |
||||
|
|
||||
|
public string TypeName |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return typeName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public string Id |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return id; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected DomainObjectException(string message, string id, Type type) |
||||
|
: this(message, id, type, null) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected DomainObjectException(string message, string id, Type type, Exception inner) |
||||
|
: base(message, inner) |
||||
|
{ |
||||
|
this.id = id; |
||||
|
|
||||
|
if (type != null) |
||||
|
{ |
||||
|
typeName = type.Name; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainObjectNotFoundException.cs
|
||||
|
// Green Parrot Framework
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Sebastian Stehle
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure |
||||
|
{ |
||||
|
public class DomainObjectNotFoundException : DomainObjectException |
||||
|
{ |
||||
|
public DomainObjectNotFoundException(string id, Type type) |
||||
|
: base(FormatMessage(id, type), id, type) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
private static string FormatMessage(string id, Type type) |
||||
|
{ |
||||
|
return $"Domain object \'{id}\' (type {type}) not found."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,47 @@ |
|||||
|
// ==========================================================================
|
||||
|
// DomainObjectVersionException.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure |
||||
|
{ |
||||
|
public class DomainObjectVersionException : DomainObjectException |
||||
|
{ |
||||
|
private readonly int currentVersion; |
||||
|
private readonly int expectedVersion; |
||||
|
|
||||
|
public int CurrentVersion |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return currentVersion; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public int ExpectedVersion |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return expectedVersion; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public DomainObjectVersionException(string id, Type type, int currentVersion, int expectedVersion) |
||||
|
: base(FormatMessage(id, type, currentVersion, expectedVersion), id, type) |
||||
|
{ |
||||
|
this.currentVersion = currentVersion; |
||||
|
|
||||
|
this.expectedVersion = expectedVersion; |
||||
|
} |
||||
|
|
||||
|
private static string FormatMessage(string id, Type type, int currentVersion, int expectedVersion) |
||||
|
{ |
||||
|
return $"Request version {expectedVersion} for object '{id}' (type {type}), but found {currentVersion}."; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
// ==========================================================================
|
||||
|
// HideAttribute.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
|
namespace PinkParrot.Infrastructure |
||||
|
{ |
||||
|
[AttributeUsage(AttributeTargets.Property)] |
||||
|
public class HideAttribute : Attribute |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,18 @@ |
|||||
|
// ==========================================================================
|
||||
|
// ISchemaProvider.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PinkParrot.Read.Services |
||||
|
{ |
||||
|
public interface ISchemaProvider |
||||
|
{ |
||||
|
Task<Guid> FindSchemaIdByNameAsync(string name); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,21 @@ |
|||||
|
// ==========================================================================
|
||||
|
// SchemaProvider.cs
|
||||
|
// PinkParrot Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) PinkParrot Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace PinkParrot.Read.Services.Implementations |
||||
|
{ |
||||
|
public class SchemaProvider : ISchemaProvider |
||||
|
{ |
||||
|
public Task<Guid> FindSchemaIdByNameAsync(string name) |
||||
|
{ |
||||
|
return Task.FromResult(Guid.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue