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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
|
|||
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
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex Group
|
|||
// All rights reserved.
|
|||
// ==========================================================================
|
|||
|
|||
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