import type { AppSettings, Env } from './types'; export const DEFAULT_SETTINGS: AppSettings = { title: 'Book a slot', subtitle: 'Pick a day, choose a time, and you are set.', timezone: 'Europe/Stockholm', bookingWindowDays: 365, minimumNoticeMinutes: 120, bufferBeforeMinutes: 0, bufferAfterMinutes: 15, slotIncrementMinutes: 15, durations: [15, 30, 45, 60], calendarId: 'primary', busyCalendarIds: ['primary'], defaultMeetingMode: 'google_meet', allowMeetingModes: ['google_meet'], inPersonLocation: '', paymentEnabled: false, paymentRatePerMinute: 0, paymentFreeDurations: [], paymentCurrency: 'sek', paymentLabel: 'Booking payment', availability: { '0': [], '1': [{ start: '09:00', end: '17:00' }], '2': [{ start: '09:00', end: '17:00' }], '3': [{ start: '09:00', end: '17:00' }], '4': [{ start: '09:00', end: '17:00' }], '5': [{ start: '09:00', end: '15:00' }], '6': [], }, }; export async function getSettings(env: Env): Promise { const row = await env.DB.prepare('SELECT value FROM settings WHERE key = ?1') .bind('app_settings') .first<{ value: string }>(); if (!row?.value) return DEFAULT_SETTINGS; try { const parsed = JSON.parse(row.value) as Partial & { paymentAmountMinor?: number }; const currency = typeof parsed.paymentCurrency === 'string' && /^[a-zA-Z]{3}$/.test(parsed.paymentCurrency) ? parsed.paymentCurrency.toLowerCase() : DEFAULT_SETTINGS.paymentCurrency; const hasPerMinuteRate = parsed.paymentRatePerMinute !== undefined && Number.isFinite(Number(parsed.paymentRatePerMinute)); const paymentRatePerMinute = hasPerMinuteRate ? Math.max(0, Number(parsed.paymentRatePerMinute)) : DEFAULT_SETTINGS.paymentRatePerMinute; return { ...DEFAULT_SETTINGS, ...parsed, // Old releases stored one fixed Stripe amount in `paymentAmountMinor`. // There is no safe way to reinterpret that as a per-minute rate, so legacy // payment settings are deliberately disabled until the owner saves a rate. paymentEnabled: hasPerMinuteRate ? Boolean(parsed.paymentEnabled) : false, paymentCurrency: currency, paymentRatePerMinute, paymentFreeDurations: Array.isArray(parsed.paymentFreeDurations) ? parsed.paymentFreeDurations.map(Number).filter((value) => Number.isFinite(value) && value > 0) : DEFAULT_SETTINGS.paymentFreeDurations, durations: Array.isArray(parsed.durations) ? parsed.durations : DEFAULT_SETTINGS.durations, busyCalendarIds: Array.isArray(parsed.busyCalendarIds) ? parsed.busyCalendarIds : DEFAULT_SETTINGS.busyCalendarIds, allowMeetingModes: Array.isArray(parsed.allowMeetingModes) ? parsed.allowMeetingModes : DEFAULT_SETTINGS.allowMeetingModes, availability: { ...DEFAULT_SETTINGS.availability, ...(parsed.availability ?? {}), }, }; } catch { return DEFAULT_SETTINGS; } } export async function saveSettings(env: Env, settings: AppSettings): Promise { await env.DB.prepare(` INSERT INTO settings (key, value, updated_at) VALUES (?1, ?2, datetime('now')) ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at `).bind('app_settings', JSON.stringify(settings)).run(); }