From e2ae59b5be9a07d161402090874453d0b3d40ff1 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Mon, 26 Aug 2024 16:06:31 +0300 Subject: [PATCH] Fix: user can get more than one events for daily question in one day if service will restarted --- internal/data/events.go | 2 ++ internal/data/pg/events.go | 16 ++++++++++++++++ internal/service/handlers/daily_question_get.go | 16 ++++++++++++++++ resources/model_daily_questions_attributes.go | 2 -- 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/internal/data/events.go b/internal/data/events.go index 38a07c8..827a5d9 100644 --- a/internal/data/events.go +++ b/internal/data/events.go @@ -47,6 +47,8 @@ type EventsQ interface { Page(*pgdb.OffsetPageParams) EventsQ Select() ([]Event, error) Get() (*Event, error) + // GetLast order events by created_at field in descending order + GetLast() (*Event, error) // Count returns the total number of events that match filters. Page is not // applied to this method. Count() (int, error) diff --git a/internal/data/pg/events.go b/internal/data/pg/events.go index 88e54df..315e0fc 100644 --- a/internal/data/pg/events.go +++ b/internal/data/pg/events.go @@ -22,6 +22,7 @@ type events struct { deleter squirrel.DeleteBuilder counter squirrel.SelectBuilder reopenable squirrel.SelectBuilder + last squirrel.SelectBuilder } func NewEvents(db *pgdb.DB) data.EventsQ { @@ -32,6 +33,7 @@ func NewEvents(db *pgdb.DB) data.EventsQ { deleter: squirrel.Delete(eventsTable), counter: squirrel.Select("COUNT(*) AS count").From(eventsTable), reopenable: squirrel.Select("nullifier", "type").Distinct().From(eventsTable + " e1"), + last: squirrel.Select("*").From(eventTypesTable).OrderBy("created_at DESC"), } } @@ -129,6 +131,19 @@ func (q *events) Get() (*data.Event, error) { return &res, nil } +func (q *events) GetLast() (*data.Event, error) { + var res data.Event + + if err := q.db.Get(&res, q.last); err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, nil + } + return nil, fmt.Errorf("get last event: %w", err) + } + + return &res, nil +} + func (q *events) Count() (int, error) { var res struct { Count int `db:"count"` @@ -265,5 +280,6 @@ func (q *events) applyCondition(cond squirrel.Sqlizer) data.EventsQ { q.deleter = q.deleter.Where(cond) q.counter = q.counter.Where(cond) q.reopenable = q.reopenable.Where(cond) + q.last = q.last.Where(cond) return q } diff --git a/internal/service/handlers/daily_question_get.go b/internal/service/handlers/daily_question_get.go index 29e6b0d..d873c61 100644 --- a/internal/service/handlers/daily_question_get.go +++ b/internal/service/handlers/daily_question_get.go @@ -9,6 +9,7 @@ import ( "github.com/go-chi/chi" "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" + "github.com/rarimo/geo-points-svc/internal/data/evtypes/models" "github.com/rarimo/geo-points-svc/resources" "gitlab.com/distributed_lab/ape" "gitlab.com/distributed_lab/ape/problems" @@ -45,6 +46,21 @@ func GetDailyQuestion(w http.ResponseWriter, r *http.Request) { } localDayStart := atDayStart(dq.LocalTime(time.Now().UTC())) + + ev, err := EventsQ(r).FilterByNullifier(nullifier).FilterByType(models.TypeDailyQuestion).GetLast() + if err != nil { + log.WithError(err).Error("Failed to get last daily_question event") + ape.RenderErr(w, problems.InternalError()) + return + } + if ev != nil && + ev.CreatedAt > int32(localDayStart.Unix()) && + ev.CreatedAt < int32(localDayStart.Add(24*time.Hour).Unix()) { + log.Debug("Points already accruing for daily question") + ape.RenderErr(w, problems.Conflict()) + return + } + question, err := DailyQuestionsQ(r).FilterByStartsAtAfter(localDayStart).Get() if err != nil { log.WithError(err).Error("Failed to get question") diff --git a/resources/model_daily_questions_attributes.go b/resources/model_daily_questions_attributes.go index 8017123..060578e 100644 --- a/resources/model_daily_questions_attributes.go +++ b/resources/model_daily_questions_attributes.go @@ -5,8 +5,6 @@ package resources type DailyQuestionsAttributes struct { - // Time limit after which it is impossible to answer the question. Calculated as current time + time for answer - Deadline int64 `json:"deadline"` // Answer options. Minimum 2, maximum 6 Options []DailyQuestionOptions `json:"options"` // Question title