Files
worktimeBot/internal/db/store.go

153 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 dayoff 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 dayoff 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"`
}