Files
backerup-website/frontend/lib/links-db.ts
T

79 lines
3.9 KiB
TypeScript

/**
* Runtime-editable link overrides stored in SQLite.
* Falls back to the static LINKS defaults for any key without an override.
*/
import { getDb, ensureReady } from './db'
import { LINKS } from './links'
export type LinkKey = keyof typeof LINKS
/** Human-readable labels for each link key (used in the admin UI). */
export const LINK_META: Record<LinkKey, { label: string; description: string; group: string }> = {
gitea: { label: 'Backerup — Gitea repo', description: 'Repository root used in nav, footer, and CTA buttons', group: 'Backerup' },
giteaReleases: { label: 'Backerup — Releases', description: 'Releases / changelog page', group: 'Backerup' },
giteaIssues: { label: 'Backerup — Issues', description: 'Issue tracker', group: 'Backerup' },
giteaDiscussions: { label: 'Backerup — Discussions', description: 'Gitea Discussions forum', group: 'Backerup' },
giteaContributing: { label: 'Backerup — Contributing', description: 'CONTRIBUTING.md guide', group: 'Backerup' },
docs: { label: 'Backerup — Docs', description: 'Primary documentation URL', group: 'Backerup' },
dockerImage: { label: 'Backerup — Docker image', description: 'Full image reference shown in install snippets', group: 'Backerup' },
uiPort: { label: 'Backerup — UI port', description: 'Default port the Backerup container exposes', group: 'Backerup' },
daterupGitea: { label: 'Daterup — Gitea repo', description: 'Repository root used in Daterup pages', group: 'Daterup' },
daterupDockerImage: { label: 'Daterup — Docker image', description: 'Full image reference shown in Daterup install snippets', group: 'Daterup' },
}
export interface LinkEntry {
key: LinkKey
label: string
description: string
group: string
defaultValue: string
override: string | null
currentValue: string
}
/** Returns merged links: DB overrides take precedence over static defaults. */
export async function getLinks(): Promise<typeof LINKS> {
await ensureReady()
const { rows } = await getDb().execute('SELECT key, value FROM links')
const overrides: Partial<Record<string, string>> = {}
for (const row of rows) {
overrides[row.key as string] = row.value as string
}
return { ...LINKS, ...overrides } as typeof LINKS
}
/** Returns all link entries with defaults, overrides, and current values — for the admin UI. */
export async function getAllLinkEntries(): Promise<LinkEntry[]> {
await ensureReady()
const { rows } = await getDb().execute('SELECT key, value FROM links')
const overrides: Partial<Record<string, string>> = {}
for (const row of rows) {
overrides[row.key as string] = row.value as string
}
return (Object.keys(LINK_META) as LinkKey[]).map((key) => ({
key,
...LINK_META[key],
defaultValue: LINKS[key],
override: overrides[key] ?? null,
currentValue: overrides[key] ?? LINKS[key],
}))
}
/** Upserts a link override. Pass the static default to effectively reset it, or use resetLink(). */
export async function setLink(key: LinkKey, value: string): Promise<void> {
await ensureReady()
await getDb().execute({
sql: `INSERT INTO links (key, value, updated_at)
VALUES (?, ?, datetime('now'))
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,
args: [key, value],
})
}
/** Removes the DB override for a key, restoring the static default. */
export async function resetLink(key: LinkKey): Promise<void> {
await ensureReady()
await getDb().execute({ sql: 'DELETE FROM links WHERE key = ?', args: [key] })
}