Skip to content

Commit c9193e5

Browse files
committed
Fix Admin Operations export and backup action feedback - PR_26168_245-admin-operations-action-feedback-fix & Add server-side Postgres backup foundation for Admin Operations - PR_26168_245-postgres-backup-foundation
1 parent 287167e commit c9193e5

10 files changed

Lines changed: 8702 additions & 6762 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ GAMEFOUNDRY_SUPABASE_ANON_KEY=
1818
# Do not expose these values to browser JavaScript, HTML, reports, or screenshots.
1919
GAMEFOUNDRY_SUPABASE_SERVICE_ROLE_KEY=
2020
GAMEFOUNDRY_DATABASE_URL=
21+
GAMEFOUNDRY_DB_BACKUP_DIR=
2122

2223
# Server-only project asset storage configuration.
2324
# Browser uploads must go through the server API and must not receive these secrets.

assets/theme-v2/js/admin-operations.js

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,64 @@ class AdminOperationsController {
146146
this.resultRows.prepend(row);
147147
}
148148

149+
artifactForResult(result = {}) {
150+
if (result.actionId === "export-project-package") {
151+
return {
152+
bytesBase64: result.package?.packageBytesBase64,
153+
fileName: result.package?.filename,
154+
missingMessage: "Export Project Package completed without downloadable .gfsp package bytes. Restore the Local API package response before retrying.",
155+
mimeType: "application/octet-stream",
156+
};
157+
}
158+
return null;
159+
}
160+
161+
decodeBase64(base64Value) {
162+
const binary = atob(base64Value);
163+
const bytes = new Uint8Array(binary.length);
164+
for (let index = 0; index < binary.length; index += 1) {
165+
bytes[index] = binary.charCodeAt(index);
166+
}
167+
return bytes;
168+
}
169+
170+
downloadArtifact(artifact) {
171+
if (!artifact?.fileName || !artifact?.bytesBase64) {
172+
throw new Error(artifact?.missingMessage || "Downloadable artifact bytes were not returned.");
173+
}
174+
const blob = new Blob([this.decodeBase64(artifact.bytesBase64)], { type: artifact.mimeType || "application/octet-stream" });
175+
const url = URL.createObjectURL(blob);
176+
const link = document.createElement("a");
177+
link.download = artifact.fileName;
178+
link.href = url;
179+
link.rel = "noopener";
180+
document.body.append(link);
181+
link.click();
182+
link.remove();
183+
window.setTimeout(() => URL.revokeObjectURL(url), 0);
184+
}
185+
186+
applyArtifactFeedback(result = {}) {
187+
const artifact = this.artifactForResult(result);
188+
if (!artifact || result.status !== "PASS" || result.executed !== true) {
189+
return result;
190+
}
191+
try {
192+
this.downloadArtifact(artifact);
193+
return {
194+
...result,
195+
message: `${result.message || "Admin operation completed."} Download started: ${artifact.fileName}.`,
196+
};
197+
} catch (error) {
198+
return {
199+
...result,
200+
executed: false,
201+
message: error instanceof Error ? error.message : "Artifact download failed before it could start.",
202+
status: "FAIL",
203+
};
204+
}
205+
}
206+
149207
async readFileAsBase64(file) {
150208
const bytes = new Uint8Array(await file.arrayBuffer());
151209
const chunkSize = 32768;
@@ -210,11 +268,19 @@ class AdminOperationsController {
210268
try {
211269
this.setStatus("SKIP", `Running ${action.label || action.id || "Admin Operation"} through the Local API.`);
212270
const options = await this.collectActionOptions(action);
213-
const result = runAdminOperationAction(action.id, options);
271+
const result = this.applyArtifactFeedback(runAdminOperationAction(action.id, options));
214272
this.appendResult(result);
215273
this.setStatus(result.status || "SKIP", result.message || "Admin operation returned no message.");
216274
} catch (error) {
217-
this.setStatus("FAIL", error instanceof Error ? error.message : "Admin operation failed.");
275+
const result = {
276+
actionId: action.id,
277+
actionLabel: action.label,
278+
executed: false,
279+
message: error instanceof Error ? error.message : "Admin operation failed.",
280+
status: "FAIL",
281+
};
282+
this.appendResult(result);
283+
this.setStatus(result.status, result.message);
218284
}
219285
}
220286
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# PR_26168_245-postgres-backup-foundation
2+
3+
## Branch Validation
4+
- PASS: Current branch was `main`.
5+
- Expected branch: `main`.
6+
- Local branches found: `main`.
7+
8+
## Summary
9+
- Create Backup now routes through Admin Operations to the Local API and runs a server-side Postgres backup foundation.
10+
- The Local API validates the configured database connection with a lightweight Postgres query before attempting backup.
11+
- Backup execution uses `pg_dump --format=custom` and writes to `GAMEFOUNDRY_DB_BACKUP_DIR` on the server side.
12+
- Backup filenames use `gamefoundry-<environment>-db-<YYJJJ>-<sequence>.dump`.
13+
- Browser backup downloads/storage were removed from Create Backup; the UI shows the Local API PASS/FAIL result only.
14+
- Restore From Backup is guarded scaffold-only and does not apply browser-uploaded backup data.
15+
16+
## Requirement Checklist
17+
- PASS: Hard stop guard passed on `main`.
18+
- PASS: Admin Operations Create Backup button calls the Local API action route.
19+
- PASS: Local API Create Backup validates DB connection before `pg_dump`.
20+
- PASS: Local API backup service runs `pg_dump --format=custom` for backups.
21+
- PASS: Backup filename format implemented as `gamefoundry-<environment>-db-<YYJJJ>-<sequence>.dump`.
22+
- PASS: Backup output is directed to `GAMEFOUNDRY_DB_BACKUP_DIR`; repo `tmp/` backup targets are rejected.
23+
- PASS: `.env.example` includes `GAMEFOUNDRY_DB_BACKUP_DIR=`.
24+
- PASS: `.env.dev`, `.env.ist`, `.env.uat`, and `.env.prd` were updated locally with `GAMEFOUNDRY_DB_BACKUP_DIR=`; they are gitignored and contain private values, so they are not included in review artifacts or ZIP output.
25+
- PASS: No real backup files or `.dump` files were created in the repo.
26+
- PASS: Missing `GAMEFOUNDRY_DB_BACKUP_DIR` returns visible FAIL diagnostics.
27+
- PASS: Missing `pg_dump` returns visible FAIL diagnostics.
28+
- PASS: Successful backup reports PASS with filename, size, and timestamp.
29+
- PASS: DB passwords and secrets are not exposed in UI results, reports, command args, or ZIP artifacts.
30+
- PASS: Restore From Backup remains guarded scaffold-only.
31+
- PASS: Export Project Package still shows visible feedback and fails visibly if package bytes are missing.
32+
- PASS: No inline scripts, inline styles, style blocks, script blocks, or inline event handlers were added.
33+
- PASS: No unrelated import, migration, reseed, or Project Workspace behavior was added.
34+
35+
## Validation Lane Report
36+
- PASS: `node --check src/dev-runtime/database/postgres-backup-service.mjs`
37+
- PASS: `node --check src/dev-runtime/server/local-api-router.mjs`
38+
- PASS: `node --check assets/theme-v2/js/admin-operations.js`
39+
- PASS: `node --check tests/dev-runtime/PostgresBackupService.test.mjs`
40+
- PASS: `node --check tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs`
41+
- PASS: `rg --pcre2 -n "<script(?![^>]+src=)|<style|\s(onclick|onchange|oninput|onsubmit|onkeydown|onkeyup|onload)=" admin/operations.html` returned no matches.
42+
- PASS: `node --test tests/dev-runtime/PostgresBackupService.test.mjs`
43+
- 5 passed.
44+
- Covered filename format, missing backup dir, unavailable `pg_dump`, repo `tmp/` rejection, and successful custom-format dump command construction.
45+
- PASS: `npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs --grep "Admin Operations" --reporter=line`
46+
- 4 passed.
47+
- Covered Create Backup visible PASS/FAIL behavior, Export Project Package visible feedback, no silent no-op behavior, no secret exposure, Admin Operations section order, DEV reseed gating, and non-admin access blocking.
48+
- PASS: Playwright V8 coverage report generated at `docs_build/dev/reports/playwright_v8_coverage_report.txt`.
49+
50+
## Manual Validation Notes
51+
- Create Backup success path now reports the server-side `.dump` filename, byte size, timestamp, and current environment.
52+
- Create Backup failure paths are visible and actionable for missing backup directory, disallowed repo `tmp/` target, unavailable `pg_dump`, invalid database config, failed database connection validation, and failed `pg_dump`.
53+
- The service passes the DB password to `pg_dump` through `PGPASSWORD`; it is not included in command arguments or browser-visible results.
54+
- Actual live backup creation requires a real `.env` with `GAMEFOUNDRY_DB_BACKUP_DIR`, a reachable Postgres database, and PostgreSQL client tools installed on the Local API host.
55+
56+
## Full Samples Decision
57+
- SKIP: Full samples smoke was not run because this PR only changes Admin Operations backup feedback, Local API backup service behavior, env placeholders, and targeted tests. No sample JSON or sample runtime surface was touched.
Lines changed: 15 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,26 @@
11
# git status --short
2-
M admin/infrastructure.html
3-
M admin/operations.html
4-
M assets/theme-v2/js/admin-infrastructure.js
2+
M .env.example
53
M assets/theme-v2/js/admin-operations.js
6-
M assets/theme-v2/js/gamefoundry-partials.js
7-
M docs_build/codex/decisions/project-packages.md
84
M docs_build/dev/reports/codex_changed_files.txt
95
M docs_build/dev/reports/codex_review.diff
106
M docs_build/dev/reports/playwright_v8_coverage_report.txt
117
M src/dev-runtime/server/local-api-router.mjs
12-
M src/engine/api/admin-operations-api-client.js
138
M tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs
14-
M tests/playwright/tools/GameWorkspaceMockRepository.spec.mjs
15-
M toolbox/game-workspace/game-workspace.js
16-
M toolbox/game-workspace/index.html
17-
?? docs_build/dev/reports/PR_26168_237-admin-ia-cleanup.md
18-
?? docs_build/dev/reports/PR_26168_238-r2-assets-end-to-end-validation.md
19-
?? docs_build/dev/reports/PR_26168_239-gfsp-package-foundation.md
20-
?? docs_build/dev/reports/PR_26168_240-project-package-export.md
21-
?? docs_build/dev/reports/PR_26168_241-project-package-validation.md
22-
?? docs_build/dev/reports/PR_26168_242-project-package-import.md
23-
?? docs_build/dev/reports/PR_26168_243-backup-recovery-foundation.md
24-
?? docs_build/dev/reports/PR_26168_244-project-workspace-real-project-flow.md
25-
?? src/dev-runtime/project-packages/
9+
?? docs_build/dev/reports/PR_26168_245-postgres-backup-foundation.md
10+
?? src/dev-runtime/database/
11+
?? tests/dev-runtime/PostgresBackupService.test.mjs
2612

2713
# git ls-files --others --exclude-standard
28-
docs_build/dev/reports/PR_26168_237-admin-ia-cleanup.md
29-
docs_build/dev/reports/PR_26168_238-r2-assets-end-to-end-validation.md
30-
docs_build/dev/reports/PR_26168_239-gfsp-package-foundation.md
31-
docs_build/dev/reports/PR_26168_240-project-package-export.md
32-
docs_build/dev/reports/PR_26168_241-project-package-validation.md
33-
docs_build/dev/reports/PR_26168_242-project-package-import.md
34-
docs_build/dev/reports/PR_26168_243-backup-recovery-foundation.md
35-
docs_build/dev/reports/PR_26168_244-project-workspace-real-project-flow.md
36-
src/dev-runtime/project-packages/project-package-service.mjs
14+
docs_build/dev/reports/PR_26168_245-postgres-backup-foundation.md
15+
src/dev-runtime/database/postgres-backup-service.mjs
16+
tests/dev-runtime/PostgresBackupService.test.mjs
3717

3818
# git diff --stat
39-
admin/infrastructure.html | 28 +-
40-
admin/operations.html | 8 +-
41-
assets/theme-v2/js/admin-infrastructure.js | 42 +-
42-
assets/theme-v2/js/admin-operations.js | 107 +-
43-
assets/theme-v2/js/gamefoundry-partials.js | 3 -
44-
docs_build/codex/decisions/project-packages.md | 24 +
45-
docs_build/dev/reports/codex_changed_files.txt | 125 +-
46-
docs_build/dev/reports/codex_review.diff | 4579 +++++++++++---------
47-
.../dev/reports/playwright_v8_coverage_report.txt | 37 +-
48-
src/dev-runtime/server/local-api-router.mjs | 361 +-
49-
src/engine/api/admin-operations-api-client.js | 4 +-
50-
.../tools/AdminPlatformToolsWireframes.spec.mjs | 155 +-
51-
.../tools/GameWorkspaceMockRepository.spec.mjs | 15 +-
52-
toolbox/game-workspace/game-workspace.js | 11 +-
53-
toolbox/game-workspace/index.html | 10 +-
54-
15 files changed, 3314 insertions(+), 2195 deletions(-)
19+
.env.example | 1 +
20+
assets/theme-v2/js/admin-operations.js | 70 +-
21+
docs_build/dev/reports/codex_changed_files.txt | 54 +-
22+
docs_build/dev/reports/codex_review.diff | 7098 +-------------------
23+
.../dev/reports/playwright_v8_coverage_report.txt | 48 +-
24+
src/dev-runtime/server/local-api-router.mjs | 126 +-
25+
.../tools/AdminPlatformToolsWireframes.spec.mjs | 156 +-
26+
7 files changed, 606 insertions(+), 6947 deletions(-)

0 commit comments

Comments
 (0)