refactor: restore stored aggregates with incremental update on edit

- Restore TotalWorkSeconds, CurrentStreak, LongestStreak writes in
  computeAndAwardXP and updateStreak (incremental on clock out)
- Add recalcDayWorkSeconds and recalcAggregates for event edit path
  (recompute day + total + streak from scratch when events change)
- Call recalcAggregates after editEventTimeSet, deleteEventConfirm,
  addEventDo in calendar.go
- Remove runtime computeStreaks and totalWorkSeconds (N+1 scan)
  from RPG display, burnout, and clockOut
- Add SetDailyWorkSeconds, SumDailyWorkSeconds store methods
- Clean up unused dead functions and dead code in rpg.go
This commit is contained in:
2026-06-25 02:19:40 +03:30
parent 4fcb694418
commit 5cd811e057
4 changed files with 147 additions and 60 deletions

View File

@@ -593,7 +593,8 @@ func (s *Store) UpdateRPGStats(st *RPGStats) error {
// -- Daily XP --
// UpsertDailyXP inserts or updates the daily XP record for a user/date.
// UpsertDailyXP inserts or updates the daily XP and work seconds for a user/date.
// xpEarned and workSeconds are accumulated on conflict.
func (s *Store) UpsertDailyXP(userID int64, date string, xpEarned, workSeconds int64) error {
_, err := s.db.Exec(`
INSERT INTO daily_xp (user_id, date, xp_earned, work_seconds)
@@ -605,6 +606,26 @@ func (s *Store) UpsertDailyXP(userID int64, date string, xpEarned, workSeconds i
return err
}
// SetDailyWorkSeconds overwrites the work_seconds for a specific day (used during recalc on edit).
func (s *Store) SetDailyWorkSeconds(userID int64, date string, workSeconds int64) error {
_, err := s.db.Exec(`
INSERT INTO daily_xp (user_id, date, xp_earned, work_seconds)
VALUES (?, ?, 0, ?)
ON CONFLICT(user_id, date) DO UPDATE SET
work_seconds = ?
`, userID, date, workSeconds, workSeconds)
return err
}
// SumDailyWorkSeconds returns the sum of work_seconds across all days for the user.
func (s *Store) SumDailyWorkSeconds(userID int64) (int64, error) {
var total int64
err := s.db.QueryRow(
"SELECT COALESCE(SUM(work_seconds), 0) FROM daily_xp WHERE user_id=?", userID,
).Scan(&total)
return total, err
}
// GetDailyXP returns the daily XP record for a user/date.
func (s *Store) GetDailyXP(userID int64, date string) (*DailyXP, error) {
row := s.db.QueryRow(`
@@ -890,6 +911,31 @@ func (s *Store) GetUserDaysInRange(userID int64, startDate, endDate string) ([]D
return days, rows.Err()
}
// GetDatesWithEvents returns all distinct dates (from the days table) on which
// the user has at least one event, ordered ascending.
func (s *Store) GetDatesWithEvents(userID int64) ([]string, error) {
rows, err := s.db.Query(`
SELECT DISTINCT d.date
FROM days d
JOIN events e ON e.day_id = d.id
WHERE d.user_id = ?
ORDER BY d.date ASC
`, userID)
if err != nil {
return nil, err
}
defer rows.Close()
var dates []string
for rows.Next() {
var date string
if err := rows.Scan(&date); err != nil {
return nil, err
}
dates = append(dates, date)
}
return dates, rows.Err()
}
// CreateEvent inserts a new event record (typically "in" or "out").
func (s *Store) CreateEvent(userID int64, dayID int64, eventType string, workTypeID *int64, occurredAt int64, note string) error {
_, err := s.db.Exec(