package store import ( "context" "database/sql" "github.com/wotra/wotra/internal/domain" ) // SettingsStore handles persistence for settings history. type SettingsStore struct { db *sql.DB } func NewSettingsStore(db *sql.DB) *SettingsStore { return &SettingsStore{db: db} } // Current returns the most recent settings effective on or before the given day key. func (s *SettingsStore) Current(ctx context.Context, asOfDayKey string) (*domain.Settings, error) { row := s.db.QueryRowContext(ctx, `SELECT id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at FROM settings_history WHERE effective_from <= ? ORDER BY effective_from DESC, id DESC LIMIT 1`, asOfDayKey) return scanSettings(row) } // Latest returns the most recently created settings row. func (s *SettingsStore) Latest(ctx context.Context) (*domain.Settings, error) { row := s.db.QueryRowContext(ctx, `SELECT id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at FROM settings_history ORDER BY effective_from DESC, id DESC LIMIT 1`) return scanSettings(row) } // History returns all settings rows ordered by effective_from DESC. func (s *SettingsStore) History(ctx context.Context) ([]*domain.Settings, error) { rows, err := s.db.QueryContext(ctx, `SELECT id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at FROM settings_history ORDER BY effective_from DESC, id DESC`) if err != nil { return nil, err } defer rows.Close() var result []*domain.Settings for rows.Next() { var s domain.Settings if err := rows.Scan(&s.ID, &s.EffectiveFrom, &s.HoursPerWeek, &s.WorkdaysMask, &s.Timezone, &s.CreatedAt, &s.UpdatedAt); err != nil { return nil, err } result = append(result, &s) } return result, rows.Err() } // Insert inserts a new settings row. func (s *SettingsStore) Insert(ctx context.Context, set *domain.Settings) error { _, err := s.db.ExecContext(ctx, `INSERT INTO settings_history (id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)`, set.ID, set.EffectiveFrom, set.HoursPerWeek, set.WorkdaysMask, set.Timezone, set.CreatedAt, set.UpdatedAt) return err } // Update overwrites an existing settings row by ID. func (s *SettingsStore) Update(ctx context.Context, set *domain.Settings) error { _, err := s.db.ExecContext(ctx, `UPDATE settings_history SET effective_from=?, hours_per_week=?, workdays_mask=?, timezone=?, updated_at=? WHERE id=?`, set.EffectiveFrom, set.HoursPerWeek, set.WorkdaysMask, set.Timezone, set.UpdatedAt, set.ID) return err } // Upsert inserts or replaces a settings row (used by sync push; last updated_at wins). func (s *SettingsStore) Upsert(ctx context.Context, set *domain.Settings) error { _, err := s.db.ExecContext(ctx, `INSERT INTO settings_history (id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET effective_from=excluded.effective_from, hours_per_week=excluded.hours_per_week, workdays_mask=excluded.workdays_mask, timezone=excluded.timezone, updated_at=excluded.updated_at WHERE excluded.updated_at > settings_history.updated_at`, set.ID, set.EffectiveFrom, set.HoursPerWeek, set.WorkdaysMask, set.Timezone, set.CreatedAt, set.UpdatedAt) return err } // Delete removes a settings row by ID. func (s *SettingsStore) Delete(ctx context.Context, id string) error { _, err := s.db.ExecContext(ctx, `DELETE FROM settings_history WHERE id=?`, id) return err } // Count returns the total number of settings rows. func (s *SettingsStore) Count(ctx context.Context) (int, error) { var n int err := s.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM settings_history`).Scan(&n) return n, err } // GetByID returns a single settings row by ID. func (s *SettingsStore) GetByID(ctx context.Context, id string) (*domain.Settings, error) { row := s.db.QueryRowContext(ctx, `SELECT id, effective_from, hours_per_week, workdays_mask, timezone, created_at, updated_at FROM settings_history WHERE id=?`, id) return scanSettings(row) } func scanSettings(row *sql.Row) (*domain.Settings, error) { var s domain.Settings err := row.Scan(&s.ID, &s.EffectiveFrom, &s.HoursPerWeek, &s.WorkdaysMask, &s.Timezone, &s.CreatedAt, &s.UpdatedAt) if err != nil { return nil, err } return &s, nil }