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.
This commit is contained in:
2026-10-23 06:03:42 +01:00
parent 9b26c169d6
commit 41bb4aaac2
48 changed files with 2573 additions and 0 deletions
@@ -0,0 +1,337 @@
import { Database } from "bun:sqlite";
import { beforeAll, describe, expect, test } from "bun:test";
import buildRecordsSql, {
buildCountSql,
buildRecordSql,
buildRecordSelect,
metaColumnAlias,
parseRecordKey,
RecordKeyAlias,
} from "./build-records-sql";
import buildSearchClause, { searchWords } from "./build-search-clause";
import buildRecordRow from "./build-record-row";
import buildTableCatalogSql from "./build-table-catalog-sql";
import groupTableCatalog from "./group-table-catalog";
import resolveTableKey from "./resolve-table-key";
import quoteSqlIdentifier from "./quote-sql-identifier";
import type { TableCatalogRow } from "./table-catalog-types";
import type { DatabaseTableColumn } from "@/src/types";
const db = new Database(":memory:");
const messages_columns: DatabaseTableColumn[] = [
{ name: "id", type: "INTEGER", pk: true },
{ name: "role", type: "TEXT", pk: false },
{ name: "text", type: "TEXT", pk: false },
];
beforeAll(() => {
db.run(`CREATE TABLE "messages" ("id" INTEGER PRIMARY KEY, "role" TEXT, "text" TEXT)`);
db.run(`INSERT INTO "messages" VALUES
(1, 'user', 'hello there'),
(2, 'bot', 'Hello there, how can I help?'),
(3, 'user', 'a "quoted" table name and a long body'),
(4, 'bot', NULL)`);
db.run(`CREATE TABLE "no_key" ("a" TEXT, "b" TEXT)`);
db.run(`INSERT INTO "no_key" VALUES ('one', 'two')`);
});
function rows({ sql, values, max_chars = 400 }: { sql: string; values: string[]; max_chars?: number }) {
return db
.query(sql)
.all(...values)
.map((row) =>
buildRecordRow({ row: row as Record<string, unknown>, columns: messages_columns, max_chars }),
);
}
describe("quoteSqlIdentifier", () => {
test("wraps a plain name", () => {
expect(quoteSqlIdentifier({ name: "bots" })).toBe(`"bots"`);
});
test("doubles a quote instead of letting it out", () => {
expect(quoteSqlIdentifier({ name: `a"b` })).toBe(`"a""b"`);
});
});
describe("resolveTableKey", () => {
test("a single primary key column names the records", () => {
expect(resolveTableKey({ columns: messages_columns, has_rowid: true })).toEqual({ kind: "column", name: "id" });
});
test("no primary key falls back to the rowid", () => {
expect(resolveTableKey({ columns: [{ name: "a", type: "TEXT", pk: false }], has_rowid: true })).toEqual({
kind: "rowid",
});
});
test("a virtual table with neither has no key", () => {
expect(resolveTableKey({ columns: [{ name: "a", type: "TEXT", pk: false }], has_rowid: false })).toEqual({
kind: "none",
});
});
test("a composite primary key falls back to the rowid", () => {
expect(
resolveTableKey({
columns: [
{ name: "a", type: "TEXT", pk: true },
{ name: "b", type: "TEXT", pk: true },
],
has_rowid: true,
}),
).toEqual({ kind: "rowid" });
});
});
describe("buildRecordsSql", () => {
test("returns a page of records newest key first", () => {
const query = buildRecordsSql({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 2,
offset: 0,
search: null,
});
const records = rows(query);
expect(records.map((record) => record.key)).toEqual([4, 3]);
expect(records[0]?.cells["text"]?.kind).toBe("null");
expect(records[1]?.cells["role"]?.text).toBe("user");
});
test("pages with the offset it is given", () => {
const query = buildRecordsSql({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 2,
offset: 2,
search: null,
});
expect(rows(query).map((record) => record.key)).toEqual([2, 1]);
});
test("cuts a long value and says so", () => {
const query = buildRecordsSql({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 4,
limit: 1,
offset: 1,
search: null,
});
const [record] = rows({ ...query, max_chars: 4 });
expect(record?.cells["text"]?.text).toBe(`a "q`); // four characters, cut
expect(record?.cells["text"]?.truncated).toBe(true);
});
test("orders by the rowid when the table has no primary key", () => {
const query = buildRecordsSql({
table: "no_key",
columns: ["a", "b"],
key: { kind: "rowid" },
max_chars: 400,
limit: 10,
offset: 0,
search: null,
});
const record = db.query(query.sql).get(...query.values) as Record<string, unknown>;
expect(Number(record[RecordKeyAlias])).toBe(1);
});
test("a table with no key comes back without one", () => {
const query = buildRecordsSql({
table: "no_key",
columns: ["a", "b"],
key: { kind: "none" },
max_chars: 400,
limit: 10,
offset: 0,
search: null,
});
const record = db.query(query.sql).get(...query.values) as Record<string, unknown>;
expect(query.sql).not.toContain(`ORDER BY`);
expect(record[RecordKeyAlias]).toBeUndefined();
});
test("selects a meta column per column, so a cut value can be known", () => {
const select = buildRecordSelect({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 400,
});
expect(select.sql).toContain(quoteSqlIdentifier({ name: metaColumnAlias({ name: "text", kind: "length" }) }));
expect(select.sql).toContain(quoteSqlIdentifier({ name: metaColumnAlias({ name: "text", kind: "type" }) }));
});
});
describe("search", () => {
const columns = messages_columns.map((column) => column.name);
const every_word = { text: "hello there", mode: "every_word" as const, max_words: 8 };
test("every word matches anywhere in the record, in any order", () => {
const query = buildRecordsSql({
table: "messages",
columns,
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 10,
offset: 0,
search: every_word,
});
expect(rows(query).map((record) => record.key).sort()).toEqual([1, 2]);
});
test("a phrase only matches where the words sit together", () => {
const query = buildRecordsSql({
table: "messages",
columns,
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 10,
offset: 0,
search: { text: "hello there", mode: "phrase", max_words: 8 },
});
expect(rows(query).map((record) => record.key).sort()).toEqual([1, 2]);
});
test("a phrase of one word that sits apart doesn't match", () => {
const query = buildRecordsSql({
table: "messages",
columns,
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 10,
offset: 0,
search: { text: "help hello", mode: "phrase", max_words: 8 },
});
expect(rows(query)).toEqual([]);
});
test("prefixes find a value, wherever it is in a word", () => {
const query = buildRecordsSql({
table: "messages",
columns,
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 10,
offset: 0,
search: { text: "quoted", mode: "every_word", max_words: 8 },
});
expect(rows(query).map((record) => record.key)).toEqual([3]);
});
test("matches an id as text", () => {
const query = buildRecordsSql({
table: "messages",
columns,
key: { kind: "column", name: "id" },
max_chars: 400,
limit: 10,
offset: 0,
search: { text: "3", mode: "every_word", max_words: 8 },
});
expect(rows(query).map((record) => record.key)).toEqual([3]);
});
test("counts only what matched", () => {
const count = buildCountSql({ table: "messages", columns, search: every_word });
const result = db.query(count.sql).get(...count.values) as { total: number };
expect(result.total).toBe(2);
});
test("counts the whole table with no search", () => {
const count = buildCountSql({ table: "messages", columns, search: null });
const result = db.query(count.sql).get(...count.values) as { total: number };
expect(result.total).toBe(4);
});
test("an empty search matches everything rather than failing", () => {
const clause = buildSearchClause({ columns, search: " ", mode: "every_word", max_words: 8 });
expect(clause).toEqual({ sql: "1", values: [] });
});
test("keeps a search to its words", () => {
expect(searchWords({ search: " a b c ", max_words: 2 })).toEqual(["a", "b"]);
});
test("a table with no columns matches nothing", () => {
const clause = buildSearchClause({ columns: [], search: "x", mode: "every_word", max_words: 8 });
expect(clause.values).toEqual(["x"]);
expect(clause.sql).toContain(`''`);
});
});
describe("buildRecordSql", () => {
test("reads one record by its key", () => {
const query = buildRecordSql({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 400,
value: 2,
});
const [record] = rows({ sql: query.sql, values: query.values as string[] });
expect(record?.key).toBe(2);
expect(record?.cells["text"]?.text).toBe("Hello there, how can I help?");
});
test("finds nothing for a key that isn't there", () => {
const query = buildRecordSql({
table: "messages",
columns: messages_columns.map((column) => column.name),
key: { kind: "column", name: "id" },
max_chars: 400,
value: 99,
});
expect(db.query(query.sql).all(...(query.values as string[]))).toEqual([]);
});
});
describe("parseRecordKey", () => {
test("an integer key comes back as a number", () => {
expect(parseRecordKey({ value: "42" })).toBe(42);
});
test("a text key stays text", () => {
expect(parseRecordKey({ value: "abc" })).toBe("abc");
});
test("a number too big to be safe stays text", () => {
expect(parseRecordKey({ value: "99999999999999999999" })).toBe("99999999999999999999");
});
});
describe("buildTableCatalogSql", () => {
test("reads every table and its columns, without SQLite's own tables", () => {
const res = db.query(buildTableCatalogSql()).all() as TableCatalogRow[];
const tables = groupTableCatalog({ rows: res });
expect(tables.map((table) => table.name)).toEqual(["messages", "no_key"]);
expect(tables[0]?.columns.map((column) => column.name)).toEqual(["id", "role", "text"]);
expect(tables[0]?.has_rowid).toBe(true);
});
});