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