public
imalexnord
read
Slot
Calendar-first scheduling powered by Cloudflare Workers and Google Calendar.
Languages
Repository composition by tracked source files.
TypeScript
79%
CSS
19%
SQL
2%
HTML
0%
Create file
Wiki Documentation
Clone
https://nobgit.com/user/imalexnord/slot.git
ssh://[email protected]:2222/user/imalexnord/slot.git
Trace
worker/settings.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 type { AppSettings, Env } from './types';
3
export const DEFAULT_SETTINGS: AppSettings = {
4
title: 'Book a slot',
5
subtitle: 'Pick a day, choose a time, and you are set.',
6
timezone: 'Europe/Stockholm',
7
bookingWindowDays: 365,
8
minimumNoticeMinutes: 120,
9
bufferBeforeMinutes: 0,
10
bufferAfterMinutes: 15,
11
slotIncrementMinutes: 15,
12
durations: [15, 30, 45, 60],
13
calendarId: 'primary',
14
busyCalendarIds: ['primary'],
15
defaultMeetingMode: 'google_meet',
16
allowMeetingModes: ['google_meet'],
17
inPersonLocation: '',
18
paymentEnabled: false,
19
paymentRatePerMinute: 0,
20
paymentFreeDurations: [],
21
paymentCurrency: 'sek',
22
paymentLabel: 'Booking payment',
23
availability: {
24
'0': [],
25
'1': [{ start: '09:00', end: '17:00' }],
26
'2': [{ start: '09:00', end: '17:00' }],
27
'3': [{ start: '09:00', end: '17:00' }],
28
'4': [{ start: '09:00', end: '17:00' }],
29
'5': [{ start: '09:00', end: '15:00' }],
30
'6': [],
31
},
32
};
34
export async function getSettings(env: Env): Promise<AppSettings> {
35
const row = await env.DB.prepare('SELECT value FROM settings WHERE key = ?1')
36
.bind('app_settings')
37
.first<{ value: string }>();
39
if (!row?.value) return DEFAULT_SETTINGS;
41
try {
42
const parsed = JSON.parse(row.value) as Partial<AppSettings> & { paymentAmountMinor?: number };
43
const currency = typeof parsed.paymentCurrency === 'string' && /^[a-zA-Z]{3}$/.test(parsed.paymentCurrency)
44
? parsed.paymentCurrency.toLowerCase()
45
: DEFAULT_SETTINGS.paymentCurrency;
46
const hasPerMinuteRate = parsed.paymentRatePerMinute !== undefined
47
&& Number.isFinite(Number(parsed.paymentRatePerMinute));
48
const paymentRatePerMinute = hasPerMinuteRate
49
? Math.max(0, Number(parsed.paymentRatePerMinute))
50
: DEFAULT_SETTINGS.paymentRatePerMinute;
51
return {
52
...DEFAULT_SETTINGS,
53
...parsed,
54
// Old releases stored one fixed Stripe amount in `paymentAmountMinor`.
55
// There is no safe way to reinterpret that as a per-minute rate, so legacy
56
// payment settings are deliberately disabled until the owner saves a rate.
57
paymentEnabled: hasPerMinuteRate ? Boolean(parsed.paymentEnabled) : false,
58
paymentCurrency: currency,
59
paymentRatePerMinute,
60
paymentFreeDurations: Array.isArray(parsed.paymentFreeDurations)
61
? parsed.paymentFreeDurations.map(Number).filter((value) => Number.isFinite(value) && value > 0)
62
: DEFAULT_SETTINGS.paymentFreeDurations,
63
durations: Array.isArray(parsed.durations) ? parsed.durations : DEFAULT_SETTINGS.durations,
64
busyCalendarIds: Array.isArray(parsed.busyCalendarIds) ? parsed.busyCalendarIds : DEFAULT_SETTINGS.busyCalendarIds,
65
allowMeetingModes: Array.isArray(parsed.allowMeetingModes)
66
? parsed.allowMeetingModes
67
: DEFAULT_SETTINGS.allowMeetingModes,
68
availability: {
69
...DEFAULT_SETTINGS.availability,
70
...(parsed.availability ?? {}),
71
},
72
};
73
} catch {
74
return DEFAULT_SETTINGS;
75
}
76
}
78
export async function saveSettings(env: Env, settings: AppSettings): Promise<void> {
79
await env.DB.prepare(`
80
INSERT INTO settings (key, value, updated_at)
81
VALUES (?1, ?2, datetime('now'))
82
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at
83
`).bind('app_settings', JSON.stringify(settings)).run();
84
}