/** * 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 = { 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 { await ensureReady() const { rows } = await getDb().execute('SELECT key, value FROM links') const overrides: Partial> = {} 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 { await ensureReady() const { rows } = await getDb().execute('SELECT key, value FROM links') const overrides: Partial> = {} 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 { 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 { await ensureReady() await getDb().execute({ sql: 'DELETE FROM links WHERE key = ?', args: [key] }) }