Repository Public
1import { mkdirSync, writeFileSync } from "node:fs";2import path from "node:path";3import "./_env";4import { sql } from "../src/lib/db";5import { env } from "../src/lib/env";6import { issueSetupToken, passkeyCount } from "../src/lib/auth";78/**9 * Idempotent first-run setup: the owner account, its settings, a starting weekly schedule and two starting10 * appointment types (all editable in /admin). While no passkey is enrolled, writes a one-time enrolment link to a11 * file readable only by the service account.12 */13async function main() {14 const email = env.ownerEmail;15 if (!email) throw new Error("OWNER_EMAIL is required");16 let [u] = await sql<{ id: string }[]>`SELECT id FROM users ORDER BY created_at LIMIT 1`;17 if (!u) {18 [u] = await sql<{ id: string }[]>`INSERT INTO users (email, display_name) VALUES (${email}, ${env.ownerName}) RETURNING id`;19 await sql`INSERT INTO settings (user_id, public_name, notify_email) VALUES (${u.id}, ${env.ownerName}, ${email})`;20 for (const weekday of [1, 2, 3, 4, 5]) {21 await sql`INSERT INTO availability_rules (user_id, weekday, start_minute, end_minute) VALUES (${u.id}, ${weekday}, 540, 720), (${u.id}, ${weekday}, 780, 1020)`;22 }23 await sql`INSERT INTO event_types (user_id, slug, title, title_en, description, description_en, duration_min, location_kind, color, position, languages)24 VALUES (${u.id}, 'rencontre-30', 'Rencontre de 30 minutes', '30-minute meeting', 'Discussion en visioconférence.', 'Video call discussion.', 30, 'video', '#2257e0', 0, '{fr,en}'),25 (${u.id}, 'appel-15', 'Appel de 15 minutes', '15-minute call', 'Appel téléphonique court.', 'Short phone call.', 15, 'phone', '#087a55', 1, '{fr,en}')`;26 console.log(" owner, settings, weekly hours and starting appointment types created");27 }28 if ((await passkeyCount(u.id)) === 0) {29 const token = await issueSetupToken(u.id, "setup", 7 * 24 * 60);30 const url = `${env.appUrl}/admin/setup#${token}`;31 const file = process.env.SETUP_URL_FILE;32 if (file) { mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 }); writeFileSync(file, url + "\n", { mode: 0o600 }); console.log(` no passkey yet: one-time enrolment link written to ${file} (valid 7 days)`); }33 else console.log(` no passkey yet: one-time enrolment link (valid 7 days): ${url}`);34 }35 await sql.end();36}37main().catch((e) => { console.error(e.message); process.exit(1); });38