Initial scaffold: Go module, SQLite schema, config loader, Telegram bot with webhook/polling modes
This commit is contained in:
152
internal/db/store.go
Normal file
152
internal/db/store.go
Normal file
@@ -0,0 +1,152 @@
|
||||
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"`
|
||||
}
|
||||
Reference in New Issue
Block a user