SPB Git

Repository Public

bootstrap.ts 2.4 KB 38 lines typescript

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
Files main
Menu

Theme