using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using MongoDB.Driver; namespace StellaOps.Vexer.Storage.Mongo.Migrations; internal sealed class VexMongoMigrationRunner { private readonly IMongoDatabase _database; private readonly IReadOnlyList _migrations; private readonly ILogger _logger; public VexMongoMigrationRunner( IMongoDatabase database, IEnumerable migrations, ILogger logger) { _database = database ?? throw new ArgumentNullException(nameof(database)); _migrations = (migrations ?? throw new ArgumentNullException(nameof(migrations))) .OrderBy(migration => migration.Id, StringComparer.Ordinal) .ToArray(); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public async ValueTask RunAsync(CancellationToken cancellationToken) { if (_migrations.Count == 0) { return; } var migrationsCollection = _database.GetCollection(VexMongoCollectionNames.Migrations); await EnsureMigrationsIndexAsync(migrationsCollection, cancellationToken).ConfigureAwait(false); var applied = await LoadAppliedMigrationsAsync(migrationsCollection, cancellationToken).ConfigureAwait(false); foreach (var migration in _migrations) { if (applied.Contains(migration.Id)) { continue; } _logger.LogInformation("Applying Vexer Mongo migration {MigrationId}", migration.Id); await migration.ExecuteAsync(_database, cancellationToken).ConfigureAwait(false); var record = new VexMigrationRecord(migration.Id, DateTimeOffset.UtcNow); await migrationsCollection.InsertOneAsync(record, cancellationToken: cancellationToken).ConfigureAwait(false); _logger.LogInformation("Completed Vexer Mongo migration {MigrationId}", migration.Id); } } private static ValueTask EnsureMigrationsIndexAsync( IMongoCollection collection, CancellationToken cancellationToken) { // default _id index already enforces uniqueness return ValueTask.CompletedTask; } private static async ValueTask> LoadAppliedMigrationsAsync( IMongoCollection collection, CancellationToken cancellationToken) { var records = await collection.Find(FilterDefinition.Empty) .ToListAsync(cancellationToken) .ConfigureAwait(false); return records.Select(static record => record.Id) .ToHashSet(StringComparer.Ordinal); } }