Files
woke/db/db.go
2026-02-01 21:15:17 +02:00

210 lines
4.9 KiB
Go

package db
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
_ "modernc.org/sqlite"
)
type Alarm struct {
ID int
Name string
Description string
Time string // HH:MM format for simple alarms
Trigger string // Cron expression or "once"
SoundPath string
Enabled bool
CreatedAt time.Time
UpdatedAt time.Time
LastTriggered *time.Time
SnoozeCount int
}
type AlarmHistory struct {
ID int
AlarmID int
TriggeredAt time.Time
DismissedAt *time.Time
SnoozedCount int
}
type Store struct {
db *sql.DB
}
func Open() (*Store, error) {
home, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("get home dir: %w", err)
}
dir := filepath.Join(home, ".config", "woke")
if err := os.MkdirAll(dir, 0755); err != nil {
return nil, fmt.Errorf("create config dir: %w", err)
}
dbPath := filepath.Join(dir, "woke.db")
db, err := sql.Open("sqlite", dbPath)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
store := &Store{db: db}
if err := store.migrate(); err != nil {
db.Close()
return nil, fmt.Errorf("migrate: %w", err)
}
return store, nil
}
func (s *Store) Close() error {
return s.db.Close()
}
func (s *Store) migrate() error {
_, err := s.db.Exec(`
CREATE TABLE IF NOT EXISTS alarms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT DEFAULT '',
time TEXT DEFAULT '',
trigger TEXT NOT NULL,
sound_path TEXT NOT NULL DEFAULT 'default',
enabled BOOLEAN DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
last_triggered TIMESTAMP,
snooze_count INTEGER DEFAULT 0
);
CREATE TABLE IF NOT EXISTS alarm_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
alarm_id INTEGER,
triggered_at TIMESTAMP,
dismissed_at TIMESTAMP,
snoozed_count INTEGER DEFAULT 0,
FOREIGN KEY (alarm_id) REFERENCES alarms(id)
);
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT
);
`)
return err
}
func (s *Store) ListAlarms() ([]Alarm, error) {
rows, err := s.db.Query(`
SELECT id, name, description, time, trigger, sound_path, enabled,
created_at, updated_at, last_triggered, snooze_count
FROM alarms ORDER BY time ASC, name ASC
`)
if err != nil {
return nil, err
}
defer rows.Close()
var alarms []Alarm
for rows.Next() {
var a Alarm
var lastTriggered sql.NullTime
err := rows.Scan(&a.ID, &a.Name, &a.Description, &a.Time, &a.Trigger,
&a.SoundPath, &a.Enabled, &a.CreatedAt, &a.UpdatedAt,
&lastTriggered, &a.SnoozeCount)
if err != nil {
return nil, err
}
if lastTriggered.Valid {
a.LastTriggered = &lastTriggered.Time
}
alarms = append(alarms, a)
}
return alarms, rows.Err()
}
func (s *Store) GetAlarm(id int) (Alarm, error) {
var a Alarm
var lastTriggered sql.NullTime
err := s.db.QueryRow(`
SELECT id, name, description, time, trigger, sound_path, enabled,
created_at, updated_at, last_triggered, snooze_count
FROM alarms WHERE id = ?
`, id).Scan(&a.ID, &a.Name, &a.Description, &a.Time, &a.Trigger,
&a.SoundPath, &a.Enabled, &a.CreatedAt, &a.UpdatedAt,
&lastTriggered, &a.SnoozeCount)
if lastTriggered.Valid {
a.LastTriggered = &lastTriggered.Time
}
return a, err
}
func (s *Store) CreateAlarm(a Alarm) (int, error) {
result, err := s.db.Exec(`
INSERT INTO alarms (name, description, time, trigger, sound_path, enabled)
VALUES (?, ?, ?, ?, ?, ?)
`, a.Name, a.Description, a.Time, a.Trigger, a.SoundPath, a.Enabled)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
return int(id), err
}
func (s *Store) UpdateAlarm(a Alarm) error {
_, err := s.db.Exec(`
UPDATE alarms SET name=?, description=?, time=?, trigger=?, sound_path=?,
enabled=?, updated_at=CURRENT_TIMESTAMP
WHERE id=?
`, a.Name, a.Description, a.Time, a.Trigger, a.SoundPath, a.Enabled, a.ID)
return err
}
func (s *Store) DeleteAlarm(id int) error {
_, err := s.db.Exec(`DELETE FROM alarms WHERE id=?`, id)
return err
}
func (s *Store) ToggleAlarm(id int) error {
_, err := s.db.Exec(`
UPDATE alarms SET enabled = NOT enabled, updated_at = CURRENT_TIMESTAMP WHERE id = ?
`, id)
return err
}
func (s *Store) MarkTriggered(id int) error {
now := time.Now()
_, err := s.db.Exec(`
UPDATE alarms SET last_triggered = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?
`, now, id)
if err != nil {
return err
}
_, err = s.db.Exec(`
INSERT INTO alarm_history (alarm_id, triggered_at) VALUES (?, ?)
`, id, now)
return err
}
func (s *Store) GetSetting(key, defaultVal string) string {
var val string
err := s.db.QueryRow(`SELECT value FROM settings WHERE key = ?`, key).Scan(&val)
if err != nil {
return defaultVal
}
return val
}
func (s *Store) SetSetting(key, value string) error {
_, err := s.db.Exec(`
INSERT INTO settings (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value = excluded.value
`, key, value)
return err
}