Fixed buttons and reset logic, databank is completly backed up and wiped now
This commit is contained in:
@@ -121,7 +121,7 @@
|
||||
<section class="card shadow-sm border-danger-subtle">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 card-title">Reset setup</h2>
|
||||
<p class="text-secondary">Move existing users to backup and mark setup as incomplete.</p>
|
||||
<p class="text-secondary">Copy the current database to a timestamped backup database and start setup with a fresh database.</p>
|
||||
|
||||
<form method="post" action="/admin/reset-setup">
|
||||
<button type="submit" class="btn btn-outline-danger">
|
||||
|
||||
@@ -13,6 +13,12 @@ public partial class Admin
|
||||
[Inject]
|
||||
protected AccessCodeService AccessCodeService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected DatabaseBackupService DatabaseBackupService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected GuestEntryService GuestEntryService { get; set; } = default!;
|
||||
|
||||
protected string Message { get; set; } = "";
|
||||
|
||||
protected string NewGuestAccessCode { get; set; } = "";
|
||||
@@ -61,16 +67,18 @@ public partial class Admin
|
||||
|
||||
try
|
||||
{
|
||||
await UserService.BackupAndClearUsersAsync("Setup reset from admin panel");
|
||||
var backupResult = await DatabaseBackupService.BackupAndDropActiveDatabaseAsync("Setup reset from admin panel");
|
||||
await SetupStatusService.ResetSetupAsync();
|
||||
await UserService.EnsureIndexesAsync();
|
||||
await GuestEntryService.EnsureIndexesAsync();
|
||||
await AccessCodeService.EnsureIndexesAsync();
|
||||
Message = $"Setup has been reset. The old database was copied to {backupResult.BackupDatabaseName}.";
|
||||
}
|
||||
catch
|
||||
{
|
||||
Message = "Setup could not be reset.";
|
||||
return;
|
||||
}
|
||||
|
||||
Message = "Setup has been reset. Existing users were moved to backup.";
|
||||
}
|
||||
|
||||
protected async Task UpdateGuestAccessCode()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<p class="text-secondary mb-0">Create the first admin and initial guest access code.</p>
|
||||
</div>
|
||||
|
||||
@if (IsSetupComplete is null || HasAnyUsers is null || HasAnyUserBackups is null)
|
||||
@if (IsSetupComplete is null || HasAnyUsers is null || HasAnyDatabaseBackups is null)
|
||||
{
|
||||
<section class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
@@ -40,9 +40,9 @@ else if (HasAnyUsers == true)
|
||||
}
|
||||
else
|
||||
{
|
||||
<form class="admin-panel-grid" method="post" action="/setup/complete">
|
||||
<div class="admin-panel-grid">
|
||||
<section class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<form class="card-body" method="post" action="/setup/complete">
|
||||
<h2 class="h5 card-title">First admin</h2>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -59,11 +59,7 @@ else
|
||||
<label class="form-label" for="setup-password">Password</label>
|
||||
<input id="setup-password" class="form-control" type="password" name="password" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 card-title">Guest access</h2>
|
||||
<p class="text-secondary">Set the first code guests use to view the list.</p>
|
||||
|
||||
@@ -80,20 +76,22 @@ else
|
||||
{
|
||||
<div class="alert alert-warning mt-3 mb-0" role="alert">@ErrorMessage</div>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@if (HasAnyUserBackups == true)
|
||||
@if (HasAnyDatabaseBackups == true)
|
||||
{
|
||||
<section class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h2 class="h5 card-title">Restore backup</h2>
|
||||
<p class="text-secondary">Restore the latest backed-up users.</p>
|
||||
<button type="button" class="btn btn-outline-primary" @onclick="RestoreLatestBackup">
|
||||
Restore latest user backup
|
||||
</button>
|
||||
<p class="text-secondary">Restore the latest database backup created by setup reset.</p>
|
||||
<form method="post" action="/setup/restore">
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
Restore latest database backup
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -13,13 +13,19 @@ public partial class Setup : ComponentBase
|
||||
[Inject]
|
||||
protected AccessCodeService AccessCodeService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected GuestEntryService GuestEntryService { get; set; } = default!;
|
||||
|
||||
[Inject]
|
||||
protected DatabaseBackupService DatabaseBackupService { get; set; } = default!;
|
||||
|
||||
protected string Username { get; set; } = "";
|
||||
protected string DisplayName { get; set; } = "";
|
||||
protected string Password { get; set; } = "";
|
||||
protected string ErrorMessage { get; set; } = "";
|
||||
|
||||
protected bool? HasAnyUsers { get; set; }
|
||||
protected bool? HasAnyUserBackups { get; set; }
|
||||
protected bool? HasAnyDatabaseBackups { get; set; }
|
||||
|
||||
protected string GuestAccessCode { get; set; } = "";
|
||||
|
||||
@@ -28,6 +34,15 @@ public partial class Setup : ComponentBase
|
||||
[SupplyParameterFromQuery(Name = "error")]
|
||||
public string? SetupError { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "backupDatabase")]
|
||||
public string? BackupDatabase { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "backedUpCollections")]
|
||||
public int? BackedUpCollections { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery(Name = "backedUpDocuments")]
|
||||
public long? BackedUpDocuments { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(SetupError))
|
||||
@@ -36,6 +51,9 @@ public partial class Setup : ComponentBase
|
||||
{
|
||||
"existing-user" => "Setup cannot create another initial admin user.",
|
||||
"setup-failed" => "Setup could not be completed. No partial admin user was kept.",
|
||||
"setup-reset" => $"Setup has been reset. The old database was copied to {BackupDatabase ?? "a backup database"}. {BackedUpCollections ?? 0} collections and {BackedUpDocuments ?? 0} documents were backed up.",
|
||||
"restore-users-exist" => "Cannot restore backup while users already exist.",
|
||||
"restore-failed" => "Could not restore latest database backup.",
|
||||
_ => SetupError
|
||||
};
|
||||
}
|
||||
@@ -44,14 +62,14 @@ public partial class Setup : ComponentBase
|
||||
{
|
||||
IsSetupComplete = await SetupStatusService.IsSetupCompleteAsync();
|
||||
HasAnyUsers = await UserService.HasAnyUsersAsync();
|
||||
HasAnyUserBackups = await UserService.HasAnyUserBackupsAsync();
|
||||
HasAnyDatabaseBackups = await DatabaseBackupService.HasAnyDatabaseBackupsAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
ErrorMessage = "Setup status could not be loaded. Please try again.";
|
||||
IsSetupComplete = false;
|
||||
HasAnyUsers = false;
|
||||
HasAnyUserBackups = false;
|
||||
HasAnyDatabaseBackups = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,33 +155,36 @@ public partial class Setup : ComponentBase
|
||||
return;
|
||||
}
|
||||
|
||||
if (HasAnyUserBackups != true)
|
||||
if (HasAnyDatabaseBackups != true)
|
||||
{
|
||||
ErrorMessage = "No user backups found.";
|
||||
ErrorMessage = "No database backups found.";
|
||||
return;
|
||||
}
|
||||
|
||||
bool restored;
|
||||
DatabaseBackupResult? restored;
|
||||
|
||||
try
|
||||
{
|
||||
restored = await UserService.RestoreLatestUserBackupAsync();
|
||||
restored = await DatabaseBackupService.RestoreLatestDatabaseBackupAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
ErrorMessage = "Could not restore latest user backup.";
|
||||
ErrorMessage = "Could not restore latest database backup.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (!restored)
|
||||
if (restored is null)
|
||||
{
|
||||
ErrorMessage = "Could not restore latest user backup.";
|
||||
ErrorMessage = "Could not restore latest database backup.";
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await SetupStatusService.MarkSetupCompleteAsync();
|
||||
await UserService.EnsureIndexesAsync();
|
||||
await GuestEntryService.EnsureIndexesAsync();
|
||||
await AccessCodeService.EnsureIndexesAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -173,8 +194,8 @@ public partial class Setup : ComponentBase
|
||||
|
||||
HasAnyUsers = true;
|
||||
IsSetupComplete = true;
|
||||
HasAnyUserBackups = await UserService.HasAnyUserBackupsAsync();
|
||||
HasAnyDatabaseBackups = await DatabaseBackupService.HasAnyDatabaseBackupsAsync();
|
||||
|
||||
ErrorMessage = "Latest user backup has been restored.";
|
||||
ErrorMessage = "Latest database backup has been restored.";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,12 +11,14 @@ public sealed class MongoOptions
|
||||
|
||||
public sealed class MongoDbContext
|
||||
{
|
||||
public IMongoClient Client { get; }
|
||||
public IMongoDatabase Database { get; }
|
||||
public string DatabaseName { get; }
|
||||
|
||||
public MongoDbContext(IOptions<MongoOptions> options)
|
||||
{
|
||||
var client = new MongoClient(options.Value.ConnectionString);
|
||||
Database = client.GetDatabase(options.Value.DatabaseName);
|
||||
Client = new MongoClient(options.Value.ConnectionString);
|
||||
DatabaseName = options.Value.DatabaseName;
|
||||
Database = Client.GetDatabase(DatabaseName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+60
-4
@@ -21,6 +21,7 @@ builder.Services.AddScoped<SetupStatusService>();
|
||||
builder.Services.AddScoped<UserService>();
|
||||
builder.Services.AddScoped<GuestEntryService>();
|
||||
builder.Services.AddScoped<AccessCodeService>();
|
||||
builder.Services.AddScoped<DatabaseBackupService>();
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
|
||||
@@ -233,6 +234,40 @@ app.MapPost("/setup/complete", async (
|
||||
return Results.Redirect("/login");
|
||||
});
|
||||
|
||||
app.MapPost("/setup/restore", async (
|
||||
DatabaseBackupService databaseBackupService,
|
||||
UserService userService,
|
||||
GuestEntryService guestEntryService,
|
||||
AccessCodeService accessCodeService,
|
||||
SetupStatusService setupStatusService) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await userService.HasAnyUsersAsync())
|
||||
{
|
||||
return Results.Redirect("/setup?error=restore-users-exist");
|
||||
}
|
||||
|
||||
var restored = await databaseBackupService.RestoreLatestDatabaseBackupAsync();
|
||||
|
||||
if (restored is null)
|
||||
{
|
||||
return Results.Redirect("/setup?error=restore-failed");
|
||||
}
|
||||
|
||||
await setupStatusService.MarkSetupCompleteAsync();
|
||||
await userService.EnsureIndexesAsync();
|
||||
await guestEntryService.EnsureIndexesAsync();
|
||||
await accessCodeService.EnsureIndexesAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Results.Redirect("/setup?error=restore-failed");
|
||||
}
|
||||
|
||||
return Results.Redirect("/login");
|
||||
});
|
||||
|
||||
app.MapPost("/guests/add", async (
|
||||
HttpContext httpContext,
|
||||
GuestEntryService guestEntryService) =>
|
||||
@@ -406,20 +441,40 @@ app.MapPost("/admin/maintainers/delete", async (
|
||||
}).RequireAuthorization("AdminOnly");
|
||||
|
||||
app.MapPost("/admin/reset-setup", async (
|
||||
HttpContext httpContext,
|
||||
DatabaseBackupService databaseBackupService,
|
||||
UserService userService,
|
||||
GuestEntryService guestEntryService,
|
||||
AccessCodeService accessCodeService,
|
||||
SetupStatusService setupStatusService) =>
|
||||
{
|
||||
DatabaseBackupResult backupResult;
|
||||
|
||||
try
|
||||
{
|
||||
await userService.BackupAndClearUsersAsync("Setup reset from admin panel");
|
||||
backupResult = await databaseBackupService.BackupAndDropActiveDatabaseAsync("Setup reset from admin panel");
|
||||
await setupStatusService.ResetSetupAsync();
|
||||
await userService.EnsureIndexesAsync();
|
||||
await guestEntryService.EnsureIndexesAsync();
|
||||
await accessCodeService.EnsureIndexesAsync();
|
||||
await httpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
httpContext.Session.Clear();
|
||||
}
|
||||
catch
|
||||
catch (InvalidOperationException exception)
|
||||
{
|
||||
return Results.Redirect("/admin?message=Setup could not be reset.");
|
||||
var error = Uri.EscapeDataString(exception.Message);
|
||||
return Results.Redirect($"/admin?message={error}");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var error = Uri.EscapeDataString($"Setup could not be reset: {exception.Message}");
|
||||
return Results.Redirect($"/admin?message={error}");
|
||||
}
|
||||
|
||||
return Results.Redirect("/admin?message=Setup has been reset. Existing users were moved to backup.");
|
||||
var backupDatabase = Uri.EscapeDataString(backupResult.BackupDatabaseName);
|
||||
|
||||
return Results.Redirect(
|
||||
$"/setup?error=setup-reset&backupDatabase={backupDatabase}&backedUpCollections={backupResult.CollectionCount}&backedUpDocuments={backupResult.DocumentCount}");
|
||||
}).RequireAuthorization("AdminOnly");
|
||||
|
||||
app.MapPost("/admin/setup-recovery", async (
|
||||
@@ -480,6 +535,7 @@ app.Use(async (context, next) =>
|
||||
isStaticFile ||
|
||||
path.StartsWithSegments("/setup") ||
|
||||
path.StartsWithSegments("/setup/complete") ||
|
||||
path.StartsWithSegments("/setup/restore") ||
|
||||
path.StartsWithSegments("/auth/logout") ||
|
||||
path.StartsWithSegments("/guest/logout") ||
|
||||
path.StartsWithSegments("/_framework") ||
|
||||
|
||||
@@ -70,7 +70,7 @@ public sealed class UserService(MongoDbContext db)
|
||||
public async Task<bool> HasAnyUsersAsync()
|
||||
=> await _users.CountDocumentsAsync(FilterDefinition<UserDocument>.Empty) > 0;
|
||||
|
||||
public async Task BackupAndClearUsersAsync(string reason)
|
||||
public async Task<int> BackupAndClearUsersAsync(string reason)
|
||||
{
|
||||
var users = await _users
|
||||
.Find(FilterDefinition<UserDocument>.Empty)
|
||||
@@ -92,7 +92,21 @@ public sealed class UserService(MongoDbContext db)
|
||||
await _userBackups.InsertManyAsync(backups);
|
||||
}
|
||||
|
||||
await _users.DeleteManyAsync(FilterDefinition<UserDocument>.Empty);
|
||||
var deleteResult = await _users.DeleteManyAsync(FilterDefinition<UserDocument>.Empty);
|
||||
|
||||
if (!deleteResult.IsAcknowledged)
|
||||
{
|
||||
throw new InvalidOperationException("MongoDB did not acknowledge user deletion.");
|
||||
}
|
||||
|
||||
var remainingUsers = await _users.CountDocumentsAsync(FilterDefinition<UserDocument>.Empty);
|
||||
|
||||
if (remainingUsers > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Users could not be cleared.");
|
||||
}
|
||||
|
||||
return users.Count;
|
||||
}
|
||||
|
||||
public async Task<bool> HasAnyUserBackupsAsync()
|
||||
|
||||
@@ -53,3 +53,5 @@ ASPNETCORE_URLS: http://+:8080
|
||||
```
|
||||
|
||||
MongoDB-Daten liegen im Compose-Volume `mongo_data`.
|
||||
|
||||
Beim Reset im Admin-Bereich kopiert die App die aktuelle Datenbank zuerst in eine neue Backup-Datenbank mit dem Muster `guestlist_backup_<timestamp>`. Danach wird die aktive Datenbank frisch angelegt und das Setup ist wieder offen. Auf der Setup-Seite kann das letzte Datenbank-Backup wiederhergestellt werden.
|
||||
|
||||
Reference in New Issue
Block a user