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.
59 lines
1.8 KiB
TypeScript
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,
|
|
}),
|
|
};
|
|
}
|