using System.Text.Json; using Microsoft.Extensions.Logging; using ZB.MOM.WW.CBDDC.Core.Network; using ZB.MOM.WW.CBDDC.Core.Storage; using ZB.MOM.WW.CBDDC.Core.Sync; using ZB.MOM.WW.CBDDC.Persistence.Surreal; namespace ZB.MOM.WW.CBDDC.Sample.Console; /// /// Surreal-backed document store for the sample app. /// public class SampleDocumentStore : SurrealDocumentStore { private const string UsersCollection = "Users"; private const string TodoListsCollection = "TodoLists"; private const string LogsCollection = "Logs"; private const string TimeseriesCollection = "Timeseries"; /// /// Initializes a new instance of the class. /// /// Sample database context. /// Peer configuration provider. /// Vector clock service. /// Optional logger. public SampleDocumentStore( SampleDbContext context, IPeerNodeConfigurationProvider configProvider, IVectorClockService vectorClockService, ILogger? logger = null) : base( context, context.SurrealEmbeddedClient, context.SchemaInitializer, configProvider, vectorClockService, new LastWriteWinsConflictResolver(), null, null, logger) { WatchCollection(UsersCollection, context.Users, u => u.Id); WatchCollection(TodoListsCollection, context.TodoLists, t => t.Id); WatchCollection(LogsCollection, context.Logs, entry => entry.Id); WatchCollection(TimeseriesCollection, context.Timeseries, point => point.Id); } /// protected override async Task ApplyContentToEntityAsync( string collection, string key, JsonElement content, CancellationToken cancellationToken) { await UpsertEntityAsync(collection, key, content, cancellationToken); } /// protected override async Task ApplyContentToEntitiesBatchAsync( IEnumerable<(string Collection, string Key, JsonElement Content)> documents, CancellationToken cancellationToken) { foreach ((string collection, string key, var content) in documents) await UpsertEntityAsync(collection, key, content, cancellationToken); } /// protected override async Task GetEntityAsJsonAsync( string collection, string key, CancellationToken cancellationToken) { return collection switch { UsersCollection => SerializeEntity(await _context.Users.FindByIdAsync(key, cancellationToken)), TodoListsCollection => SerializeEntity(await _context.TodoLists.FindByIdAsync(key, cancellationToken)), LogsCollection => SerializeEntity(await _context.Logs.FindByIdAsync(key, cancellationToken)), TimeseriesCollection => SerializeEntity(await _context.Timeseries.FindByIdAsync(key, cancellationToken)), _ => null }; } /// protected override async Task RemoveEntityAsync( string collection, string key, CancellationToken cancellationToken) { await DeleteEntityAsync(collection, key, cancellationToken); } /// protected override async Task RemoveEntitiesBatchAsync( IEnumerable<(string Collection, string Key)> documents, CancellationToken cancellationToken) { foreach ((string collection, string key) in documents) await DeleteEntityAsync(collection, key, cancellationToken); } /// protected override async Task> GetAllEntitiesAsJsonAsync( string collection, CancellationToken cancellationToken) { return collection switch { UsersCollection => (await _context.Users.FindAllAsync(cancellationToken)) .Select(u => (u.Id, SerializeEntity(u)!.Value)) .ToList(), TodoListsCollection => (await _context.TodoLists.FindAllAsync(cancellationToken)) .Select(t => (t.Id, SerializeEntity(t)!.Value)) .ToList(), LogsCollection => (await _context.Logs.FindAllAsync(cancellationToken)) .Select(entry => (entry.Id, SerializeEntity(entry)!.Value)) .ToList(), TimeseriesCollection => (await _context.Timeseries.FindAllAsync(cancellationToken)) .Select(point => (point.Id, SerializeEntity(point)!.Value)) .ToList(), _ => [] }; } private async Task UpsertEntityAsync( string collection, string key, JsonElement content, CancellationToken cancellationToken) { switch (collection) { case UsersCollection: var user = content.Deserialize() ?? throw new InvalidOperationException("Failed to deserialize user."); user.Id = key; if (await _context.Users.FindByIdAsync(key, cancellationToken) == null) await _context.Users.InsertAsync(user, cancellationToken); else await _context.Users.UpdateAsync(user, cancellationToken); break; case TodoListsCollection: var todo = content.Deserialize() ?? throw new InvalidOperationException("Failed to deserialize todo list."); todo.Id = key; if (await _context.TodoLists.FindByIdAsync(key, cancellationToken) == null) await _context.TodoLists.InsertAsync(todo, cancellationToken); else await _context.TodoLists.UpdateAsync(todo, cancellationToken); break; case LogsCollection: var logEntry = content.Deserialize() ?? throw new InvalidOperationException("Failed to deserialize telemetry log."); logEntry.Id = key; if (await _context.Logs.FindByIdAsync(key, cancellationToken) == null) await _context.Logs.InsertAsync(logEntry, cancellationToken); else await _context.Logs.UpdateAsync(logEntry, cancellationToken); break; case TimeseriesCollection: var point = content.Deserialize() ?? throw new InvalidOperationException("Failed to deserialize timeseries point."); point.Id = key; if (await _context.Timeseries.FindByIdAsync(key, cancellationToken) == null) await _context.Timeseries.InsertAsync(point, cancellationToken); else await _context.Timeseries.UpdateAsync(point, cancellationToken); break; default: throw new NotSupportedException($"Collection '{collection}' is not supported for sync."); } } private async Task DeleteEntityAsync(string collection, string key, CancellationToken cancellationToken) { switch (collection) { case UsersCollection: await _context.Users.DeleteAsync(key, cancellationToken); break; case TodoListsCollection: await _context.TodoLists.DeleteAsync(key, cancellationToken); break; case LogsCollection: await _context.Logs.DeleteAsync(key, cancellationToken); break; case TimeseriesCollection: await _context.Timeseries.DeleteAsync(key, cancellationToken); break; default: _logger.LogWarning("Attempted to remove entity from unsupported collection: {Collection}", collection); break; } } private static JsonElement? SerializeEntity(T? entity) where T : class { return entity == null ? null : JsonSerializer.SerializeToElement(entity); } }