Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Notify.Storage.Mongo.Internal;
|
||||
|
||||
namespace StellaOps.Notify.Storage.Mongo.Migrations;
|
||||
|
||||
internal sealed class EnsureNotifyCollectionsMigration : INotifyMongoMigration
|
||||
{
|
||||
private readonly ILogger<EnsureNotifyCollectionsMigration> _logger;
|
||||
|
||||
public EnsureNotifyCollectionsMigration(ILogger<EnsureNotifyCollectionsMigration> logger)
|
||||
=> _logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
|
||||
public string Id => "20251019_notify_collections_v1";
|
||||
|
||||
public async ValueTask ExecuteAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
var requiredCollections = new[]
|
||||
{
|
||||
context.Options.RulesCollection,
|
||||
context.Options.ChannelsCollection,
|
||||
context.Options.TemplatesCollection,
|
||||
context.Options.DeliveriesCollection,
|
||||
context.Options.DigestsCollection,
|
||||
context.Options.LocksCollection,
|
||||
context.Options.AuditCollection,
|
||||
context.Options.MigrationsCollection
|
||||
};
|
||||
|
||||
var cursor = await context.Database
|
||||
.ListCollectionNamesAsync(cancellationToken: cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var existingNames = await cursor.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var collection in requiredCollections)
|
||||
{
|
||||
if (existingNames.Contains(collection, StringComparer.Ordinal))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Creating Notify Mongo collection '{CollectionName}'.", collection);
|
||||
await context.Database.CreateCollectionAsync(collection, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Notify.Storage.Mongo.Internal;
|
||||
|
||||
namespace StellaOps.Notify.Storage.Mongo.Migrations;
|
||||
|
||||
internal sealed class EnsureNotifyIndexesMigration : INotifyMongoMigration
|
||||
{
|
||||
public string Id => "20251019_notify_indexes_v1";
|
||||
|
||||
public async ValueTask ExecuteAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(context);
|
||||
|
||||
await EnsureRulesIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureChannelsIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureTemplatesIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureDeliveriesIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureDigestsIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureLocksIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
await EnsureAuditIndexesAsync(context, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureRulesIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.RulesCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Ascending("enabled");
|
||||
|
||||
var model = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_enabled"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureChannelsIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.ChannelsCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Ascending("type")
|
||||
.Ascending("enabled");
|
||||
|
||||
var model = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_type_enabled"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureTemplatesIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.TemplatesCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Ascending("channelType")
|
||||
.Ascending("key")
|
||||
.Ascending("locale");
|
||||
|
||||
var model = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_channel_key_locale",
|
||||
Unique = true
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureDeliveriesIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.DeliveriesCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Descending("sortKey");
|
||||
|
||||
var sortModel = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_sortKey"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(sortModel, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var statusModel = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("tenantId").Ascending("status"),
|
||||
new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_status"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(statusModel, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (context.Options.DeliveryHistoryRetention > TimeSpan.Zero)
|
||||
{
|
||||
var ttlModel = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("completedAt"),
|
||||
new CreateIndexOptions
|
||||
{
|
||||
Name = "completedAt_ttl",
|
||||
ExpireAfter = context.Options.DeliveryHistoryRetention
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(ttlModel, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task EnsureDigestsIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.DigestsCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Ascending("actionKey");
|
||||
|
||||
var model = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_actionKey"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureLocksIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.LocksCollection);
|
||||
var uniqueModel = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("tenantId").Ascending("resource"),
|
||||
new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_resource",
|
||||
Unique = true
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(uniqueModel, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var ttlModel = new CreateIndexModel<BsonDocument>(
|
||||
Builders<BsonDocument>.IndexKeys.Ascending("expiresAt"),
|
||||
new CreateIndexOptions
|
||||
{
|
||||
Name = "expiresAt_ttl",
|
||||
ExpireAfter = TimeSpan.Zero
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(ttlModel, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static async Task EnsureAuditIndexesAsync(NotifyMongoContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var collection = context.Database.GetCollection<BsonDocument>(context.Options.AuditCollection);
|
||||
var keys = Builders<BsonDocument>.IndexKeys
|
||||
.Ascending("tenantId")
|
||||
.Descending("timestamp");
|
||||
|
||||
var model = new CreateIndexModel<BsonDocument>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "tenant_timestamp"
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StellaOps.Notify.Storage.Mongo.Internal;
|
||||
|
||||
namespace StellaOps.Notify.Storage.Mongo.Migrations;
|
||||
|
||||
internal interface INotifyMongoMigration
|
||||
{
|
||||
string Id { get; }
|
||||
|
||||
ValueTask ExecuteAsync(NotifyMongoContext context, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
|
||||
namespace StellaOps.Notify.Storage.Mongo.Migrations;
|
||||
|
||||
internal sealed class NotifyMongoMigrationRecord
|
||||
{
|
||||
[BsonId]
|
||||
public ObjectId Id { get; init; }
|
||||
|
||||
[BsonElement("migrationId")]
|
||||
public required string MigrationId { get; init; }
|
||||
|
||||
[BsonElement("appliedAt")]
|
||||
public required DateTimeOffset AppliedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using MongoDB.Bson;
|
||||
using MongoDB.Driver;
|
||||
using StellaOps.Notify.Storage.Mongo.Internal;
|
||||
|
||||
namespace StellaOps.Notify.Storage.Mongo.Migrations;
|
||||
|
||||
internal sealed class NotifyMongoMigrationRunner
|
||||
{
|
||||
private readonly NotifyMongoContext _context;
|
||||
private readonly IReadOnlyList<INotifyMongoMigration> _migrations;
|
||||
private readonly ILogger<NotifyMongoMigrationRunner> _logger;
|
||||
|
||||
public NotifyMongoMigrationRunner(
|
||||
NotifyMongoContext context,
|
||||
IEnumerable<INotifyMongoMigration> migrations,
|
||||
ILogger<NotifyMongoMigrationRunner> logger)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
ArgumentNullException.ThrowIfNull(migrations);
|
||||
_migrations = 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 collection = _context.Database.GetCollection<NotifyMongoMigrationRecord>(_context.Options.MigrationsCollection);
|
||||
await EnsureMigrationIndexAsync(collection, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var applied = await collection
|
||||
.Find(FilterDefinition<NotifyMongoMigrationRecord>.Empty)
|
||||
.Project(record => record.MigrationId)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var appliedSet = applied.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (var migration in _migrations)
|
||||
{
|
||||
if (appliedSet.Contains(migration.Id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Applying Notify Mongo migration {MigrationId}.", migration.Id);
|
||||
await migration.ExecuteAsync(_context, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var record = new NotifyMongoMigrationRecord
|
||||
{
|
||||
Id = ObjectId.GenerateNewId(),
|
||||
MigrationId = migration.Id,
|
||||
AppliedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
|
||||
await collection.InsertOneAsync(record, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
_logger.LogInformation("Completed Notify Mongo migration {MigrationId}.", migration.Id);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task EnsureMigrationIndexAsync(
|
||||
IMongoCollection<NotifyMongoMigrationRecord> collection,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var keys = Builders<NotifyMongoMigrationRecord>.IndexKeys.Ascending(record => record.MigrationId);
|
||||
var model = new CreateIndexModel<NotifyMongoMigrationRecord>(keys, new CreateIndexOptions
|
||||
{
|
||||
Name = "migrationId_unique",
|
||||
Unique = true
|
||||
});
|
||||
|
||||
await collection.Indexes.CreateOneAsync(model, cancellationToken: cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user