| | | 1 | | using Anichron.API.Endpoints; |
| | | 2 | | using Anichron.API.Services; |
| | | 3 | | using Anichron.API.Settings; |
| | | 4 | | using Anichron.Core.Data; |
| | | 5 | | using Anichron.Infrastructure.Data; |
| | | 6 | | using Microsoft.AspNetCore.Diagnostics.HealthChecks; |
| | | 7 | | |
| | | 8 | | namespace Anichron.API.Infrastructure; |
| | | 9 | | |
| | | 10 | | public static partial class ApplicationExtensions |
| | | 11 | | { |
| | | 12 | | extension(WebApplication app) |
| | | 13 | | { |
| | | 14 | | public WebApplication MapApiEndpoints() |
| | 0 | 15 | | { |
| | 0 | 16 | | app.MapHealthChecks(ApiPaths.Healthz, new HealthCheckOptions |
| | 0 | 17 | | { |
| | 0 | 18 | | ResponseWriter = HealthCheckResponseWriter.WriteResponseAsync |
| | 0 | 19 | | }).AllowAnonymous(); |
| | | 20 | | |
| | 0 | 21 | | var api = app.MapGroup(ApiPaths.Base); |
| | 0 | 22 | | api.MapAuthEndpoints(); |
| | 0 | 23 | | api.MapUserEndpoints(); |
| | 0 | 24 | | api.MapAdminEndpoints(); |
| | 0 | 25 | | return app; |
| | | 26 | | } |
| | | 27 | | |
| | | 28 | | public async Task MigrateAndSeedDatabaseAsync(CancellationToken ct) |
| | 0 | 29 | | { |
| | | 30 | | const int maxAttempts = AppDefaults.Startup.MaxDbRetryAttempts; |
| | | 31 | | const int retryDelaySeconds = AppDefaults.Startup.DbRetryDelaySeconds; |
| | | 32 | | |
| | 0 | 33 | | for (var attempt = 1; attempt <= maxAttempts; attempt++) |
| | 0 | 34 | | { |
| | | 35 | | try |
| | 0 | 36 | | { |
| | 0 | 37 | | await using var scope = app.Services.CreateAsyncScope(); |
| | 0 | 38 | | var dbContext = scope.ServiceProvider.GetRequiredService<AnichronDbContext>(); |
| | 0 | 39 | | var logger = scope.ServiceProvider.GetRequiredService<ILogger<AnichronDbContext>>(); |
| | | 40 | | |
| | 0 | 41 | | await dbContext.Database.MigrateWithAdvisoryLockAsync(ct); |
| | 0 | 42 | | Log.MigrationApplied(logger); |
| | | 43 | | |
| | 0 | 44 | | var bootstrapSeeder = scope.ServiceProvider.GetRequiredService<IBootstrapSeeder>(); |
| | 0 | 45 | | await bootstrapSeeder.SeedAsync(ct); |
| | | 46 | | |
| | 0 | 47 | | var adminResetService = scope.ServiceProvider.GetRequiredService<IBootstrapResetService>(); |
| | 0 | 48 | | await adminResetService.ResetIfRequestedAsync(ct); |
| | 0 | 49 | | return; |
| | 0 | 50 | | } |
| | 0 | 51 | | catch (Exception ex) when (attempt < maxAttempts) |
| | 0 | 52 | | { |
| | 0 | 53 | | Log.DatabaseNotReady(app.Logger, ex, attempt, maxAttempts, retryDelaySeconds); |
| | 0 | 54 | | await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds), ct); |
| | 0 | 55 | | } |
| | 0 | 56 | | catch (Exception ex) |
| | 0 | 57 | | { |
| | 0 | 58 | | Log.DatabaseUnavailable(app.Logger, ex, AppDefaults.Startup.MaxDbRetryAttempts); |
| | 0 | 59 | | throw new InvalidOperationException( |
| | 0 | 60 | | $"Database unavailable after {AppDefaults.Startup.MaxDbRetryAttempts} attempts. Aborting.", |
| | 0 | 61 | | ex); |
| | | 62 | | } |
| | 0 | 63 | | } |
| | 0 | 64 | | } |
| | | 65 | | } |
| | | 66 | | |
| | | 67 | | private static partial class Log |
| | | 68 | | { |
| | | 69 | | [LoggerMessage(Level = LogLevel.Information, Message = "Database migration applied successfully.")] |
| | | 70 | | public static partial void MigrationApplied(ILogger logger); |
| | | 71 | | |
| | | 72 | | [LoggerMessage(Level = LogLevel.Warning, Message = "Database not ready (attempt {Attempt}/{Max}). Retrying in {D |
| | | 73 | | public static partial void DatabaseNotReady(ILogger logger, Exception ex, int attempt, int max, int delay); |
| | | 74 | | |
| | | 75 | | [LoggerMessage(Level = LogLevel.Error, Message = "Database unavailable after {Max} attempts. Aborting.")] |
| | | 76 | | public static partial void DatabaseUnavailable(ILogger logger, Exception ex, int max); |
| | | 77 | | } |
| | | 78 | | } |