public
anord
read
Ledger
Why work hard when you can work easier?
Languages
Repository composition by tracked source files.
TypeScript
86%
CSS
10%
SQL
3%
Shell
1%
HTML
0%
Commit
Fixing loading issue when putting Ledger behind domain
b71e6dd
apps/api/src/db/migrations/003_footer_links.sql | 2 +
apps/api/src/db/seed.ts | 4 +-
apps/api/src/http/routes/settings.ts | 11 ++++-
apps/api/src/services/settings.ts | 59 ++++++++++++++++++++-----
apps/api/src/services/setup.ts | 2 +-
packages/shared/src/contracts.ts | 1 +
6 files changed, 64 insertions(+), 15 deletions(-)
create mode 100644 apps/api/src/db/migrations/003_footer_links.sql
Diff
diff --git a/apps/api/src/db/migrations/003_footer_links.sql b/apps/api/src/db/migrations/003_footer_links.sql
new file mode 100644
index 0000000..f9214d6
--- /dev/null
+++ b/apps/api/src/db/migrations/003_footer_links.sql
@@ -0,0 +1,2 @@
+ALTER TABLE branding_settings
+ADD COLUMN IF NOT EXISTS footer_links JSONB NOT NULL DEFAULT '[]'::jsonb;
diff --git a/apps/api/src/db/seed.ts b/apps/api/src/db/seed.ts
index 5884d13..d505c8c 100644
--- a/apps/api/src/db/seed.ts
+++ b/apps/api/src/db/seed.ts
@@ -23,8 +23,8 @@ async function main() {
await pool.query(
`
- INSERT INTO branding_settings (site_name, logo_url, brand_color, footer_text, public_knowledge_base_enabled)
- SELECT 'Ledger', NULL, '#245cff', 'Built for fast, trusted answers.', true
+ INSERT INTO branding_settings (site_name, logo_url, brand_color, footer_text, public_knowledge_base_enabled, footer_links)
+ SELECT 'Ledger', NULL, '#245cff', 'Powered by Ledger made by ANord.cc', true, '[]'::jsonb
WHERE NOT EXISTS (SELECT 1 FROM branding_settings)
`
);
diff --git a/apps/api/src/http/routes/settings.ts b/apps/api/src/http/routes/settings.ts
index d00f30f..f97e901 100644
--- a/apps/api/src/http/routes/settings.ts
+++ b/apps/api/src/http/routes/settings.ts
@@ -8,7 +8,13 @@ const brandingSchema = z.object({
siteName: z.string().min(2),
logoUrl: z.string().url().nullable(),
brandColor: z.string().regex(/^#[0-9a-fA-F]{6}$/),
- publicKnowledgeBaseEnabled: z.boolean()
+ publicKnowledgeBaseEnabled: z.boolean(),
+ footerLinks: z.array(
+ z.object({
+ label: z.string().min(1),
+ href: z.string().url()
+ })
+ ).default([])
});
export const settingsRouter = Router();
@@ -20,7 +26,8 @@ settingsRouter.get("/public", async (_req, res) => {
siteName: branding.site_name,
logoUrl: branding.logo_url,
brandColor: branding.brand_color,
- publicKnowledgeBaseEnabled: branding.public_knowledge_base_enabled
+ publicKnowledgeBaseEnabled: branding.public_knowledge_base_enabled,
+ footerLinks: branding.footer_links ?? []
}
});
});
diff --git a/apps/api/src/services/settings.ts b/apps/api/src/services/settings.ts
index ef379f4..25b9d1c 100644
--- a/apps/api/src/services/settings.ts
+++ b/apps/api/src/services/settings.ts
@@ -3,18 +3,46 @@ import { pool } from "../db/pool.js";
const FIXED_FOOTER = "Powered by Ledger made by ANord.cc";
+function parseFooterLinks(value: unknown) {
+ if (!Array.isArray(value)) {
+ return [] as Array<{ label: string; href: string }>;
+ }
+
+ return value
+ .map((item) => {
+ if (!item || typeof item !== "object") {
+ return null;
+ }
+
+ const label = "label" in item ? String(item.label ?? "").trim() : "";
+ const href = "href" in item ? String(item.href ?? "").trim() : "";
+ if (!label || !href) {
+ return null;
+ }
+
+ return { label, href };
+ })
+ .filter(Boolean) as Array<{ label: string; href: string }>;
+}
+
export async function getBrandingSettings() {
const result = await pool.query(`SELECT * FROM branding_settings ORDER BY created_at ASC LIMIT 1`);
- return (
+ const row =
result.rows[0] ?? {
id: "default",
site_name: "Ledger",
logo_url: null,
brand_color: "#245cff",
footer_text: FIXED_FOOTER,
+ footer_links: [],
public_knowledge_base_enabled: true
- }
- );
+ };
+
+ return {
+ ...row,
+ footer_text: FIXED_FOOTER,
+ footer_links: parseFooterLinks(row.footer_links)
+ };
}
export async function upsertBrandingSettings(input: {
@@ -22,6 +50,7 @@ export async function upsertBrandingSettings(input: {
logoUrl: string | null;
brandColor: string;
publicKnowledgeBaseEnabled: boolean;
+ footerLinks: Array<{ label: string; href: string }>;
}) {
const existing = await getBrandingSettings();
if (existing) {
@@ -29,7 +58,7 @@ export async function upsertBrandingSettings(input: {
`
UPDATE branding_settings
SET site_name = $2, logo_url = $3, brand_color = $4, footer_text = $5,
- public_knowledge_base_enabled = $6, updated_at = now()
+ public_knowledge_base_enabled = $6, footer_links = $7, updated_at = now()
WHERE id = $1
RETURNING *
`,
@@ -39,16 +68,21 @@ export async function upsertBrandingSettings(input: {
input.logoUrl,
input.brandColor,
FIXED_FOOTER,
- input.publicKnowledgeBaseEnabled
+ input.publicKnowledgeBaseEnabled,
+ JSON.stringify(input.footerLinks)
]
);
- return result.rows[0];
+ return {
+ ...result.rows[0],
+ footer_text: FIXED_FOOTER,
+ footer_links: parseFooterLinks(result.rows[0].footer_links)
+ };
}
const created = await pool.query(
`
- INSERT INTO branding_settings (site_name, logo_url, brand_color, footer_text, public_knowledge_base_enabled)
- VALUES ($1, $2, $3, $4, $5)
+ INSERT INTO branding_settings (site_name, logo_url, brand_color, footer_text, public_knowledge_base_enabled, footer_links)
+ VALUES ($1, $2, $3, $4, $5, $6)
RETURNING *
`,
[
@@ -56,10 +90,15 @@ export async function upsertBrandingSettings(input: {
input.logoUrl,
input.brandColor,
FIXED_FOOTER,
- input.publicKnowledgeBaseEnabled
+ input.publicKnowledgeBaseEnabled,
+ JSON.stringify(input.footerLinks)
]
);
- return created.rows[0];
+ return {
+ ...created.rows[0],
+ footer_text: FIXED_FOOTER,
+ footer_links: parseFooterLinks(created.rows[0].footer_links)
+ };
}
export async function getAdminSettingsBundle() {
diff --git a/apps/api/src/services/setup.ts b/apps/api/src/services/setup.ts
index 39bbacd..4d66db1 100644
--- a/apps/api/src/services/setup.ts
+++ b/apps/api/src/services/setup.ts
@@ -48,7 +48,7 @@ export async function initializeLedger(input: SetupInput) {
`
UPDATE branding_settings
SET site_name = $1, brand_color = $2, footer_text = $3,
- public_knowledge_base_enabled = $4, updated_at = now()
+ public_knowledge_base_enabled = $4, footer_links = '[]'::jsonb, updated_at = now()
WHERE id = (SELECT id FROM branding_settings ORDER BY created_at ASC LIMIT 1)
RETURNING id
`,
diff --git a/packages/shared/src/contracts.ts b/packages/shared/src/contracts.ts
index f47c04e..5b755a0 100644
--- a/packages/shared/src/contracts.ts
+++ b/packages/shared/src/contracts.ts
@@ -70,6 +70,7 @@ export interface BrandingSettings {
logoUrl: string | null;
brandColor: string;
publicKnowledgeBaseEnabled: boolean;
+ footerLinks: Array<{ label: string; href: string }>;
}
export interface SmtpSettings {