101 lines
4.5 KiB
TypeScript
101 lines
4.5 KiB
TypeScript
import type { FastifyInstance } from "fastify";
|
|
import type { ThtRunner } from "../tht/tht-runner.js";
|
|
import type { PrincipalContext } from "../auth/principal.js";
|
|
import type { Settings } from "../settings/settings-store.js";
|
|
import type { WorkspaceRegistry } from "../workspaces/registry.js";
|
|
import { hasPermission, isPrincipalContext, requirePermission } from "../auth/authorization.js";
|
|
|
|
export function sqlRoutes(app: FastifyInstance, deps: {
|
|
tht: ThtRunner; getSettings: (principal: PrincipalContext) => Promise<Settings>;
|
|
workspaceRegistry: WorkspaceRegistry;
|
|
}): void {
|
|
const runnerFor = (principal: PrincipalContext): any => {
|
|
const runner = deps.tht as any;
|
|
return typeof runner.withPrincipal === "function" ? runner.withPrincipal(principal) : runner;
|
|
};
|
|
const readPrincipal = (principal: PrincipalContext): PrincipalContext => ({
|
|
...principal,
|
|
// The harness still consumes this compatibility bit; it must never exceed session.read_all.
|
|
isAdmin: hasPermission(principal, "session.read_all"),
|
|
});
|
|
const isNotFound = (error: unknown) => /not found|non trovata|inesistente|404/i.test(
|
|
error instanceof Error ? error.message : String(error),
|
|
);
|
|
const locate = async (principal: PrincipalContext, id: string, legacyWorkspace?: string) => {
|
|
const runner = runnerFor(readPrincipal(principal));
|
|
if (typeof runner.sessionShow !== "function") return { manifest: {}, workspace: legacyWorkspace };
|
|
const registry = deps.workspaceRegistry as Partial<WorkspaceRegistry>;
|
|
const revisions = typeof registry.listRetainedSnapshots === "function"
|
|
? await registry.listRetainedSnapshots.call(deps.workspaceRegistry)
|
|
: await deps.workspaceRegistry.list();
|
|
for (const revision of revisions) {
|
|
try {
|
|
const manifest = await runner.sessionShow(id, revision.snapshotPath);
|
|
if (!manifest) continue;
|
|
const saved = manifest as { workspace_id?: string; workspace_revision?: string };
|
|
if (saved.workspace_id && saved.workspace_revision) {
|
|
const pinned = await deps.workspaceRegistry.readPinned(saved.workspace_id, saved.workspace_revision);
|
|
return {
|
|
manifest,
|
|
workspace: pinned.workspaceConfigPath ?? (pinned as any).revision?.snapshotPath,
|
|
};
|
|
}
|
|
return { manifest, workspace: revision.snapshotPath };
|
|
} catch (error) {
|
|
if (!isNotFound(error)) throw error;
|
|
}
|
|
}
|
|
try {
|
|
const manifest = await runner.sessionShow(id, legacyWorkspace);
|
|
return manifest ? { manifest, workspace: legacyWorkspace } : undefined;
|
|
} catch (error) {
|
|
if (isNotFound(error)) return undefined;
|
|
throw error;
|
|
}
|
|
};
|
|
app.post("/sessions/:id/sql/preview", async (req, reply) => {
|
|
const id = (req.params as any).id as string;
|
|
const { limit, offset } = (req.body as any) ?? {};
|
|
let principal: PrincipalContext;
|
|
let workspace: string | undefined;
|
|
try {
|
|
const authorized = requirePermission(req, reply, "session.use");
|
|
if (!isPrincipalContext(authorized)) return authorized;
|
|
principal = authorized;
|
|
const settings = await deps.getSettings(principal);
|
|
const located = await locate(principal, id, settings.workspace);
|
|
if (!located) return reply.code(404).send({ error: "session not found" });
|
|
workspace = located.workspace;
|
|
} catch {
|
|
return reply.code(503).send({ error: "session storage is unavailable" });
|
|
}
|
|
try {
|
|
return await runnerFor(readPrincipal(principal)).sqlPreview(id, { limit, offset }, workspace);
|
|
} catch (error: any) {
|
|
return reply.code(500).send({ error: error.message ?? String(error) });
|
|
}
|
|
});
|
|
|
|
app.post("/sessions/:id/sql/export", async (req, reply) => {
|
|
const id = (req.params as any).id as string;
|
|
let principal: PrincipalContext;
|
|
let workspace: string | undefined;
|
|
try {
|
|
const authorized = requirePermission(req, reply, "session.use");
|
|
if (!isPrincipalContext(authorized)) return authorized;
|
|
principal = authorized;
|
|
const settings = await deps.getSettings(principal);
|
|
const located = await locate(principal, id, settings.workspace);
|
|
if (!located) return reply.code(404).send({ error: "session not found" });
|
|
workspace = located.workspace;
|
|
} catch {
|
|
return reply.code(503).send({ error: "session storage is unavailable" });
|
|
}
|
|
try {
|
|
return await runnerFor(readPrincipal(principal)).sqlExport(id, workspace);
|
|
} catch (error: any) {
|
|
return reply.code(500).send({ error: error.message ?? String(error) });
|
|
}
|
|
});
|
|
}
|