mirror of https://github.com/Squidex/squidex.git
35 changed files with 825 additions and 771 deletions
@ -0,0 +1,11 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
namespace Squidex.Infrastructure.MongoDb.EventStore |
||||
|
{ |
||||
|
public class MongoStreamsRepository |
||||
|
{ |
||||
|
} |
||||
|
} |
||||
@ -1,13 +1,19 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// ICatchEventConsumer.cs
|
// IEventCatchConsumer.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events |
namespace Squidex.Infrastructure.CQRS.Events |
||||
{ |
{ |
||||
public interface ICatchEventConsumer : IEventConsumer |
public interface IEventCatchConsumer |
||||
{ |
{ |
||||
|
Task<long> GetLastHandledEventNumber(); |
||||
|
|
||||
|
Task On(Envelope<IEvent> @event, long eventNumber); |
||||
} |
} |
||||
} |
} |
||||
@ -1,13 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ILiveEventConsumer.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events |
|
||||
{ |
|
||||
public interface ILiveEventConsumer : IEventConsumer |
|
||||
{ |
|
||||
} |
|
||||
} |
|
||||
@ -1,32 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// InMemoryEventBus.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Reactive.Subjects; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events |
|
||||
{ |
|
||||
public class InMemoryEventBus : IEventPublisher, IEventStream |
|
||||
{ |
|
||||
private readonly Subject<EventData> subject = new Subject<EventData>(); |
|
||||
|
|
||||
public void Dispose() |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
public void Publish(EventData eventData) |
|
||||
{ |
|
||||
subject.OnNext(eventData); |
|
||||
} |
|
||||
|
|
||||
public void Connect(string queuePrefix, Action<EventData> received) |
|
||||
{ |
|
||||
subject.Subscribe(received); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,15 +1,25 @@ |
|||||
// ==========================================================================
|
// ==========================================================================
|
||||
// IEventPublisher.cs
|
// StoredEvent.cs
|
||||
// Squidex Headless CMS
|
// Squidex Headless CMS
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
// Copyright (c) Squidex Group
|
// Copyright (c) Squidex Group
|
||||
// All rights reserved.
|
// All rights reserved.
|
||||
// ==========================================================================
|
// ==========================================================================
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Events |
namespace Squidex.Infrastructure.CQRS.Events |
||||
{ |
{ |
||||
public interface IEventPublisher |
public sealed class StoredEvent |
||||
|
{ |
||||
|
public long EventNumber { get; } |
||||
|
|
||||
|
public EventData Data { get; } |
||||
|
|
||||
|
public StoredEvent(long eventNumber, EventData data) |
||||
{ |
{ |
||||
void Publish(EventData eventData); |
Guard.NotNull(data, nameof(data)); |
||||
|
|
||||
|
EventNumber = eventNumber; |
||||
|
|
||||
|
Data = data; |
||||
|
} |
||||
} |
} |
||||
} |
} |
||||
@ -1,17 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// IReplayableStore.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Replay |
|
||||
{ |
|
||||
public interface IReplayableStore |
|
||||
{ |
|
||||
Task ClearAsync(); |
|
||||
} |
|
||||
} |
|
||||
@ -1,107 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ReplayGenerator.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
using System; |
|
||||
using System.Collections.Generic; |
|
||||
using System.Reactive.Linq; |
|
||||
using System.Threading.Tasks; |
|
||||
using Microsoft.Extensions.Logging; |
|
||||
using Squidex.Infrastructure.CQRS.Events; |
|
||||
|
|
||||
namespace Squidex.Infrastructure.CQRS.Replay |
|
||||
{ |
|
||||
public sealed class ReplayGenerator : ICliCommand |
|
||||
{ |
|
||||
private readonly ILogger<ReplayGenerator> logger; |
|
||||
private readonly IEventStore eventStore; |
|
||||
private readonly IEventPublisher eventPublisher; |
|
||||
private readonly IEnumerable<IReplayableStore> stores; |
|
||||
|
|
||||
public string Name { get; } = "replay"; |
|
||||
|
|
||||
public ReplayGenerator( |
|
||||
ILogger<ReplayGenerator> logger, |
|
||||
IEventStore eventStore, |
|
||||
IEventPublisher eventPublisher, |
|
||||
IEnumerable<IReplayableStore> stores) |
|
||||
{ |
|
||||
Guard.NotNull(logger, nameof(logger)); |
|
||||
Guard.NotNull(eventStore, nameof(eventStore)); |
|
||||
Guard.NotNull(eventPublisher, nameof(eventPublisher)); |
|
||||
Guard.NotNull(stores, nameof(stores)); |
|
||||
|
|
||||
this.stores = stores; |
|
||||
this.logger = logger; |
|
||||
this.eventStore = eventStore; |
|
||||
this.eventPublisher = eventPublisher; |
|
||||
} |
|
||||
|
|
||||
public void Execute(string[] args) |
|
||||
{ |
|
||||
ReplayAllAsync().Wait(); |
|
||||
} |
|
||||
|
|
||||
public async Task ReplayAllAsync() |
|
||||
{ |
|
||||
logger.LogDebug("Starting to replay all events"); |
|
||||
|
|
||||
if (!await ClearAsync()) |
|
||||
{ |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
await ReplayEventsAsync(); |
|
||||
|
|
||||
logger.LogDebug("Finished to replay all events"); |
|
||||
} |
|
||||
|
|
||||
private async Task ReplayEventsAsync() |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
logger.LogDebug("Replaying all messages"); |
|
||||
|
|
||||
await eventStore.GetEventsAsync().ForEachAsync(eventData => |
|
||||
{ |
|
||||
eventPublisher.Publish(eventData); |
|
||||
}); |
|
||||
|
|
||||
logger.LogDebug("Replayed all messages"); |
|
||||
} |
|
||||
catch (Exception e) |
|
||||
{ |
|
||||
logger.LogCritical(InfrastructureErrors.ReplayPublishingFailed, e, "Failed to publish events to {0}", eventPublisher); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private async Task<bool> ClearAsync() |
|
||||
{ |
|
||||
logger.LogDebug("Clearing replayable stores"); |
|
||||
|
|
||||
foreach (var store in stores) |
|
||||
{ |
|
||||
try |
|
||||
{ |
|
||||
await store.ClearAsync(); |
|
||||
|
|
||||
logger.LogDebug("Cleared store {0}", store); |
|
||||
} |
|
||||
catch (Exception e) |
|
||||
{ |
|
||||
logger.LogCritical(InfrastructureErrors.ReplayClearingFailed, e, "Failed to clear store {0}", store); |
|
||||
|
|
||||
return false; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
logger.LogDebug("Cleared replayable stores"); |
|
||||
|
|
||||
return true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -1,17 +0,0 @@ |
|||||
// ==========================================================================
|
|
||||
// ICommand.cs
|
|
||||
// Squidex Headless CMS
|
|
||||
// ==========================================================================
|
|
||||
// Copyright (c) Squidex Group
|
|
||||
// All rights reserved.
|
|
||||
// ==========================================================================
|
|
||||
|
|
||||
namespace Squidex.Infrastructure |
|
||||
{ |
|
||||
public interface ICliCommand |
|
||||
{ |
|
||||
string Name { get; } |
|
||||
|
|
||||
void Execute(string[] args); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,68 @@ |
|||||
|
// ==========================================================================
|
||||
|
// CompletionTimer.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
// ReSharper disable InvertIf
|
||||
|
|
||||
|
namespace Squidex.Infrastructure.Timers |
||||
|
{ |
||||
|
public sealed class CompletionTimer : DisposableObject |
||||
|
{ |
||||
|
private readonly CancellationTokenSource disposeCancellationTokenSource = new CancellationTokenSource(); |
||||
|
private readonly Task runTask; |
||||
|
private CancellationTokenSource delayCancellationSource; |
||||
|
|
||||
|
public CompletionTimer(int delay, Func<CancellationToken, Task> callback) |
||||
|
{ |
||||
|
Guard.NotNull(callback, nameof(callback)); |
||||
|
Guard.GreaterThan(delay, 0, nameof(delay)); |
||||
|
|
||||
|
runTask = RunInternal(delay, callback); |
||||
|
} |
||||
|
|
||||
|
private async Task RunInternal(int delay, Func<CancellationToken, Task> callback) |
||||
|
{ |
||||
|
while (!disposeCancellationTokenSource.IsCancellationRequested) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
await callback(disposeCancellationTokenSource.Token).ConfigureAwait(false); |
||||
|
} |
||||
|
catch (TaskCanceledException) |
||||
|
{ |
||||
|
Console.WriteLine("Task in TriggerTimer has been cancelled."); |
||||
|
} |
||||
|
|
||||
|
delayCancellationSource = new CancellationTokenSource(); |
||||
|
|
||||
|
await Task.Delay(delay, delayCancellationSource.Token).ConfigureAwait(false); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void DisposeObject(bool disposing) |
||||
|
{ |
||||
|
if (disposing) |
||||
|
{ |
||||
|
delayCancellationSource?.Cancel(); |
||||
|
disposeCancellationTokenSource.Cancel(); |
||||
|
|
||||
|
runTask.Wait(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Trigger() |
||||
|
{ |
||||
|
ThrowIfDisposed(); |
||||
|
|
||||
|
delayCancellationSource?.Cancel(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,111 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoAppRepository_EventHandling.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Events.Apps; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Read.MongoDb.Utils; |
||||
|
|
||||
|
namespace Squidex.Read.MongoDb.Apps |
||||
|
{ |
||||
|
public partial class MongoAppRepository |
||||
|
{ |
||||
|
public Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(AppCreated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await Collection.CreateAsync(headers, a => |
||||
|
{ |
||||
|
SimpleMapper.Map(@event, a); |
||||
|
}); |
||||
|
|
||||
|
appProvider.Remove(headers.AggregateId()); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppContributorAssigned @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
var contributor = a.Contributors.GetOrAddNew(@event.ContributorId); |
||||
|
|
||||
|
SimpleMapper.Map(@event, contributor); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppContributorRemoved @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Contributors.Remove(@event.ContributorId); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppClientAttached @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Clients[@event.Id] = SimpleMapper.Map(@event, new MongoAppClientEntity()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppClientRevoked @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Clients.Remove(@event.Id); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppClientRenamed @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Clients[@event.Id].Name = @event.Name; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppLanguageAdded @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Languages.Add(@event.Language.Iso2Code); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppLanguageRemoved @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.Languages.Remove(@event.Language.Iso2Code); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(AppMasterLanguageSet @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateAsync(headers, a => |
||||
|
{ |
||||
|
a.MasterLanguage = @event.Language.Iso2Code; |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public async Task UpdateAsync(EnvelopeHeaders headers, Action<MongoAppEntity> updater) |
||||
|
{ |
||||
|
await Collection.UpdateAsync(headers, updater); |
||||
|
|
||||
|
appProvider.Remove(headers.AggregateId()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,148 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoContentRepository_EventHandling.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Bson; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Events; |
||||
|
using Squidex.Events.Contents; |
||||
|
using Squidex.Events.Schemas; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Read.MongoDb.Utils; |
||||
|
|
||||
|
// ReSharper disable ConvertToLambdaExpression
|
||||
|
|
||||
|
namespace Squidex.Read.MongoDb.Contents |
||||
|
{ |
||||
|
public partial class MongoContentRepository |
||||
|
{ |
||||
|
protected UpdateDefinitionBuilder<MongoContentEntity> Update |
||||
|
{ |
||||
|
get |
||||
|
{ |
||||
|
return Builders<MongoContentEntity>.Update; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public async Task ClearAsync() |
||||
|
{ |
||||
|
using (var collections = await database.ListCollectionsAsync()) |
||||
|
{ |
||||
|
while (await collections.MoveNextAsync()) |
||||
|
{ |
||||
|
foreach (var collection in collections.Current) |
||||
|
{ |
||||
|
var name = collection["name"].ToString(); |
||||
|
|
||||
|
if (name.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase)) |
||||
|
{ |
||||
|
await database.DropCollectionAsync(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
||||
|
} |
||||
|
|
||||
|
protected Task On(SchemaCreated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaIdAsync(headers.AggregateId(), async collection => |
||||
|
{ |
||||
|
await collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.IsPublished)); |
||||
|
await collection.Indexes.CreateOneAsync(IndexKeys.Text(x => x.Text)); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ContentCreated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaAsync(headers.SchemaId(), (collection, schema) => |
||||
|
{ |
||||
|
return collection.CreateAsync(headers, x => |
||||
|
{ |
||||
|
SimpleMapper.Map(@event, x); |
||||
|
|
||||
|
x.SetData(schema, @event.Data); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ContentUpdated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaAsync(headers.SchemaId(), (collection, schema) => |
||||
|
{ |
||||
|
return collection.UpdateAsync(headers, x => |
||||
|
{ |
||||
|
x.SetData(schema, @event.Data); |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ContentPublished @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaIdAsync(headers.SchemaId(), collection => |
||||
|
{ |
||||
|
return collection.UpdateAsync(headers, x => |
||||
|
{ |
||||
|
x.IsPublished = true; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ContentUnpublished @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaIdAsync(headers.SchemaId(), collection => |
||||
|
{ |
||||
|
return collection.UpdateAsync(headers, x => |
||||
|
{ |
||||
|
x.IsPublished = false; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(ContentDeleted @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaIdAsync(headers.SchemaId(), collection => |
||||
|
{ |
||||
|
return collection.UpdateAsync(headers, x => |
||||
|
{ |
||||
|
x.IsDeleted = true; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldDeleted @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return ForSchemaIdAsync(headers.SchemaId(), collection => |
||||
|
{ |
||||
|
return collection.UpdateManyAsync(new BsonDocument(), Update.Unset(new StringFieldDefinition<MongoContentEntity>($"Data.{@event.FieldId}"))); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private async Task ForSchemaIdAsync(Guid schemaId, Func<IMongoCollection<MongoContentEntity>, Task> action) |
||||
|
{ |
||||
|
var collection = GetCollection(schemaId); |
||||
|
|
||||
|
await action(collection); |
||||
|
} |
||||
|
|
||||
|
private IMongoCollection<MongoContentEntity> GetCollection(Guid schemaId) |
||||
|
{ |
||||
|
var name = $"{Prefix}{schemaId}"; |
||||
|
|
||||
|
return database.GetCollection<MongoContentEntity>(name); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,112 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoSchemaRepository_EventHandling.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Squidex.Core.Schemas; |
||||
|
using Squidex.Events.Schemas; |
||||
|
using Squidex.Events.Schemas.Utils; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.Dispatching; |
||||
|
using Squidex.Infrastructure.Reflection; |
||||
|
using Squidex.Read.MongoDb.Utils; |
||||
|
|
||||
|
namespace Squidex.Read.MongoDb.Schemas |
||||
|
{ |
||||
|
public partial class MongoSchemaRepository |
||||
|
{ |
||||
|
public Task On(Envelope<IEvent> @event) |
||||
|
{ |
||||
|
return this.DispatchActionAsync(@event.Payload, @event.Headers); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(SchemaCreated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
var schema = SchemaEventDispatcher.Dispatch(@event); |
||||
|
|
||||
|
await Collection.CreateAsync(headers, s => { UpdateSchema(s, schema); SimpleMapper.Map(@event, s); }); |
||||
|
|
||||
|
schemaProvider.Remove(headers.AggregateId()); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldDeleted @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldDisabled @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldEnabled @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldHidden @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldShown @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldUpdated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(SchemaUpdated @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(SchemaPublished @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(SchemaUnpublished @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s)); |
||||
|
} |
||||
|
|
||||
|
protected Task On(FieldAdded @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
return UpdateSchema(headers, s => SchemaEventDispatcher.Dispatch(@event, s, registry)); |
||||
|
} |
||||
|
|
||||
|
protected async Task On(SchemaDeleted @event, EnvelopeHeaders headers) |
||||
|
{ |
||||
|
await Collection.UpdateAsync(headers, s => s.IsDeleted = true); |
||||
|
|
||||
|
schemaProvider.Remove(headers.AggregateId()); |
||||
|
} |
||||
|
|
||||
|
private async Task UpdateSchema(EnvelopeHeaders headers, Func<Schema, Schema> updater) |
||||
|
{ |
||||
|
await Collection.UpdateAsync(headers, e => UpdateSchema(e, updater)); |
||||
|
|
||||
|
schemaProvider.Remove(headers.AggregateId()); |
||||
|
} |
||||
|
|
||||
|
private void UpdateSchema(MongoSchemaEntity entity, Func<Schema, Schema> updater) |
||||
|
{ |
||||
|
entity.UpdateSchema(serializer, updater); |
||||
|
} |
||||
|
|
||||
|
private void UpdateSchema(MongoSchemaEntity entity, Schema schema) |
||||
|
{ |
||||
|
entity.SerializeSchema(schema, serializer); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
// ==========================================================================
|
||||
|
// MongoDbStore.cs
|
||||
|
// Squidex Headless CMS
|
||||
|
// ==========================================================================
|
||||
|
// Copyright (c) Squidex Group
|
||||
|
// All rights reserved.
|
||||
|
// ==========================================================================
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
using MongoDB.Bson; |
||||
|
using MongoDB.Driver; |
||||
|
using Squidex.Infrastructure; |
||||
|
using Squidex.Infrastructure.CQRS; |
||||
|
using Squidex.Infrastructure.CQRS.Events; |
||||
|
using Squidex.Infrastructure.MongoDb; |
||||
|
|
||||
|
namespace Squidex.Read.MongoDb.Utils |
||||
|
{ |
||||
|
public sealed class EventPosition |
||||
|
{ |
||||
|
public string Name { get; set; } |
||||
|
|
||||
|
public long EventNumber { get; set; } |
||||
|
} |
||||
|
|
||||
|
public sealed class MongoDbConsumerWrapper : MongoRepositoryBase<EventPosition>, IEventCatchConsumer |
||||
|
{ |
||||
|
private static readonly UpdateOptions upsert = new UpdateOptions { IsUpsert = true }; |
||||
|
private readonly IEventConsumer eventConsumer; |
||||
|
private readonly string eventStoreName; |
||||
|
|
||||
|
public MongoDbConsumerWrapper(IMongoDatabase database, IEventConsumer eventConsumer) |
||||
|
: base(database) |
||||
|
{ |
||||
|
Guard.NotNull(eventConsumer, nameof(eventConsumer)); |
||||
|
|
||||
|
this.eventConsumer = eventConsumer; |
||||
|
|
||||
|
eventStoreName = GetType().Name; |
||||
|
} |
||||
|
|
||||
|
protected override string CollectionName() |
||||
|
{ |
||||
|
return "EventPositions"; |
||||
|
} |
||||
|
|
||||
|
protected override Task SetupCollectionAsync(IMongoCollection<EventPosition> collection) |
||||
|
{ |
||||
|
return collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.Name), new CreateIndexOptions { Unique = true }); |
||||
|
} |
||||
|
|
||||
|
public async Task On(Envelope<IEvent> @event, long eventNumber) |
||||
|
{ |
||||
|
await eventConsumer.On(@event); |
||||
|
|
||||
|
await SetLastHandledEventNumber(eventNumber); |
||||
|
} |
||||
|
|
||||
|
private Task SetLastHandledEventNumber(long eventNumber) |
||||
|
{ |
||||
|
return Collection.ReplaceOneAsync(x => x.Name == eventStoreName, new EventPosition { Name = eventStoreName, EventNumber = eventNumber }, upsert); |
||||
|
} |
||||
|
|
||||
|
public async Task<long> GetLastHandledEventNumber() |
||||
|
{ |
||||
|
var collectionPosition = |
||||
|
await Collection |
||||
|
.Find(new BsonDocument()).SortByDescending(x => x.EventNumber).Limit(1) |
||||
|
.FirstOrDefaultAsync(); |
||||
|
|
||||
|
return collectionPosition?.EventNumber ?? -1; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue