mirror of https://github.com/Squidex/squidex.git
45 changed files with 862 additions and 128 deletions
@ -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<bool> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<bool> 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; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<LogExceptionHandler> logger; |
||||
|
|
||||
|
public LogExceptionHandler(ILogger<LogExceptionHandler> logger) |
||||
|
{ |
||||
|
this.logger = logger; |
||||
|
} |
||||
|
|
||||
|
public Task<bool> 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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<LogExecutingHandler> logger; |
||||
|
|
||||
|
public LogExecutingHandler(ILogger<LogExecutingHandler> logger) |
||||
|
{ |
||||
|
this.logger = logger; |
||||
|
} |
||||
|
|
||||
|
public Task<bool> HandleAsync(CommandContext context) |
||||
|
{ |
||||
|
logger.LogError("Handling {0} command", context.Command); |
||||
|
|
||||
|
return Task.FromResult(false); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ILiveEventConsumer> liveConsumers; |
||||
|
private readonly IEnumerable<ICatchEventConsumer> catchConsumers; |
||||
|
private readonly ILogger<EventStoreBus> logger; |
||||
|
private readonly IStreamPositionStorage positions; |
||||
|
private EventStoreAllCatchUpSubscription catchSubscription; |
||||
|
|
||||
|
public EventStoreBus( |
||||
|
ILogger<EventStoreBus> logger, |
||||
|
IEnumerable<ILiveEventConsumer> liveConsumers, |
||||
|
IEnumerable<ICatchEventConsumer> 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<IEventConsumer> consumers, Envelope<IEvent> @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()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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 |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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<TEntity> |
||||
|
{ |
||||
|
private const string CollectionFormat = "{0}Set"; |
||||
|
private readonly IMongoCollection<TEntity> mongoCollection; |
||||
|
private readonly IMongoDatabase mongoDatabase; |
||||
|
private readonly string typeName; |
||||
|
|
||||
|
protected string TypeName |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return typeName; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected ProjectionDefinitionBuilder<TEntity> Projection |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<TEntity>.Projection; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected SortDefinitionBuilder<TEntity> Sort |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<TEntity>.Sort; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected UpdateDefinitionBuilder<TEntity> Update |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<TEntity>.Update; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected FilterDefinitionBuilder<TEntity> Filter |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<TEntity>.Filter; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected IndexKeysDefinitionBuilder<TEntity> IndexKeys |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<TEntity>.IndexKeys; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected IMongoCollection<TEntity> 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<TEntity> CreateCollection() |
||||
|
{ |
||||
|
var databaseCollection = mongoDatabase.GetCollection<TEntity>( |
||||
|
CollectionName(), |
||||
|
CollectionSettings() ?? new MongoCollectionSettings()); |
||||
|
|
||||
|
SetupCollectionAsync(databaseCollection).Wait(); |
||||
|
|
||||
|
return databaseCollection; |
||||
|
} |
||||
|
|
||||
|
protected virtual Task SetupCollectionAsync(IMongoCollection<TEntity> collection) |
||||
|
{ |
||||
|
return Task.FromResult(true); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ModelSchemaRM>, IModelSchemaRepository, ICatchEventConsumer |
||||
|
{ |
||||
|
public MongoModelSchemaRepository(IMongoDatabase database) |
||||
|
: base(database) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
protected override Task SetupCollectionAsync(IMongoCollection<ModelSchemaRM> collection) |
||||
|
{ |
||||
|
return Collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.SchemaId)); |
||||
|
} |
||||
|
|
||||
|
public IQueryable<ModelSchemaRM> QuerySchemas() |
||||
|
{ |
||||
|
return Collection.AsQueryable(); |
||||
|
} |
||||
|
|
||||
|
public Task<List<ModelSchemaRM>> 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<IEvent> @event) |
||||
|
{ |
||||
|
this.DispatchAction(@event.Payload, @event.Headers); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<ModelSchemaRM>, 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<ModelSchemaRM> QuerySchemas() |
|
||||
{ |
|
||||
return Collection.AsQueryable(); |
|
||||
} |
|
||||
|
|
||||
public Task<List<ModelSchemaRM>> QueryAllAsync() |
|
||||
{ |
|
||||
return Collection.Find(s => true).ToListAsync(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<T> |
|
||||
{ |
|
||||
private readonly IMongoCollection<T> collection; |
|
||||
private readonly IndexKeysDefinitionBuilder<T> indexKeys = new IndexKeysDefinitionBuilder<T>(); |
|
||||
|
|
||||
protected IMongoCollection<T> Collection |
|
||||
{ |
|
||||
get { return collection; } |
|
||||
} |
|
||||
|
|
||||
protected IndexKeysDefinitionBuilder<T> IndexKeys |
|
||||
{ |
|
||||
get { return indexKeys; } |
|
||||
} |
|
||||
|
|
||||
protected BaseRepository(IMongoDatabase database, string collectioName) |
|
||||
{ |
|
||||
Guard.NotNull(database, nameof(database)); |
|
||||
Guard.NotNullOrEmpty(collectioName, nameof(collectioName)); |
|
||||
|
|
||||
collection = database.GetCollection<T>(collectioName); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -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<Guid> ProvideTenantIdByDomainAsync(string domain); |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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<MongoPosition>, IStreamPositionStorage |
||||
|
{ |
||||
|
private static readonly ObjectId Id = new ObjectId("507f1f77bcf86cd799439011"); |
||||
|
|
||||
|
public MongoStreamPositionsStorage(IMongoDatabase database) |
||||
|
: base(database) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public Position? ReadPosition() |
||||
|
{ |
||||
|
var document = IFindFluentExtensions.FirstOrDefault<MongoPosition, MongoPosition>(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<MongoPosition, MongoPosition>(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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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<Guid> ProvideTenantIdByDomainAsync(string domain) |
||||
|
{ |
||||
|
return Task.FromResult(Guid.Empty); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue