support snapshots

This commit is contained in:
2026-03-15 15:04:18 -05:00
parent 28b23bc355
commit e7cfff3eca
10 changed files with 199 additions and 12 deletions

View File

@@ -1,10 +1,12 @@
import type {
AdminDiagnosticsDto,
BackupGuidanceDto,
AdminPermissionOptionDto,
AdminRoleDto,
AdminRoleInput,
AdminUserDto,
AdminUserInput,
SupportSnapshotDto,
AuditEventDto,
} from "@mrp/shared";
import fs from "node:fs/promises";
@@ -547,3 +549,76 @@ export async function getAdminDiagnostics(): Promise<AdminDiagnosticsDto> {
recentAuditEvents: recentAuditEvents.map(mapAuditEvent),
};
}
export function getBackupGuidance(): BackupGuidanceDto {
return {
dataPath: paths.dataDir,
databasePath: `${paths.prismaDir}/app.db`,
uploadsPath: paths.uploadsDir,
recommendedBackupTarget: "/mnt/user/backups/mrp-codex",
backupSteps: [
{
id: "stop-app",
label: "Stop writes before copying data",
detail: "Stop the container or application process before copying the data directory so SQLite and attachments stay consistent.",
},
{
id: "copy-data",
label: "Back up the full data directory",
detail: `Copy the full data directory at ${paths.dataDir}, not just the SQLite file, so uploads and attachments are preserved with the database.`,
},
{
id: "retain-metadata",
label: "Keep timestamps and structure",
detail: "Preserve directory structure, filenames, and timestamps during backup so support recovery remains straightforward.",
},
{
id: "record-build",
label: "Record image/version context",
detail: "Capture the deployed image tag or commit alongside the backup so schema and runtime expectations are clear during restore.",
},
],
restoreSteps: [
{
id: "stop-target",
label: "Stop the target app before restore",
detail: "Do not restore into a running instance. Stop the target container or process before replacing the data directory.",
},
{
id: "replace-data",
label: "Restore the full data directory",
detail: `Replace the target data directory with the backed-up copy so ${paths.prismaDir}/app.db and uploads come back together.`,
},
{
id: "start-and-migrate",
label: "Start the app and let migrations run",
detail: "Restart the application after restore and allow the normal startup migration flow to complete before validation.",
},
{
id: "validate-core",
label: "Validate login, files, and PDFs",
detail: "Confirm admin login, attachment access, and PDF generation after restore to verify the operational surface is healthy.",
},
],
};
}
export async function getSupportSnapshot(): Promise<SupportSnapshotDto> {
const diagnostics = await getAdminDiagnostics();
const [users, roles] = await Promise.all([
prisma.user.findMany({
where: { isActive: true },
select: { email: true },
orderBy: [{ email: "asc" }],
}),
prisma.role.count(),
]);
return {
generatedAt: new Date().toISOString(),
diagnostics,
userCount: diagnostics.userCount,
roleCount: roles,
activeUserEmails: users.map((user) => user.email),
};
}