package db import ( "database/sql" "os" "path/filepath" _ "modernc.org/sqlite" ) const driverName = "sqlite" var schemaPath = filepath.Join("db", "schema.sql") // Store wraps the sql.DB connection. type Store struct { db *sql.DB } // NewStore opens the SQLite DB and runs the schema file. func NewStore(path string) (*Store, error) { db, err := sql.Open(driverName, path) if err != nil { return nil, err } if err := db.Ping(); err != nil { return nil, err } if err := execSchema(db, schemaPath); err != nil { return nil, err } return &Store{db: db}, nil } func execSchema(db *sql.DB, path string) error { data, err := os.ReadFile(path) if err != nil { return err } _, err = db.Exec(string(data)) return err } // CreateEvent inserts a new event. func (s *Store) CreateEvent(eventType string, occurredAt int64, note string) error { query := ` INSERT INTO events (event_type, occurred_at, note) VALUES (?, ?, ?) ` _, err := s.db.Exec(query, eventType, occurredAt, note) return err } // GetLastEvent returns the most recent event. func (s *Store) GetLastEvent() (*Event, error) { row, err := s.db.Query(` SELECT id, event_type, occurred_at, note, created_at FROM events ORDER BY occurred_at DESC LIMIT 1 `) if err != nil { return nil, err } defer row.Close() if !row.Next() { return nil, sql.ErrNoRows } var e Event if err := row.Scan(&e.ID, &e.EventType, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil { return nil, err } return &e, nil } // GetSetting returns a setting value by key. func (s *Store) GetSetting(key string) (string, error) { var value string err := s.db.QueryRow("SELECT value FROM settings WHERE key = ?", key).Scan(&value) if err != nil { return "", err } return value, nil } // SetSetting stores or updates a setting. func (s *Store) SetSetting(key, value string) error { query := ` INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value ` _, err := s.db.Exec(query, key, value) return err } // SetDayOff adds or updates a day‑off record. func (s *Store) SetDayOff(date, reason string) error { query := ` INSERT INTO days_off (date, reason) VALUES (?, ?) ON CONFLICT(date) DO UPDATE SET reason=excluded.reason ` _, err := s.db.Exec(query, date, reason) return err } // RemoveDayOff deletes a day‑off record. func (s *Store) RemoveDayOff(date string) error { _, err := s.db.Exec(`DELETE FROM days_off WHERE date = ?`, date) return err } // IsDayOff returns true if the given date (YYYY-MM-DD) is marked as day off. func (s *Store) IsDayOff(date string) (bool, error) { var exists int err := s.db.QueryRow(`SELECT COUNT(1) FROM days_off WHERE date = ?`, date).Scan(&exists) if err != nil { return false, err } return exists > 0, nil } // EventsForDay returns all events for a given day (ISO date string). func (s *Store) EventsForDay(day string) ([]Event, error) { rows, err := s.db.Query(` SELECT id, event_type, occurred_at, note, created_at FROM events WHERE strftime('%Y-%m-%d', occurred_at, 'unixepoch') = ? ORDER BY occurred_at ASC `, day) if err != nil { return nil, err } defer rows.Close() var events []Event for rows.Next() { var e Event if err := rows.Scan(&e.ID, &e.EventType, &e.OccurredAt, &e.Note, &e.CreatedAt); err != nil { return nil, err } events = append(events, e) } return events, rows.Err() } // Event represents a row from the events table. type Event struct { ID int64 `json:"id"` EventType string `json:"event_type"` OccurredAt int64 `json:"occurred_at"` Note string `json:"note"` CreatedAt int64 `json:"created_at"` }