package postgres import ( "context" "botpontos/internal/domain" ) const dayCols = `user_id, to_char(day, 'YYYY-MM-DD'), status, zero_point_checks, last_point_count, next_check_at, resumed_by, updated_at` func readDay(row scanner) (domain.UserDay, error) { var d domain.UserDay var status string var resumed *string if err := row.Scan(&d.UserID, &d.Day, &status, &d.ZeroPointChecks, &d.LastPointCount, &d.NextCheckAt, &resumed, &d.UpdatedAt); err != nil { return d, mapErr(err) } d.Status = domain.DayStatus(status) if resumed != nil { r := domain.ResumeSource(*resumed) d.ResumedBy = &r } return d, nil } // GetUserDay devolve a situação do dia; found=false se o dia ainda não foi consultado. func (s *Store) GetUserDay(ctx context.Context, userID int64, day string) (domain.UserDay, bool, error) { d, err := readDay(s.pool.QueryRow(ctx, `SELECT `+dayCols+` FROM user_days WHERE user_id = $1 AND day = $2::text::date`, userID, day)) if err == domain.ErrNotFound { return domain.NewUserDay(userID, day), false, nil } return d, err == nil, err } func (s *Store) SaveUserDay(ctx context.Context, d domain.UserDay) error { var resumed *string if d.ResumedBy != nil { v := string(*d.ResumedBy) resumed = &v } _, err := s.pool.Exec(ctx, ` INSERT INTO user_days (user_id, day, status, zero_point_checks, last_point_count, next_check_at, resumed_by) VALUES ($1, $2::text::date, $3::text::day_status, $4, $5, $6, $7) ON CONFLICT (user_id, day) DO UPDATE SET status = EXCLUDED.status, zero_point_checks = EXCLUDED.zero_point_checks, last_point_count = EXCLUDED.last_point_count, next_check_at = EXCLUDED.next_check_at, resumed_by = EXCLUDED.resumed_by`, d.UserID, d.Day, string(d.Status), d.ZeroPointChecks, d.LastPointCount, d.NextCheckAt, resumed) return mapErr(err) } // ResetNextChecks antecipa as próximas consultas do dia para agora; usado quando a // janela muda (9.13), para a reconciliação recalcular tudo no próximo tick. Inclui // o dia anterior para cobrir usuários em fusos diferentes perto da meia-noite. func (s *Store) ResetNextChecks(ctx context.Context, day string) error { _, err := s.pool.Exec(ctx, ` UPDATE user_days SET next_check_at = now() WHERE day >= $1::text::date - 1 AND next_check_at IS NOT NULL`, day) return err }