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%
Trace
apps/api/src/services/setup.ts
Trace helps you understand code history line by line. See who changed each line, when it changed, and which commit introduced it.
Author
Date
Commit
Line
Code
1
import { hashPassword, createSessionToken, getUserForSession } from "./auth.js";
2
import { logAudit } from "./audit.js";
3
import { pool } from "../db/pool.js";
5
const FIXED_FOOTER = "Powered by Ledger made by ANord.cc";
7
interface SetupInput {
8
siteName: string;
9
brandColor: string;
10
publicKnowledgeBaseEnabled: boolean;
11
ownerEmail: string;
12
ownerDisplayName: string;
13
password: string;
14
}
16
export async function getSetupStatus() {
17
const users = await pool.query(`SELECT COUNT(*)::int AS count FROM users`);
18
const branding = await pool.query(`SELECT site_name, brand_color FROM branding_settings ORDER BY created_at ASC LIMIT 1`);
20
return {
21
isInitialized: users.rows[0].count > 0,
22
branding: branding.rows[0] ?? null
23
};
24
}
26
export async function initializeLedger(input: SetupInput) {
27
const existingUsers = await pool.query(`SELECT COUNT(*)::int AS count FROM users`);
28
if (existingUsers.rows[0].count > 0) {
29
throw new Error("Ledger has already been initialized");
30
}
32
await pool.query("BEGIN");
33
try {
34
const ownerRole = await pool.query(`SELECT id FROM roles WHERE key = 'owner'`);
36
const user = await pool.query(
37
`
38
INSERT INTO users (email, password_hash, display_name, primary_role_id)
39
VALUES ($1, $2, $3, $4)
40
RETURNING id
41
`,
42
[input.ownerEmail, await hashPassword(input.password), input.ownerDisplayName, ownerRole.rows[0].id]
43
);
45
const ownerUserId = user.rows[0].id;
47
const branding = await pool.query(
48
`
49
UPDATE branding_settings
50
SET site_name = $1, brand_color = $2, footer_text = $3,
51
public_knowledge_base_enabled = $4, footer_links = '[]'::jsonb, updated_at = now()
52
WHERE id = (SELECT id FROM branding_settings ORDER BY created_at ASC LIMIT 1)
53
RETURNING id
54
`,
55
[input.siteName, input.brandColor, FIXED_FOOTER, input.publicKnowledgeBaseEnabled]
56
);
58
await pool.query(
59
`
60
INSERT INTO spaces (name, key, description, visibility, created_by_user_id)
61
VALUES
62
('Docs', 'docs', 'Public documentation for your organization', 'public', $1),
63
('Team', 'team', 'Internal knowledge for your organization', 'internal', $1)
64
ON CONFLICT (key) DO NOTHING
65
`,
66
[ownerUserId]
67
);
69
const docsSpace = await pool.query(`SELECT id FROM spaces WHERE key = 'docs'`);
70
const welcomePage = await pool.query(
71
`
72
INSERT INTO pages (space_id, title, slug, excerpt, visibility, state, is_public, owner_user_id)
73
VALUES ($1, 'Welcome to Ledger', 'welcome-to-ledger', 'Start customizing your new knowledge base.', 'public', 'published', true, $2)
74
RETURNING id
75
`,
76
[docsSpace.rows[0].id, ownerUserId]
77
);
79
const welcomeRevision = await pool.query(
80
`
81
INSERT INTO page_revisions (page_id, title, slug, excerpt, visibility, state, body_markdown, edited_by_user_id)
82
VALUES (
83
$1,
84
'Welcome to Ledger',
85
'welcome-to-ledger',
86
'Start customizing your new knowledge base.',
87
'public',
88
'published',
89
'# Welcome to Ledger\n\nYour knowledge base is ready.\n\n## Next steps\n\n- Customize branding\n- Invite your team\n- Publish your first public and internal pages',
90
$2
91
)
92
RETURNING id
93
`,
94
[welcomePage.rows[0].id, ownerUserId]
95
);
97
await pool.query(`UPDATE pages SET current_revision_id = $2 WHERE id = $1`, [
98
welcomePage.rows[0].id,
99
welcomeRevision.rows[0].id
100
]);
102
await pool.query("COMMIT");
104
const sessionUser = await getUserForSession(ownerUserId);
105
const token = createSessionToken(sessionUser!);
106
await logAudit(ownerUserId, "setup.initialize", "ledger", "instance", {
107
brandingSettingsId: branding.rows[0].id
108
});
110
return { token, user: sessionUser };
111
} catch (error) {
112
await pool.query("ROLLBACK");
113
throw error;
114
}
115
}