153 lines
3.6 KiB
Go
153 lines
3.6 KiB
Go
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"`
|
||
}
|