import Database from 'better-sqlite3'; import { mkdirSync } from 'node:fs'; import { join } from 'node:path'; import type { SleepEntry } from './sleep'; const dataDirectory = process.env.DATA_DIR ?? join(process.cwd(), 'data'); mkdirSync(dataDirectory, { recursive: true }); const database = new Database(join(dataDirectory, 'sleepy.db')); database.pragma('journal_mode = WAL'); database.exec(` CREATE TABLE IF NOT EXISTS sleep_entries ( date TEXT PRIMARY KEY NOT NULL, napped INTEGER NOT NULL DEFAULT 0, nap_start TEXT, nap_duration_minutes INTEGER, bedtime TEXT NOT NULL, sleep_latency_minutes INTEGER NOT NULL, awakenings INTEGER NOT NULL, waso_minutes INTEGER NOT NULL, rise_time TEXT NOT NULL, alarm_type TEXT NOT NULL, rating INTEGER NOT NULL, notes TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); `); type SleepEntryRow = Omit & { napped: number; nap_start: string | null; nap_duration_minutes: number | null; sleep_latency_minutes: number; waso_minutes: number; rise_time: string; alarm_type: SleepEntry['alarmType']; created_at: string; updated_at: string; }; function mapEntry(row: SleepEntryRow): SleepEntry { return { date: row.date, napped: Boolean(row.napped), napStart: row.nap_start, napDurationMinutes: row.nap_duration_minutes, bedtime: row.bedtime, sleepLatencyMinutes: row.sleep_latency_minutes, awakenings: row.awakenings, wasoMinutes: row.waso_minutes, riseTime: row.rise_time, alarmType: row.alarm_type, rating: row.rating, notes: row.notes, createdAt: row.created_at, updatedAt: row.updated_at, }; } export function getEntry(date: string): SleepEntry | null { const row = database.prepare('SELECT * FROM sleep_entries WHERE date = ?').get(date) as SleepEntryRow | undefined; return row ? mapEntry(row) : null; } export function saveEntry(entry: SleepEntry): SleepEntry { database.prepare(` INSERT INTO sleep_entries ( date, napped, nap_start, nap_duration_minutes, bedtime, sleep_latency_minutes, awakenings, waso_minutes, rise_time, alarm_type, rating, notes ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(date) DO UPDATE SET napped = excluded.napped, nap_start = excluded.nap_start, nap_duration_minutes = excluded.nap_duration_minutes, bedtime = excluded.bedtime, sleep_latency_minutes = excluded.sleep_latency_minutes, awakenings = excluded.awakenings, waso_minutes = excluded.waso_minutes, rise_time = excluded.rise_time, alarm_type = excluded.alarm_type, rating = excluded.rating, notes = excluded.notes, updated_at = CURRENT_TIMESTAMP `).run( entry.date, Number(entry.napped), entry.napStart, entry.napDurationMinutes, entry.bedtime, entry.sleepLatencyMinutes, entry.awakenings, entry.wasoMinutes, entry.riseTime, entry.alarmType, entry.rating, entry.notes, ); return getEntry(entry.date) ?? entry; }