First draft
This commit is contained in:
209
db/db.go
Normal file
209
db/db.go
Normal file
@@ -0,0 +1,209 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user