feat: add /setcalendar command, store tests, and improve error handling
All checks were successful
CI / build (push) Successful in 48s

- Add /setcalendar command for calendar type parity (Settings UI)
- Add 17 store tests covering all repository methods
- Add handler tests for formatDuration, fmtHHMM, fmtDDHHMM
- Add domain tests for BurnoutLevel.String, NavMonth, FormatSalary
- Fix silent error discarding in save* helpers (log write failures)
- Fix computeAndAwardXP error handling instead of discarding
- Wrap critical store.go SQL errors with fmt.Errorf context
- Update README commands table with /setcalendar
This commit is contained in:
2026-06-25 15:56:00 +03:30
parent d9e13cb51d
commit 7dbd9fda12
8 changed files with 816 additions and 22 deletions

View File

@@ -21,16 +21,16 @@ type Store struct {
func NewStore(path string) (*Store, error) {
db, err := sql.Open(driverName, path)
if err != nil {
return nil, err
return nil, fmt.Errorf("open: %w", err)
}
if err := db.Ping(); err != nil {
return nil, err
return nil, fmt.Errorf("ping: %w", err)
}
if _, err := db.Exec("PRAGMA journal_mode=WAL"); err != nil {
slog.Warn("failed to set WAL mode", "error", err)
}
if err := runMigrations(db); err != nil {
return nil, err
return nil, fmt.Errorf("migrate: %w", err)
}
return &Store{db: db}, nil
}
@@ -38,33 +38,36 @@ func NewStore(path string) (*Store, error) {
func (s *Store) DeleteUserData(userID int64) error {
tx, err := s.db.Begin()
if err != nil {
return err
return fmt.Errorf("delete user data begin tx: %w", err)
}
defer tx.Rollback()
if _, err := tx.Exec(`DELETE FROM events WHERE day_id IN (SELECT id FROM days WHERE user_id=?)`, userID); err != nil {
return err
return fmt.Errorf("delete user data events: %w", err)
}
if _, err := tx.Exec(`DELETE FROM days WHERE user_id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data days: %w", err)
}
if _, err := tx.Exec(`DELETE FROM user_achievements WHERE user_id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data achievements: %w", err)
}
if _, err := tx.Exec(`DELETE FROM daily_xp WHERE user_id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data daily_xp: %w", err)
}
if _, err := tx.Exec(`DELETE FROM user_rpg WHERE user_id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data rpg: %w", err)
}
if _, err := tx.Exec(`DELETE FROM user_settings WHERE user_id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data settings: %w", err)
}
if _, err := tx.Exec(`DELETE FROM users WHERE id=?`, userID); err != nil {
return err
return fmt.Errorf("delete user data users: %w", err)
}
return tx.Commit()
if err := tx.Commit(); err != nil {
return fmt.Errorf("delete user data commit: %w", err)
}
return nil
}
func (s *Store) Close() error {
@@ -98,7 +101,7 @@ func (s *Store) GetOrCreateUser(chatID int64) (*domain.User, error) {
"INSERT OR IGNORE INTO users (chat_id, username) VALUES (?, ?)",
chatID, randomUsername(),
); err != nil {
return nil, err
return nil, fmt.Errorf("get or create user: %w", err)
}
return s.GetUserByChatID(chatID)
}
@@ -113,7 +116,7 @@ func (s *Store) GetUserByChatID(chatID int64) (*domain.User, error) {
&u.ID, &u.ChatID, &u.Username, &u.Timezone, &u.IsActive,
&u.Calendar, &u.CreatedAt, &u.UpdatedAt,
); err != nil {
return nil, err
return nil, fmt.Errorf("get user by chat id: %w", err)
}
return &u, nil
}
@@ -123,7 +126,10 @@ func (s *Store) UpdateUser(user *domain.User) error {
"UPDATE users SET username=?, timezone=?, is_active=?, calendar=?, updated_at=unixepoch() WHERE id=?",
user.Username, user.Timezone, user.IsActive, user.Calendar, user.ID,
)
return err
if err != nil {
return fmt.Errorf("update user: %w", err)
}
return nil
}
func (s *Store) GetAllUsers() ([]domain.User, error) {