Spaces:
Runtime error
Runtime error
| import { NextRequest, NextResponse } from 'next/server' | |
| import { requireRole } from '@/lib/auth' | |
| import { getDatabase } from '@/lib/db' | |
| import { logger } from '@/lib/logger' | |
| import { nextIncompleteStepIndex, parseCompletedSteps, shouldShowOnboarding, markStepCompleted } from '@/lib/onboarding-state' | |
| const ONBOARDING_STEPS = [ | |
| { id: 'welcome', title: 'Welcome' }, | |
| { id: 'interface-mode', title: 'Interface' }, | |
| { id: 'gateway-link', title: 'Gateway' }, | |
| { id: 'credentials', title: 'Credentials' }, | |
| ] as const | |
| const ONBOARDING_SETTING_KEYS = { | |
| completed: 'onboarding.completed', | |
| completedAt: 'onboarding.completed_at', | |
| skipped: 'onboarding.skipped', | |
| completedSteps: 'onboarding.completed_steps', | |
| checklistDismissed: 'onboarding.checklist_dismissed', | |
| } as const | |
| type OnboardingSettingKey = typeof ONBOARDING_SETTING_KEYS[keyof typeof ONBOARDING_SETTING_KEYS] | |
| function scopedOnboardingKey(key: OnboardingSettingKey, username: string): string { | |
| return `user.${username}.${key}` | |
| } | |
| function getOnboardingSetting(key: string): string { | |
| try { | |
| const db = getDatabase() | |
| const row = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined | |
| return row?.value ?? '' | |
| } catch { | |
| return '' | |
| } | |
| } | |
| function setOnboardingSetting(key: string, value: string, actor: string) { | |
| const db = getDatabase() | |
| db.prepare(` | |
| INSERT INTO settings (key, value, description, category, updated_by, updated_at) | |
| VALUES (?, ?, ?, 'onboarding', ?, unixepoch()) | |
| ON CONFLICT(key) DO UPDATE SET | |
| value = excluded.value, | |
| updated_by = excluded.updated_by, | |
| updated_at = unixepoch() | |
| `).run(key, value, `Onboarding: ${key}`, actor) | |
| } | |
| function readUserOnboardingSetting(key: OnboardingSettingKey, username: string): string { | |
| const scopedValue = getOnboardingSetting(scopedOnboardingKey(key, username)) | |
| if (scopedValue !== '') return scopedValue | |
| return getOnboardingSetting(key) | |
| } | |
| function writeUserOnboardingSetting(key: OnboardingSettingKey, value: string, actor: string) { | |
| setOnboardingSetting(scopedOnboardingKey(key, actor), value, actor) | |
| } | |
| export async function GET(request: NextRequest) { | |
| const auth = requireRole(request, 'viewer') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| try { | |
| const completed = readUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completed, auth.user.username) === 'true' | |
| const skipped = readUserOnboardingSetting(ONBOARDING_SETTING_KEYS.skipped, auth.user.username) === 'true' | |
| const checklistDismissed = readUserOnboardingSetting(ONBOARDING_SETTING_KEYS.checklistDismissed, auth.user.username) === 'true' | |
| const completedStepsRaw = readUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedSteps, auth.user.username) | |
| const completedSteps = parseCompletedSteps(completedStepsRaw, ONBOARDING_STEPS) | |
| const isAdmin = auth.user.role === 'admin' | |
| const showOnboarding = shouldShowOnboarding({ completed, skipped, isAdmin }) | |
| const steps = ONBOARDING_STEPS.map((s) => ({ | |
| ...s, | |
| completed: completedSteps.includes(s.id), | |
| })) | |
| const currentStep = nextIncompleteStepIndex(ONBOARDING_STEPS, completedSteps) | |
| return NextResponse.json({ | |
| showOnboarding, | |
| completed, | |
| skipped, | |
| checklistDismissed, | |
| isAdmin, | |
| currentStep: currentStep === -1 ? steps.length - 1 : currentStep, | |
| steps, | |
| }) | |
| } catch (error) { | |
| logger.error({ err: error }, 'Onboarding GET error') | |
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | |
| } | |
| } | |
| export async function POST(request: NextRequest) { | |
| const auth = requireRole(request, 'admin') | |
| if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) | |
| try { | |
| const body = await request.json() | |
| const { action, step } = body as { action: string; step?: string } | |
| switch (action) { | |
| case 'complete_step': { | |
| if (!step) return NextResponse.json({ error: 'step is required' }, { status: 400 }) | |
| const valid = ONBOARDING_STEPS.some(s => s.id === step) | |
| if (!valid) return NextResponse.json({ error: 'Invalid step' }, { status: 400 }) | |
| const raw = readUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedSteps, auth.user.username) | |
| const parsed = parseCompletedSteps(raw, ONBOARDING_STEPS) | |
| const steps = markStepCompleted(parsed, step, ONBOARDING_STEPS) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedSteps, JSON.stringify(steps), auth.user.username) | |
| return NextResponse.json({ ok: true, completedSteps: steps }) | |
| } | |
| case 'complete': { | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completed, 'true', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedAt, String(Date.now()), auth.user.username) | |
| return NextResponse.json({ ok: true }) | |
| } | |
| case 'skip': { | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.skipped, 'true', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedAt, String(Date.now()), auth.user.username) | |
| return NextResponse.json({ ok: true }) | |
| } | |
| case 'dismiss_checklist': { | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.checklistDismissed, 'true', auth.user.username) | |
| return NextResponse.json({ ok: true }) | |
| } | |
| case 'reset': { | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completed, 'false', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedAt, '', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.skipped, 'false', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.completedSteps, '[]', auth.user.username) | |
| writeUserOnboardingSetting(ONBOARDING_SETTING_KEYS.checklistDismissed, 'false', auth.user.username) | |
| return NextResponse.json({ ok: true }) | |
| } | |
| default: | |
| return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) | |
| } | |
| } catch (error) { | |
| logger.error({ err: error }, 'Onboarding POST error') | |
| return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) | |
| } | |
| } | |