wip: ops admin panel + migration sync + crud/rbac phase-5 snapshot
Working-tree checkpoint of in-progress work carried across prior sessions on the feat/crud-rbac branch, committed so it lands on the remote alongside the CI changes. - Operaciones admin panel: apps/api/src/ops (ingest upload, backup / restore / re-import jobs) wired into app.module + RBAC abilities, and the apps/web/src/app/operaciones page. docker-compose gets INGEST_DIR / BACKUP_DIR volumes; .gitignore excludes migration/ingest + backups. - migration/sync.py plus transform_*.py / run_all / config / dbenv / blob_extract adjustments for the additive sync path. - crud/rbac phase-5 web bits: AppShell, api/labels/types libs, globals. - schema.prisma + PLAN/RESUME doc updates. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,10 @@ import type {
|
||||
PolicyStats,
|
||||
PolicyStatus,
|
||||
LookupsResponse,
|
||||
OpsJob,
|
||||
OpsJobKind,
|
||||
IngestFile,
|
||||
BackupFile,
|
||||
PropertyDetail,
|
||||
PropertyFacets,
|
||||
PropertyInput,
|
||||
@@ -593,3 +597,62 @@ export function resetUserPassword(id: string, password: string): Promise<UserRow
|
||||
body: JSON.stringify({ password }),
|
||||
});
|
||||
}
|
||||
|
||||
/* ------------------------------------------- DB operations (admin only) */
|
||||
|
||||
export function listIngest(): Promise<IngestFile[]> {
|
||||
return apiFetch<IngestFile[]>("/ops/ingest");
|
||||
}
|
||||
|
||||
/** Multipart upload — not JSON, so it bypasses apiFetch's Content-Type. */
|
||||
export async function uploadIngest(name: string, file: File): Promise<void> {
|
||||
const body = new FormData();
|
||||
body.append("file", file, name);
|
||||
const res = await fetch(`${API_ORIGIN}/ops/ingest/${encodeURIComponent(name)}`, {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
body,
|
||||
});
|
||||
if (!res.ok) {
|
||||
let message = `Error ${res.status}`;
|
||||
try {
|
||||
const b = await res.json();
|
||||
if (b?.message) message = b.message;
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
throw new ApiError(res.status, message);
|
||||
}
|
||||
}
|
||||
|
||||
export function deleteIngest(name: string): Promise<unknown> {
|
||||
return apiFetch(`/ops/ingest/${encodeURIComponent(name)}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function listBackups(): Promise<BackupFile[]> {
|
||||
return apiFetch<BackupFile[]>("/ops/backups");
|
||||
}
|
||||
|
||||
export function backupDownloadUrl(name: string): string {
|
||||
return `${API_ORIGIN}/ops/backups/${encodeURIComponent(name)}/download`;
|
||||
}
|
||||
|
||||
export function deleteBackup(name: string): Promise<unknown> {
|
||||
return apiFetch(`/ops/backups/${encodeURIComponent(name)}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function listOpsJobs(): Promise<OpsJob[]> {
|
||||
return apiFetch<OpsJob[]>("/ops/jobs");
|
||||
}
|
||||
|
||||
export function getOpsJob(id: string): Promise<OpsJob> {
|
||||
return apiFetch<OpsJob>(`/ops/jobs/${id}`);
|
||||
}
|
||||
|
||||
/** Start a mutating op. `file` is required for RESTORE. 409 if one is running. */
|
||||
export function startOpsJob(kind: OpsJobKind, file?: string): Promise<OpsJob> {
|
||||
return apiFetch<OpsJob>("/ops/jobs", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ kind, file }),
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user