Files
cobble/src/functions/backend/database/grab-table-record.ts
T
tben 41bb4aaac2 Let a super admin read the database from the app
A new super admin settings page, /admin/settings/database, lists every
table with its record count and size, and one page of the open table's
records newest key first. Values are cut inside SQL (400 characters, with
a flag saying so) and a record opens whole in a modal, so a table of long
texts reads without shipping it all. A search matches every column's
text, word by word or as a phrase, which is the one thing that also works
on a virtual table. Nothing in it writes: the catalog, the records and
the single record are all reads, GET only.
2026-10-23 06:03:42 +01:00

59 lines
1.8 KiB
TypeScript

import BunSQLite from "@moduletrace/bun-sqlite";
import AppError from "@/src/utils/app-error";
import buildRecordRow from "./build-record-row";
import { buildRecordSql, parseRecordKey } from "./build-records-sql";
import grabTableCatalogEntry from "./grab-table-catalog-entry";
import type { DatabaseRecordPayload } from "@/src/types";
import { AppData } from "@/src/data/app-data";
type Params = {
table?: string | null;
/**
* The record's key, as it came in the query string.
*/
key: string;
};
/**
* One record of one table, in full for its modal: every value read whole
* (up to `AppData.DatabaseBrowserValueMaxChars`, since a megabyte of blob
* or checkpoint is not something to render), so a cell cut short in the
* table can still be read to its end.
*/
export default async function grabTableRecord({ table, key }: Params): Promise<DatabaseRecordPayload> {
const entry = await grabTableCatalogEntry({ table });
if (entry.key.kind == "none") {
throw new AppError(`"${entry.name}" has no key to open a single record by.`);
}
const columns = entry.columns.map((column) => column.name);
const query = buildRecordSql({
table: entry.name,
columns,
key: entry.key,
max_chars: AppData.DatabaseBrowserValueMaxChars,
value: parseRecordKey({ value: key }),
});
const res = await BunSQLite.sql<Record<string, unknown>>(query);
if (!res.success) {
throw new AppError(`Couldn't read that record of "${entry.name}".`);
}
const row = res.payload?.[0];
if (!row) {
throw new AppError(`That record isn't in "${entry.name}" any more.`);
}
return {
table: entry,
record: buildRecordRow({
row,
columns: entry.columns,
max_chars: AppData.DatabaseBrowserValueMaxChars,
}),
};
}