65 lines
2.3 KiB
Go
65 lines
2.3 KiB
Go
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
|
|
}
|