Files
ThothII/backend/src/routes/sql.ts
T

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) });
}
});
}