From 7e3c56b5e4137ba07c1369c6fc377061b99a1f72 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Fri, 23 Aug 2024 14:02:28 +0300 Subject: [PATCH 01/29] edit daily questions cleaner --- .../workers/cleanquestiondeadlines/main.go | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/internal/service/workers/cleanquestiondeadlines/main.go b/internal/service/workers/cleanquestiondeadlines/main.go index 084851d..1a08181 100644 --- a/internal/service/workers/cleanquestiondeadlines/main.go +++ b/internal/service/workers/cleanquestiondeadlines/main.go @@ -2,40 +2,45 @@ package cleanquestiondeadlines import ( "context" - "time" + "fmt" + "github.com/go-co-op/gocron/v2" "github.com/rarimo/geo-points-svc/internal/config" + "github.com/rarimo/geo-points-svc/internal/service/workers/cron" ) func Run(ctx context.Context, cfg config.Config, sig chan struct{}) { offset := cfg.DailyQuestions().Timezone + if offset < 0 { + offset = 12 + offset + } + cron.Init(cfg.Log()) - for { - now := time.Now().UTC().Add(time.Duration(offset) * time.Hour) - cfg.Log().Info("Daily Question cleaning start") - nextMidnight := time.Date(now.Year(), now.Month(), now.Day()+1, 0, 0, 0, 0, time.UTC). - Add(time.Duration(offset) * time.Hour) - - durationUntilNextTick := nextMidnight.Sub(now) + atDayStart := gocron.NewAtTimes(gocron.NewAtTime(uint(offset), 0, 0)) - timer := time.NewTimer(durationUntilNextTick) + _, err := cron.NewJob( + gocron.DailyJob(1, atDayStart), + gocron.NewTask(job, cfg, ctx, sig), + gocron.WithName("Daily Questions leaner"), + ) + if err != nil { + panic(fmt.Errorf("cleaner daily questions: failed to initialize daily job: %w", err)) + } - select { - case <-timer.C: - res := cfg.DailyQuestions().ClearDeadlines() - cfg.Log().Infof("Сleared daily questions quantity: %v", res) +} - timer.Stop() +func job(cfg config.Config, ctx context.Context, sig chan struct{}) { + select { + default: + res := cfg.DailyQuestions().ClearDeadlines() + cfg.Log().Infof("Сleared daily questions quantity: %v", res) - case <-sig: - cfg.Log().Info("Daily Question cleaning stop") - timer.Stop() - return + case <-sig: + cfg.Log().Info("Daily Question cleaning stop") + return - case <-ctx.Done(): - cfg.Log().Info("Daily Question cleaning stop") - timer.Stop() - return - } + case <-ctx.Done(): + cfg.Log().Info("Daily Question cleaning stop") + return } } From a63a464484064ebbaa56ee40b396c57f20af0642 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Fri, 23 Aug 2024 14:05:25 +0300 Subject: [PATCH 02/29] edit daily questions cleaner change ctx --- internal/service/workers/cleanquestiondeadlines/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/workers/cleanquestiondeadlines/main.go b/internal/service/workers/cleanquestiondeadlines/main.go index 1a08181..b0f5e64 100644 --- a/internal/service/workers/cleanquestiondeadlines/main.go +++ b/internal/service/workers/cleanquestiondeadlines/main.go @@ -20,7 +20,7 @@ func Run(ctx context.Context, cfg config.Config, sig chan struct{}) { _, err := cron.NewJob( gocron.DailyJob(1, atDayStart), - gocron.NewTask(job, cfg, ctx, sig), + gocron.NewTask(job, ctx, cfg, sig), gocron.WithName("Daily Questions leaner"), ) if err != nil { @@ -29,7 +29,7 @@ func Run(ctx context.Context, cfg config.Config, sig chan struct{}) { } -func job(cfg config.Config, ctx context.Context, sig chan struct{}) { +func job(ctx context.Context, cfg config.Config, sig chan struct{}) { select { default: res := cfg.DailyQuestions().ClearDeadlines() From 9ed3923d5cdd1c71dc917d59418cd094b142b141 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Mon, 26 Aug 2024 17:47:01 +0300 Subject: [PATCH 03/29] add admin flow daily questions --- .../components/schemas/DailyQuestionDel.yaml | 15 +++ .../schemas/DailyQuestionDelKey.yaml | 11 ++ .../schemas/DailyQuestionDetails.yaml | 78 +++++++++++++ .../schemas/DailyQuestionDetailsKey.yaml | 11 ++ .../components/schemas/DailyQuestionEdit.yaml | 56 ++++++++++ .../schemas/DailyQuestionEditKey.yaml | 11 ++ ...-svc@v1@public@daily_questions@create.yaml | 39 +++++++ ...v1@public@daily_questions@delete@{ID}.yaml | 31 ++++++ ...c@v1@public@daily_questions@edit@{ID}.yaml | 37 ++++++ ...aily_questions@filter_start_at@{date}.yaml | 25 +++++ internal/data/daily_questions.go | 9 +- internal/data/pg/daily_questions.go | 28 +++++ .../service/handlers/daily_question_create.go | 105 ++++++++++++++++++ .../service/handlers/daily_question_delete.go | 59 ++++++++++ .../service/handlers/daily_question_edit.go | 101 +++++++++++++++++ .../handlers/daily_questions_filter_date.go | 104 +++++++++++++++++ .../service/requests/daily_question_create.go | 25 +++++ .../service/requests/daily_question_edit.go | 25 +++++ .../service/requests/daily_question_filter.go | 22 ++++ internal/service/router.go | 17 ++- resources/model_daily_question_del.go | 43 +++++++ .../model_daily_question_del_attributes.go | 10 ++ resources/model_daily_question_details.go | 43 +++++++ ...model_daily_question_details_attributes.go | 30 +++++ resources/model_daily_question_edit.go | 43 +++++++ .../model_daily_question_edit_attributes.go | 22 ++++ resources/model_daily_questions_attributes.go | 2 - resources/model_resource_type.go | 3 + 28 files changed, 997 insertions(+), 8 deletions(-) create mode 100644 docs/spec/components/schemas/DailyQuestionDel.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionDelKey.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionDetails.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionDetailsKey.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionEdit.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionEditKey.yaml create mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml create mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml create mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml create mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml create mode 100644 internal/service/handlers/daily_question_create.go create mode 100644 internal/service/handlers/daily_question_delete.go create mode 100644 internal/service/handlers/daily_question_edit.go create mode 100644 internal/service/handlers/daily_questions_filter_date.go create mode 100644 internal/service/requests/daily_question_create.go create mode 100644 internal/service/requests/daily_question_edit.go create mode 100644 internal/service/requests/daily_question_filter.go create mode 100644 resources/model_daily_question_del.go create mode 100644 resources/model_daily_question_del_attributes.go create mode 100644 resources/model_daily_question_details.go create mode 100644 resources/model_daily_question_details_attributes.go create mode 100644 resources/model_daily_question_edit.go create mode 100644 resources/model_daily_question_edit_attributes.go diff --git a/docs/spec/components/schemas/DailyQuestionDel.yaml b/docs/spec/components/schemas/DailyQuestionDel.yaml new file mode 100644 index 0000000..e43e232 --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionDel.yaml @@ -0,0 +1,15 @@ +allOf: + - $ref: "#/components/schemas/DailyQuestionDelKey" + - type: object + required: + - attributes + properties: + attributes: + type: object + required: + - title + properties: + title: + type: string + description: Question title + example: Georgian capital \ No newline at end of file diff --git a/docs/spec/components/schemas/DailyQuestionDelKey.yaml b/docs/spec/components/schemas/DailyQuestionDelKey.yaml new file mode 100644 index 0000000..cbbff07 --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionDelKey.yaml @@ -0,0 +1,11 @@ +type: object +required: + - id + - type +properties: + id: + type: string + description: Question id + type: + type: string + enum: [ daily_question_del ] diff --git a/docs/spec/components/schemas/DailyQuestionDetails.yaml b/docs/spec/components/schemas/DailyQuestionDetails.yaml new file mode 100644 index 0000000..2a3f8f9 --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionDetails.yaml @@ -0,0 +1,78 @@ +allOf: + - $ref: "#/components/schemas/DailyQuestionDetailsKey" + - type: object + required: + - attributes + properties: + attributes: + type: object + required: + - title + - reward + - options + - correct_answer + - time_for_answer + - starts_at + - created_at + - num_correct_answers + - num_incorrect_answers + - num_all_participants + properties: + title: + type: string + description: Question title + example: Georgian capital + reward: + type: integer + format: int + description: reward for a correct answer + options: + type: array + description: Answer options. Minimum 2, maximum 6 + items: + $ref: "#/components/schemas/DailyQuestionOptions" + example: [ + { + "id": 0, + "title": "" + }, + { + "id": 1, + "title": "" + }, + { + "id": 2, + "title": "" + } + ] + correct_answer: + type: integer + format: int64 + description: right answer index + time_for_answer: + type: integer + format: int64 + description: time for answer + starts_at: + type: integer + format: time.Time + description: start date when this question is available, hours and minutes are always 0 + example: "2024-08-26T00:00:00Z" + created_at: + type: integer + format: time.Time + description: start date when this question was create + num_correct_answers: + type: integer + format: int64 + description: number of correct answers + num_incorrect_answers: + type: integer + format: int64 + description: number of incorrect answers + num_all_participants: + type: integer + format: int64 + description: | + users who received the question, those who answered and + those who did not answer in the time given to them diff --git a/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml new file mode 100644 index 0000000..56014aa --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml @@ -0,0 +1,11 @@ +type: object +required: + - id + - type +properties: + id: + type: string + description: Question id + type: + type: string + enum: [ daily_question_details ] diff --git a/docs/spec/components/schemas/DailyQuestionEdit.yaml b/docs/spec/components/schemas/DailyQuestionEdit.yaml new file mode 100644 index 0000000..c05f4fa --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionEdit.yaml @@ -0,0 +1,56 @@ +allOf: + - $ref: "#/components/schemas/DailyQuestionEditKey" + - type: object + required: + - attributes + properties: + attributes: + type: object + required: + - title + - reward + - options + - correct_answer + - time_for_answer + - starts_at + properties: + title: + type: string + description: Question title + example: Georgian capital + reward: + type: integer + format: int + description: reward for a correct answer + options: + type: array + description: Answer options. Minimum 2, maximum 6 + items: + $ref: "#/components/schemas/DailyQuestionOptions" + example: [ + { + "id": 0, + "title": "" + }, + { + "id": 1, + "title": "" + }, + { + "id": 2, + "title": "" + } + ] + correct_answer: + type: integer + format: int64 + description: right answer index + time_for_answer: + type: integer + format: int64 + description: time for answer + starts_at: + type: integer + format: time.Time + description: start date when this question is available, hours and minutes are always 0 + example: "2024-08-23" \ No newline at end of file diff --git a/docs/spec/components/schemas/DailyQuestionEditKey.yaml b/docs/spec/components/schemas/DailyQuestionEditKey.yaml new file mode 100644 index 0000000..4dff18c --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionEditKey.yaml @@ -0,0 +1,11 @@ +type: object +required: + - id + - type +properties: + id: + type: string + description: Question id + type: + type: string + enum: [ daily_question_edit ] diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml new file mode 100644 index 0000000..485a6f8 --- /dev/null +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml @@ -0,0 +1,39 @@ +post: + tags: + - Daily Questions + summary: Create daily question + description: | + Create Daily Question user must be superuser + operationId: createDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionDetails' + 400: + $ref: '#/components/responses/invalidParameter' + 401: + $ref: '#/components/responses/invalidAuth' + 403: + description: Correct answer option outside the range of answer options + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 409: + description: On this day, the daily question already exists + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 500: + $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml new file mode 100644 index 0000000..6d25aaf --- /dev/null +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml @@ -0,0 +1,31 @@ +delete: + tags: + - Daily Questions + summary: Delete daily question + description: | + Delete Daily Question user must be superuser + operationId: deleteDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionDel' + 400: + $ref: '#/components/responses/invalidParameter' + 404: + description: Question with ID not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 500: + $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml new file mode 100644 index 0000000..685d51e --- /dev/null +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml @@ -0,0 +1,37 @@ +patch: + tags: + - Daily Questions + summary: Edit daily question + description: | + Edit Daily Question user must be superuser + operationId: editDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionEdit' + 400: + $ref: '#/components/responses/invalidParameter' + 409: + description: On this day, the daily question already exists + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 403: + description: Correct answer option outside the range of answer options + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 500: + $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml new file mode 100644 index 0000000..d115fda --- /dev/null +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml @@ -0,0 +1,25 @@ +get: + tags: + - Daily Questions + summary: Filter Daily Question by start + description: | + Filtering of daily questions by their activation time + operationId: filterStartAtDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/DailyQuestionDetails' + 500: + $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/internal/data/daily_questions.go b/internal/data/daily_questions.go index dcbccd2..b0fbb28 100644 --- a/internal/data/daily_questions.go +++ b/internal/data/daily_questions.go @@ -10,6 +10,12 @@ import ( ) const ( + ColDailyQuestionTitle = "title" + ColTimeForAnswer = "time_for_answer" + ColDailyQuestionReward = "reward" + ColAnswerOption = "answer_options" + ColCorrectAnswerId = "correct_answer" + ColCorrectAnswers = "num_correct_answers" ColIncorrectAnswers = "num_incorrect_answers" ColAllParticipants = "num_all_participants" @@ -33,7 +39,7 @@ type DailyQuestionsQ interface { New() DailyQuestionsQ Insert(DailyQuestion) error Update(map[string]any) error - + Delete() (int64, error) Count() (int64, error) Select() ([]DailyQuestion, error) Get() (*DailyQuestion, error) @@ -42,6 +48,7 @@ type DailyQuestionsQ interface { FilterByCreatedAtAfter(date time.Time) DailyQuestionsQ FilterByStartsAtAfter(date time.Time) DailyQuestionsQ FilterByID(ID int64) DailyQuestionsQ + FilterDayQuestions(location *time.Location, day time.Time) DailyQuestionsQ IncrementCorrectAnswer() error IncrementIncorrectAnswer() error diff --git a/internal/data/pg/daily_questions.go b/internal/data/pg/daily_questions.go index 48697b9..47819cb 100644 --- a/internal/data/pg/daily_questions.go +++ b/internal/data/pg/daily_questions.go @@ -18,6 +18,7 @@ type dailyQuestionsQ struct { selector squirrel.SelectBuilder updater squirrel.UpdateBuilder counter squirrel.SelectBuilder + deleter squirrel.DeleteBuilder } func NewDailyQuestionsQ(db *pgdb.DB) data.DailyQuestionsQ { @@ -25,6 +26,7 @@ func NewDailyQuestionsQ(db *pgdb.DB) data.DailyQuestionsQ { db: db, selector: squirrel.Select("*").From(dailyQuestionsTable), updater: squirrel.Update(dailyQuestionsTable), + deleter: squirrel.Delete(dailyQuestionsTable), counter: squirrel.Select("COUNT(*) as count").From(dailyQuestionsTable), } } @@ -58,6 +60,20 @@ func (q *dailyQuestionsQ) Update(fields map[string]any) error { return nil } +func (q *dailyQuestionsQ) Delete() (int64, error) { + res, err := q.db.ExecWithResult(q.deleter) + if err != nil { + return 0, fmt.Errorf("delete daily question: %w", err) + } + + rows, err := res.RowsAffected() + if err != nil { + return 0, fmt.Errorf("count rows affected: %w", err) + } + + return rows, nil +} + func (q *dailyQuestionsQ) Count() (int64, error) { res := struct { Count int64 `db:"count"` @@ -112,6 +128,17 @@ func (q *dailyQuestionsQ) FilterTodayQuestions(offset int) data.DailyQuestionsQ }) } +func (q *dailyQuestionsQ) FilterDayQuestions(location *time.Location, day time.Time) data.DailyQuestionsQ { + dayInLocation := day.In(location) + dayStart := time.Date(dayInLocation.Year(), dayInLocation.Month(), dayInLocation.Day(), 0, 0, 0, 0, location).UTC() + dayEnd := dayStart.Add(24 * time.Hour).Add(-time.Nanosecond).UTC() + + return q.applyCondition(squirrel.And{ + squirrel.GtOrEq{"starts_at": dayStart}, + squirrel.LtOrEq{"starts_at": dayEnd}, + }) +} + func (q *dailyQuestionsQ) FilterByID(ID int64) data.DailyQuestionsQ { return q.applyCondition(squirrel.Eq{"id": ID}) } @@ -145,6 +172,7 @@ func (q *dailyQuestionsQ) IncrementAllParticipants() error { func (q *dailyQuestionsQ) applyCondition(cond squirrel.Sqlizer) data.DailyQuestionsQ { q.selector = q.selector.Where(cond) q.updater = q.updater.Where(cond) + q.deleter = q.deleter.Where(cond) q.counter = q.counter.Where(cond) return q } diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go new file mode 100644 index 0000000..ee808f8 --- /dev/null +++ b/internal/service/handlers/daily_question_create.go @@ -0,0 +1,105 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + + "github.com/rarimo/geo-points-svc/internal/data" + "github.com/rarimo/geo-points-svc/internal/service/requests" + "github.com/rarimo/geo-points-svc/resources" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { + req, err := requests.NewDailyQuestion(r) + if err != nil { + Log(r).Errorf("Error get request NewDailyQuestion: %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + location := DailyQuestions(r).Location + question, err := DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() + if question != nil { + Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) + ape.RenderErr(w, problems.Conflict()) + return + } + if err != nil { + Log(r).Errorf("Error on this day %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + answerOptions, err := json.Marshal(req.Options) + if err != nil { + Log(r).Errorf("Error marshalling answer options: %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + correctAnswerFound := false + for _, option := range req.Options { + if option.Id == int(req.CorrectAnswer) { + correctAnswerFound = true + break + } + } + if !correctAnswerFound { + Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) + ape.RenderErr(w, problems.Forbidden()) + return + } + + stmt := data.DailyQuestion{ + Title: req.Title, + TimeForAnswer: req.TimeForAnswer, + Reward: int64(req.Reward), + AnswerOptions: answerOptions, + CorrectAnswer: req.CorrectAnswer, + StartsAt: req.StartsAt, + } + + err = DailyQuestionsQ(r).Insert(stmt) + if err != nil { + Log(r).Errorf("Error ger request NewDailyQuestion: %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + question, err = DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() + if question == nil { + Log(r).Errorf("Error on this day %v, create question '%v'", req.StartsAt, req.Title) + ape.RenderErr(w, problems.InternalError()) + return + } + if err != nil { + Log(r).Errorf("Error on this day %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + ape.Render(w, NewDailyQuestionCrate(&stmt, req.Options, question.ID)) +} + +func NewDailyQuestionCrate(q *data.DailyQuestion, options []resources.DailyQuestionOptions, ID int64) resources.DailyQuestionDetailsResponse { + return resources.DailyQuestionDetailsResponse{ + Data: resources.DailyQuestionDetails{ + Key: resources.Key{ + ID: strconv.Itoa(int(ID)), + Type: resources.DAILY_QUESTIONS, + }, + Attributes: resources.DailyQuestionDetailsAttributes{ + CorrectAnswer: q.CorrectAnswer, + Options: options, + Reward: int(q.Reward), + StartsAt: q.StartsAt, + TimeForAnswer: q.TimeForAnswer, + Title: q.Title, + }, + }, + } + +} diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go new file mode 100644 index 0000000..69881b0 --- /dev/null +++ b/internal/service/handlers/daily_question_delete.go @@ -0,0 +1,59 @@ +package handlers + +import ( + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi" + "github.com/rarimo/geo-points-svc/resources" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { + IDStr := strings.ToLower(chi.URLParam(r, "ID")) + ID, err := strconv.ParseInt(IDStr, 10, 64) + Log(r).Infof("ID: %v", ID) + if err != nil { + Log(r).WithError(err).Error("failed to parse ID") + ape.RenderErr(w, problems.InternalError()) + return + } + + question, err := DailyQuestionsQ(r).FilterByID(ID).Get() + if err != nil { + Log(r).WithError(err).Error("Error getting question") + ape.RenderErr(w, problems.InternalError()) + return + } + if question == nil { + Log(r).Warnf("Question with ID %d not found", ID) + ape.RenderErr(w, problems.NotFound()) + return + } + title := question.Title + + _, err = DailyQuestionsQ(r).New().FilterByID(ID).Delete() + if err != nil { + Log(r).WithError(err).Error("Error deleting daily question") + ape.RenderErr(w, problems.InternalError()) + return + } + + ape.Render(w, NewDailyQuestionDelete(ID, title)) +} + +func NewDailyQuestionDelete(ID int64, title string) resources.DailyQuestionDelResponse { + return resources.DailyQuestionDelResponse{ + Data: resources.DailyQuestionDel{ + Key: resources.Key{ + ID: strconv.Itoa(int(ID)), + Type: resources.DAILY_QUESTION_DEL, + }, + Attributes: resources.DailyQuestionDelAttributes{ + Title: title, + }, + }, + } +} diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go new file mode 100644 index 0000000..a084002 --- /dev/null +++ b/internal/service/handlers/daily_question_edit.go @@ -0,0 +1,101 @@ +package handlers + +import ( + "encoding/json" + "net/http" + "strconv" + "strings" + + "github.com/go-chi/chi" + "github.com/rarimo/geo-points-svc/internal/data" + "github.com/rarimo/geo-points-svc/internal/service/requests" + "github.com/rarimo/geo-points-svc/resources" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { + IDStr := strings.ToLower(chi.URLParam(r, "ID")) + ID, err := strconv.ParseInt(IDStr, 10, 64) + if err != nil { + Log(r).WithError(err).Error("failed to parse ID") + ape.RenderErr(w, problems.InternalError()) + return + } + + req, err := requests.NewDailyQuestionEdit(r) + if err != nil { + Log(r).WithError(err).Error("Error creating daily question edit request") + ape.RenderErr(w, problems.InternalError()) + return + } + + location := DailyQuestions(r).Location + question, err := DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() + if question != nil && ID != question.ID { + Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) + ape.RenderErr(w, problems.Conflict()) + return + } + if err != nil { + Log(r).Errorf("Error on this day %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + answerOptions, err := json.Marshal(req.Options) + if err != nil { + Log(r).Errorf("Error marshalling answer options: %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + + correctAnswerFound := false + for _, option := range req.Options { + if option.Id == int(req.CorrectAnswer) { + correctAnswerFound = true + break + } + } + if !correctAnswerFound { + Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) + ape.RenderErr(w, problems.Forbidden()) + return + } + + err = DailyQuestionsQ(r).FilterByID(ID).Update(map[string]any{ + data.ColDailyQuestionTitle: req.Title, + data.ColTimeForAnswer: req.TimeForAnswer, + data.ColDailyQuestionReward: req.Reward, + data.ColAnswerOption: answerOptions, + data.ColCorrectAnswerId: req.CorrectAnswer, + }) + + if err != nil { + Log(r).WithError(err).Error("Error editing daily question") + ape.RenderErr(w, problems.InternalError()) + return + } + + ape.Render(w, NewDailyQuestionEdite(ID, req)) +} + +func NewDailyQuestionEdite(ID int64, req resources.DailyQuestionEditAttributes) resources.DailyQuestionEditResponse { + return resources.DailyQuestionEditResponse{ + Data: resources.DailyQuestionEdit{ + Key: resources.Key{ + ID: strconv.Itoa(int(ID)), + Type: resources.DAILY_QUESTION_EDIT, + }, + Attributes: resources.DailyQuestionEditAttributes{ + CorrectAnswer: req.CorrectAnswer, + Options: req.Options, + Reward: req.Reward, + StartsAt: req.StartsAt, + TimeForAnswer: req.TimeForAnswer, + Title: req.Title, + }, + }, + } + +} diff --git a/internal/service/handlers/daily_questions_filter_date.go b/internal/service/handlers/daily_questions_filter_date.go new file mode 100644 index 0000000..e7935ba --- /dev/null +++ b/internal/service/handlers/daily_questions_filter_date.go @@ -0,0 +1,104 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/go-chi/chi" + "github.com/rarimo/geo-points-svc/internal/data" + "github.com/rarimo/geo-points-svc/internal/service/requests" + "github.com/rarimo/geo-points-svc/resources" + "gitlab.com/distributed_lab/ape" + "gitlab.com/distributed_lab/ape/problems" +) + +func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { + dateStr := strings.ToLower(chi.URLParam(r, "date")) + req, err := requests.NewFilterStartAtDailyQuestions(r) + if err != nil { + Log(r).WithError(err).Error("error creating filter start at daily questions request") + ape.RenderErr(w, problems.InternalError()) + } + + date, err := time.Parse("2006-01-02", dateStr) + if err != nil { + Log(r).WithError(err).Error("Invalid date format, expected YYYY-MM-DD") + ape.RenderErr(w, problems.InternalError()) + return + } + + res, err := DailyQuestionsQ(r).FilterByStartsAtAfter(date).Select() + if err != nil { + Log(r).WithError(err).Error("Error filtering questions") + ape.RenderErr(w, problems.InternalError()) + return + } + + resp, err := NewDailyQuestionsFilterDate(res) + if err != nil { + Log(r).WithError(err).Error("Error filtering questions") + ape.RenderErr(w, problems.InternalError()) + return + } + + resp.Links = req.GetLinks(r) + if req.Count { + QuestionListCount, err := DailyQuestionsQ(r).FilterByStartsAtAfter(date).Count() + if err != nil { + Log(r).WithError(err).Error("Failed to count balances") + ape.RenderErr(w, problems.InternalError()) + return + } + + _ = resp.PutMeta(struct { + QuestionCount int64 `json:"question_count"` + }{QuestionListCount}) + } + ape.Render(w, resp) +} + +func NewDailyQuestionModel(question data.DailyQuestion) (resources.DailyQuestionDetails, error) { + var options []resources.DailyQuestionOptions + + err := json.Unmarshal(question.AnswerOptions, &options) + if err != nil { + err := fmt.Errorf("failed to unmarshal AnswerOptions: %v", err) + return resources.DailyQuestionDetails{}, err + } + + return resources.DailyQuestionDetails{ + Key: resources.Key{ + ID: strconv.Itoa(int(question.ID)), + Type: resources.DAILY_QUESTION_DETAILS, + }, + Attributes: resources.DailyQuestionDetailsAttributes{ + CorrectAnswer: question.CorrectAnswer, + CreatedAt: question.CreatedAt, + NumAllParticipants: question.NumAllParticipants, + NumCorrectAnswers: question.NumCorrectAnswers, + NumIncorrectAnswers: question.NumIncorrectAnswers, + Options: options, + Reward: int(question.Reward), + StartsAt: question.StartsAt, + TimeForAnswer: question.TimeForAnswer, + Title: question.Title, + }, + }, nil +} + +func NewDailyQuestionsFilterDate(questions []data.DailyQuestion) (resources.DailyQuestionDetailsListResponse, error) { + list := make([]resources.DailyQuestionDetails, len(questions)) + for i, q := range questions { + qModel, err := NewDailyQuestionModel(q) + if err != nil { + return resources.DailyQuestionDetailsListResponse{}, fmt.Errorf("error make daily question model, %s", err) + } + list[i] = qModel + } + + return resources.DailyQuestionDetailsListResponse{Data: list}, nil +} diff --git a/internal/service/requests/daily_question_create.go b/internal/service/requests/daily_question_create.go new file mode 100644 index 0000000..2e0cad9 --- /dev/null +++ b/internal/service/requests/daily_question_create.go @@ -0,0 +1,25 @@ +package requests + +import ( + "encoding/json" + "net/http" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/rarimo/geo-points-svc/resources" +) + +func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionDetailsAttributes, err error) { + if err = json.NewDecoder(r.Body).Decode(&req); err != nil { + err = newDecodeError("body", err) + return + } + + return req, validation.Errors{ + "time_for_answer": validation.Validate(&req.TimeForAnswer, validation.Required), + "correct_answer": validation.Validate(&req.CorrectAnswer, validation.Required), + "starts_at": validation.Validate(&req.StartsAt, validation.Required), + "options": validation.Validate(&req.Options, validation.Required), + "reward": validation.Validate(&req.Reward, validation.Required), + "title": validation.Validate(&req.Title, validation.Required), + }.Filter() +} diff --git a/internal/service/requests/daily_question_edit.go b/internal/service/requests/daily_question_edit.go new file mode 100644 index 0000000..cfa9439 --- /dev/null +++ b/internal/service/requests/daily_question_edit.go @@ -0,0 +1,25 @@ +package requests + +import ( + "encoding/json" + "net/http" + + validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/rarimo/geo-points-svc/resources" +) + +func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditAttributes, err error) { + if err = json.NewDecoder(r.Body).Decode(&req); err != nil { + err = newDecodeError("body", err) + return + } + + return req, validation.Errors{ + "time_for_answer": validation.Validate(&req.TimeForAnswer, validation.Required), + "correct_answer": validation.Validate(&req.CorrectAnswer, validation.Required), + "starts_at": validation.Validate(&req.StartsAt, validation.Required), + "options": validation.Validate(&req.Options, validation.Required), + "reward": validation.Validate(&req.Reward, validation.Required), + "title": validation.Validate(&req.Title, validation.Required), + }.Filter() +} diff --git a/internal/service/requests/daily_question_filter.go b/internal/service/requests/daily_question_filter.go new file mode 100644 index 0000000..3602bda --- /dev/null +++ b/internal/service/requests/daily_question_filter.go @@ -0,0 +1,22 @@ +package requests + +import ( + "net/http" + + "github.com/rarimo/geo-points-svc/internal/service/page" + "gitlab.com/distributed_lab/urlval/v4" +) + +type QuestionList struct { + page.OffsetParams + Count bool `url:"count"` +} + +func NewFilterStartAtDailyQuestions(r *http.Request) (req QuestionList, err error) { + if err = urlval.Decode(r.URL.Query(), &req); err != nil { + err = newDecodeError("query", err) + return + } + + return req, req.Validate() +} diff --git a/internal/service/router.go b/internal/service/router.go index 6b49672..ec509db 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -45,11 +45,18 @@ func Run(ctx context.Context, cfg config.Config) { }) }) }) - r.Route("/daily_questions/{nullifier}", func(r chi.Router) { - r.Use(authMW) - r.Get("/status", handlers.GetDailyQuestionsStatus) - r.Get("/", handlers.GetDailyQuestion) - r.Post("/", handlers.CheckDailyQuestion) + + r.Route("/daily_questions", func(r chi.Router) { + r.Post("/create", handlers.CreateDailyQuestion) + r.Delete("/delete/{ID}", handlers.DeleteDailyQuestion) + r.Patch("/edit/{ID}", handlers.EditDailyQuestion) + r.Get("/filter_start_at/{date}", handlers.FilterStartAtDailyQuestions) + r.Route("/{nullifier}", func(r chi.Router) { + r.Use(authMW) + r.Get("/status", handlers.GetDailyQuestionsStatus) + r.Get("/", handlers.GetDailyQuestion) + r.Post("/", handlers.CheckDailyQuestion) + }) }) r.Route("/events", func(r chi.Router) { r.Use(authMW) diff --git a/resources/model_daily_question_del.go b/resources/model_daily_question_del.go new file mode 100644 index 0000000..90542d3 --- /dev/null +++ b/resources/model_daily_question_del.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type DailyQuestionDel struct { + Key + Attributes DailyQuestionDelAttributes `json:"attributes"` +} +type DailyQuestionDelResponse struct { + Data DailyQuestionDel `json:"data"` + Included Included `json:"included"` +} + +type DailyQuestionDelListResponse struct { + Data []DailyQuestionDel `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *DailyQuestionDelListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *DailyQuestionDelListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustDailyQuestionDel - returns DailyQuestionDel from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustDailyQuestionDel(key Key) *DailyQuestionDel { + var dailyQuestionDel DailyQuestionDel + if c.tryFindEntry(key, &dailyQuestionDel) { + return &dailyQuestionDel + } + return nil +} diff --git a/resources/model_daily_question_del_attributes.go b/resources/model_daily_question_del_attributes.go new file mode 100644 index 0000000..3d50eed --- /dev/null +++ b/resources/model_daily_question_del_attributes.go @@ -0,0 +1,10 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +type DailyQuestionDelAttributes struct { + // Question title + Title string `json:"title"` +} diff --git a/resources/model_daily_question_details.go b/resources/model_daily_question_details.go new file mode 100644 index 0000000..11f9d7b --- /dev/null +++ b/resources/model_daily_question_details.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type DailyQuestionDetails struct { + Key + Attributes DailyQuestionDetailsAttributes `json:"attributes"` +} +type DailyQuestionDetailsResponse struct { + Data DailyQuestionDetails `json:"data"` + Included Included `json:"included"` +} + +type DailyQuestionDetailsListResponse struct { + Data []DailyQuestionDetails `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *DailyQuestionDetailsListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *DailyQuestionDetailsListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustDailyQuestionDetails - returns DailyQuestionDetails from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustDailyQuestionDetails(key Key) *DailyQuestionDetails { + var dailyQuestionDetails DailyQuestionDetails + if c.tryFindEntry(key, &dailyQuestionDetails) { + return &dailyQuestionDetails + } + return nil +} diff --git a/resources/model_daily_question_details_attributes.go b/resources/model_daily_question_details_attributes.go new file mode 100644 index 0000000..b411725 --- /dev/null +++ b/resources/model_daily_question_details_attributes.go @@ -0,0 +1,30 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "time" + +type DailyQuestionDetailsAttributes struct { + // right answer index + CorrectAnswer int64 `json:"correct_answer"` + // start date when this question was create + CreatedAt time.Time `json:"created_at"` + // users who received the question, those who answered and those who did not answer in the time given to them + NumAllParticipants int64 `json:"num_all_participants"` + // number of correct answers + NumCorrectAnswers int64 `json:"num_correct_answers"` + // number of incorrect answers + NumIncorrectAnswers int64 `json:"num_incorrect_answers"` + // Answer options. Minimum 2, maximum 6 + Options []DailyQuestionOptions `json:"options"` + // reward for a correct answer + Reward int `json:"reward"` + // start date when this question is available, hours and minutes are always 0 + StartsAt time.Time `json:"starts_at"` + // time for answer + TimeForAnswer int64 `json:"time_for_answer"` + // Question title + Title string `json:"title"` +} diff --git a/resources/model_daily_question_edit.go b/resources/model_daily_question_edit.go new file mode 100644 index 0000000..8ef533a --- /dev/null +++ b/resources/model_daily_question_edit.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type DailyQuestionEdit struct { + Key + Attributes DailyQuestionEditAttributes `json:"attributes"` +} +type DailyQuestionEditResponse struct { + Data DailyQuestionEdit `json:"data"` + Included Included `json:"included"` +} + +type DailyQuestionEditListResponse struct { + Data []DailyQuestionEdit `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *DailyQuestionEditListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *DailyQuestionEditListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustDailyQuestionEdit - returns DailyQuestionEdit from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustDailyQuestionEdit(key Key) *DailyQuestionEdit { + var dailyQuestionEdit DailyQuestionEdit + if c.tryFindEntry(key, &dailyQuestionEdit) { + return &dailyQuestionEdit + } + return nil +} diff --git a/resources/model_daily_question_edit_attributes.go b/resources/model_daily_question_edit_attributes.go new file mode 100644 index 0000000..9740a47 --- /dev/null +++ b/resources/model_daily_question_edit_attributes.go @@ -0,0 +1,22 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "time" + +type DailyQuestionEditAttributes struct { + // right answer index + CorrectAnswer int64 `json:"correct_answer"` + // Answer options. Minimum 2, maximum 6 + Options []DailyQuestionOptions `json:"options"` + // reward for a correct answer + Reward int `json:"reward"` + // start date when this question is available, hours and minutes are always 0 + StartsAt time.Time `json:"starts_at"` + // time for answer + TimeForAnswer int64 `json:"time_for_answer"` + // Question title + Title string `json:"title"` +} 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 diff --git a/resources/model_resource_type.go b/resources/model_resource_type.go index 339c618..a89c7ee 100644 --- a/resources/model_resource_type.go +++ b/resources/model_resource_type.go @@ -12,6 +12,9 @@ const ( BALANCE ResourceType = "balance" CLAIM_EVENT ResourceType = "claim_event" CREATE_BALANCE ResourceType = "create_balance" + DAILY_QUESTION_DEL ResourceType = "daily_question_del" + DAILY_QUESTION_DETAILS ResourceType = "daily_question_details" + DAILY_QUESTION_EDIT ResourceType = "daily_question_edit" DAILY_QUESTIONS ResourceType = "daily_questions" DAILY_QUESTIONS_STATUS ResourceType = "daily_questions_status" EVENT_CLAIMING_STATE ResourceType = "event_claiming_state" From c2f7ea7c0f92dfd23945c0ad5cd5506869de0d3d Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 12:37:24 +0300 Subject: [PATCH 04/29] update docs --- .../schemas/DailyQuestionDetails.yaml | 25 +++++++------- .../components/schemas/DailyQuestionEdit.yaml | 13 ++++--- ...points-svc@v1@public@daily_questions.yaml} | 26 ++++++++++++++ ...v1@public@daily_questions@delete@{ID}.yaml | 31 ----------------- ...aily_questions@filter_start_at@{date}.yaml | 25 -------------- ...public@daily_questions@{question_id}.yaml} | 34 ++++++++++++++++++- 6 files changed, 77 insertions(+), 77 deletions(-) rename docs/spec/paths/{integrations@geo-points-svc@v1@public@daily_questions@create.yaml => integrations@geo-points-svc@v1@public@daily_questions.yaml} (62%) delete mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml delete mode 100644 docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml rename docs/spec/paths/{integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml => integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml} (52%) diff --git a/docs/spec/components/schemas/DailyQuestionDetails.yaml b/docs/spec/components/schemas/DailyQuestionDetails.yaml index 2a3f8f9..628b8ed 100644 --- a/docs/spec/components/schemas/DailyQuestionDetails.yaml +++ b/docs/spec/components/schemas/DailyQuestionDetails.yaml @@ -24,8 +24,8 @@ allOf: example: Georgian capital reward: type: integer - format: int - description: reward for a correct answer + format: int64 + description: Reward for a correct answer options: type: array description: Answer options. Minimum 2, maximum 6 @@ -48,31 +48,30 @@ allOf: correct_answer: type: integer format: int64 - description: right answer index + description: Correct answer ID time_for_answer: type: integer format: int64 - description: time for answer + description: Time for answer starts_at: - type: integer - format: time.Time - description: start date when this question is available, hours and minutes are always 0 + type: string + description: Start date when this question is available, hours and minutes are always 0 example: "2024-08-26T00:00:00Z" created_at: - type: integer - format: time.Time - description: start date when this question was create + type: string + description: Start date when this question was create + example: "2024-08-26T00:00:00Z" num_correct_answers: type: integer format: int64 - description: number of correct answers + description: Number of correct answers num_incorrect_answers: type: integer format: int64 - description: number of incorrect answers + description: Number of incorrect answers num_all_participants: type: integer format: int64 description: | - users who received the question, those who answered and + Users who received the question, those who answered and those who did not answer in the time given to them diff --git a/docs/spec/components/schemas/DailyQuestionEdit.yaml b/docs/spec/components/schemas/DailyQuestionEdit.yaml index c05f4fa..325ca9d 100644 --- a/docs/spec/components/schemas/DailyQuestionEdit.yaml +++ b/docs/spec/components/schemas/DailyQuestionEdit.yaml @@ -20,8 +20,8 @@ allOf: example: Georgian capital reward: type: integer - format: int - description: reward for a correct answer + format: int64 + description: Reward for a correct answer options: type: array description: Answer options. Minimum 2, maximum 6 @@ -44,13 +44,12 @@ allOf: correct_answer: type: integer format: int64 - description: right answer index + description: Correct answer ID time_for_answer: type: integer format: int64 - description: time for answer + description: Time for answer starts_at: - type: integer - format: time.Time - description: start date when this question is available, hours and minutes are always 0 + type: string + description: Start date when this question is available, hours and minutes are always 0 example: "2024-08-23" \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml similarity index 62% rename from docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml rename to docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml index 485a6f8..ad271ae 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@create.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml @@ -35,5 +35,31 @@ post: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' + 500: + $ref: '#/components/responses/internalError' + +get: + tags: + - Daily Questions + summary: Filter Daily Question by start + description: | + Filtering of daily questions by their activation time + operationId: filterStartAtDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + type: array + items: + $ref: '#/components/schemas/DailyQuestionDetails' 500: $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml deleted file mode 100644 index 6d25aaf..0000000 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@delete@{ID}.yaml +++ /dev/null @@ -1,31 +0,0 @@ -delete: - tags: - - Daily Questions - summary: Delete daily question - description: | - Delete Daily Question user must be superuser - operationId: deleteDailyQuestion - security: - - BearerAuth: [] - responses: - 200: - description: Success - content: - application/vnd.api+json: - schema: - type: object - required: - - data - properties: - data: - $ref: '#/components/schemas/DailyQuestionDel' - 400: - $ref: '#/components/responses/invalidParameter' - 404: - description: Question with ID not found - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/Errors' - 500: - $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml deleted file mode 100644 index d115fda..0000000 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@filter_start_at@{date}.yaml +++ /dev/null @@ -1,25 +0,0 @@ -get: - tags: - - Daily Questions - summary: Filter Daily Question by start - description: | - Filtering of daily questions by their activation time - operationId: filterStartAtDailyQuestion - security: - - BearerAuth: [] - responses: - 200: - description: Success - content: - application/vnd.api+json: - schema: - type: object - required: - - data - properties: - data: - type: array - items: - $ref: '#/components/schemas/DailyQuestionDetails' - 500: - $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml similarity index 52% rename from docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml rename to docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml index 685d51e..5da3eea 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@edit@{ID}.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml @@ -1,3 +1,35 @@ +delete: + tags: + - Daily Questions + summary: Delete daily question + description: | + Delete Daily Question user must be superuser + operationId: deleteDailyQuestion + security: + - BearerAuth: [] + responses: + 200: + description: Success + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionDetails' + 400: + $ref: '#/components/responses/invalidParameter' + 404: + description: Question with ID not found + content: + application/vnd.api+json: + schema: + $ref: '#/components/schemas/Errors' + 500: + $ref: '#/components/responses/internalError' + patch: tags: - Daily Questions @@ -18,7 +50,7 @@ patch: - data properties: data: - $ref: '#/components/schemas/DailyQuestionEdit' + $ref: '#/components/schemas/DailyQuestionDetails' 400: $ref: '#/components/responses/invalidParameter' 409: From b685b5160c0a8711cbf7e962e4bb2282e9ad9d6d Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 13:11:09 +0300 Subject: [PATCH 05/29] update docs add requests fo dq admin --- ...tions@geo-points-svc@v1@public@daily_questions.yaml | 10 ++++++++++ ...ts-svc@v1@public@daily_questions@{question_id}.yaml | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml index ad271ae..32b2a0d 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml @@ -7,6 +7,16 @@ post: operationId: createDailyQuestion security: - BearerAuth: [] + requestBody: + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionEdit' responses: 200: description: Success diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml index 5da3eea..5b6a09b 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml @@ -39,6 +39,16 @@ patch: operationId: editDailyQuestion security: - BearerAuth: [] + requestBody: + content: + application/vnd.api+json: + schema: + type: object + required: + - data + properties: + data: + $ref: '#/components/schemas/DailyQuestionEdit' responses: 200: description: Success From ba1f9507685c1d6e01e6adf8f8bed7d77db22563 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 13:29:47 +0300 Subject: [PATCH 06/29] update docs add requests fo dq admin 2 --- docs/spec/components/schemas/DailyQuestionDelKey.yaml | 2 +- docs/spec/components/schemas/DailyQuestionDetailsKey.yaml | 2 +- ...eo-points-svc@v1@public@daily_questions@{question_id}.yaml | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/spec/components/schemas/DailyQuestionDelKey.yaml b/docs/spec/components/schemas/DailyQuestionDelKey.yaml index cbbff07..9049e40 100644 --- a/docs/spec/components/schemas/DailyQuestionDelKey.yaml +++ b/docs/spec/components/schemas/DailyQuestionDelKey.yaml @@ -8,4 +8,4 @@ properties: description: Question id type: type: string - enum: [ daily_question_del ] + enum: [ daily_question ] diff --git a/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml index 56014aa..9049e40 100644 --- a/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml +++ b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml @@ -8,4 +8,4 @@ properties: description: Question id type: type: string - enum: [ daily_question_details ] + enum: [ daily_question ] diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml index 5b6a09b..561809e 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml @@ -19,6 +19,8 @@ delete: properties: data: $ref: '#/components/schemas/DailyQuestionDetails' + 204: + description: No content 400: $ref: '#/components/responses/invalidParameter' 404: @@ -50,6 +52,8 @@ patch: data: $ref: '#/components/schemas/DailyQuestionEdit' responses: + 204: + description: No content 200: description: Success content: From a7dc1f5ea0d721698d830f088840704aedbc2faf Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 13:34:46 +0300 Subject: [PATCH 07/29] update docs add requests fo dq admin 3 --- docs/spec/components/schemas/DailyQuestionDelKey.yaml | 2 +- docs/spec/components/schemas/DailyQuestionDetailsKey.yaml | 2 +- docs/spec/components/schemas/DailyQuestionEditKey.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/spec/components/schemas/DailyQuestionDelKey.yaml b/docs/spec/components/schemas/DailyQuestionDelKey.yaml index 9049e40..9a4e05f 100644 --- a/docs/spec/components/schemas/DailyQuestionDelKey.yaml +++ b/docs/spec/components/schemas/DailyQuestionDelKey.yaml @@ -8,4 +8,4 @@ properties: description: Question id type: type: string - enum: [ daily_question ] + enum: [ daily_questions ] diff --git a/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml index 9049e40..9a4e05f 100644 --- a/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml +++ b/docs/spec/components/schemas/DailyQuestionDetailsKey.yaml @@ -8,4 +8,4 @@ properties: description: Question id type: type: string - enum: [ daily_question ] + enum: [ daily_questions ] diff --git a/docs/spec/components/schemas/DailyQuestionEditKey.yaml b/docs/spec/components/schemas/DailyQuestionEditKey.yaml index 4dff18c..9a4e05f 100644 --- a/docs/spec/components/schemas/DailyQuestionEditKey.yaml +++ b/docs/spec/components/schemas/DailyQuestionEditKey.yaml @@ -8,4 +8,4 @@ properties: description: Question id type: type: string - enum: [ daily_question_edit ] + enum: [ daily_questions ] From 26b01627b02a2c03d8d4d6f4e3ac0889bad69393 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 14:19:06 +0300 Subject: [PATCH 08/29] update docs add admin route for dq --- ...egrations@geo-points-svc@v1@public@daily_questions@admin.yaml} | 0 ...points-svc@v1@public@daily_questions@admin@{question_id}.yaml} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename docs/spec/paths/{integrations@geo-points-svc@v1@public@daily_questions.yaml => integrations@geo-points-svc@v1@public@daily_questions@admin.yaml} (100%) rename docs/spec/paths/{integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml => integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml} (100%) diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml similarity index 100% rename from docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions.yaml rename to docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml similarity index 100% rename from docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@{question_id}.yaml rename to docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml From 1ec0a37a305e25c3912eb2e57118b083aaf2d444 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 16:12:57 +0300 Subject: [PATCH 09/29] DQ admin fix --- .../schemas/DailyQuestionCreate.yaml | 55 ++++++ .../schemas/DailyQuestionCreateKey.yaml | 11 ++ .../components/schemas/DailyQuestionEdit.yaml | 7 - ...s-svc@v1@public@daily_questions@admin.yaml | 2 +- internal/data/daily_questions.go | 23 ++- internal/data/pg/daily_questions.go | 57 ++++-- .../service/handlers/daily_question_create.go | 66 ++++--- .../service/handlers/daily_question_delete.go | 60 ++++-- .../service/handlers/daily_question_edit.go | 175 +++++++++++++----- ...lter_date.go => daily_questions_select.go} | 38 ++-- .../service/requests/daily_question_create.go | 14 +- .../service/requests/daily_question_edit.go | 12 +- internal/service/router.go | 12 +- resources/model_daily_question_create.go | 43 +++++ .../model_daily_question_create_attributes.go | 20 ++ ...model_daily_question_details_attributes.go | 24 ++- .../model_daily_question_edit_attributes.go | 22 +-- resources/model_resource_type.go | 3 - 18 files changed, 450 insertions(+), 194 deletions(-) create mode 100644 docs/spec/components/schemas/DailyQuestionCreate.yaml create mode 100644 docs/spec/components/schemas/DailyQuestionCreateKey.yaml rename internal/service/handlers/{daily_questions_filter_date.go => daily_questions_select.go} (73%) create mode 100644 resources/model_daily_question_create.go create mode 100644 resources/model_daily_question_create_attributes.go diff --git a/docs/spec/components/schemas/DailyQuestionCreate.yaml b/docs/spec/components/schemas/DailyQuestionCreate.yaml new file mode 100644 index 0000000..325ca9d --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionCreate.yaml @@ -0,0 +1,55 @@ +allOf: + - $ref: "#/components/schemas/DailyQuestionEditKey" + - type: object + required: + - attributes + properties: + attributes: + type: object + required: + - title + - reward + - options + - correct_answer + - time_for_answer + - starts_at + properties: + title: + type: string + description: Question title + example: Georgian capital + reward: + type: integer + format: int64 + description: Reward for a correct answer + options: + type: array + description: Answer options. Minimum 2, maximum 6 + items: + $ref: "#/components/schemas/DailyQuestionOptions" + example: [ + { + "id": 0, + "title": "" + }, + { + "id": 1, + "title": "" + }, + { + "id": 2, + "title": "" + } + ] + correct_answer: + type: integer + format: int64 + description: Correct answer ID + time_for_answer: + type: integer + format: int64 + description: Time for answer + starts_at: + type: string + description: Start date when this question is available, hours and minutes are always 0 + example: "2024-08-23" \ No newline at end of file diff --git a/docs/spec/components/schemas/DailyQuestionCreateKey.yaml b/docs/spec/components/schemas/DailyQuestionCreateKey.yaml new file mode 100644 index 0000000..9a4e05f --- /dev/null +++ b/docs/spec/components/schemas/DailyQuestionCreateKey.yaml @@ -0,0 +1,11 @@ +type: object +required: + - id + - type +properties: + id: + type: string + description: Question id + type: + type: string + enum: [ daily_questions ] diff --git a/docs/spec/components/schemas/DailyQuestionEdit.yaml b/docs/spec/components/schemas/DailyQuestionEdit.yaml index 325ca9d..19d3363 100644 --- a/docs/spec/components/schemas/DailyQuestionEdit.yaml +++ b/docs/spec/components/schemas/DailyQuestionEdit.yaml @@ -6,13 +6,6 @@ allOf: properties: attributes: type: object - required: - - title - - reward - - options - - correct_answer - - time_for_answer - - starts_at properties: title: type: string diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml index 32b2a0d..2302e91 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml @@ -16,7 +16,7 @@ post: - data properties: data: - $ref: '#/components/schemas/DailyQuestionEdit' + $ref: '#/components/schemas/DailyQuestionCreate' responses: 200: description: Success diff --git a/internal/data/daily_questions.go b/internal/data/daily_questions.go index b0fbb28..3bfa6fe 100644 --- a/internal/data/daily_questions.go +++ b/internal/data/daily_questions.go @@ -7,18 +7,19 @@ import ( "time" "github.com/rarimo/geo-points-svc/resources" + "gitlab.com/distributed_lab/kit/pgdb" ) const ( - ColDailyQuestionTitle = "title" - ColTimeForAnswer = "time_for_answer" - ColDailyQuestionReward = "reward" - ColAnswerOption = "answer_options" - ColCorrectAnswerId = "correct_answer" - - ColCorrectAnswers = "num_correct_answers" - ColIncorrectAnswers = "num_incorrect_answers" - ColAllParticipants = "num_all_participants" + ColDailyQuestionTitle = "title" + ColTimeForAnswer = "time_for_answer" + ColAnswerOption = "answer_options" + ColCorrectAnswerId = "correct_answer" + ColReward = "reward" + ColStartAt = "start_at" + ColCorrectAnswers = "num_correct_answers" + ColIncorrectAnswers = "num_incorrect_answers" + ColAllParticipants = "num_all_participants" ) type DailyQuestion struct { @@ -42,8 +43,10 @@ type DailyQuestionsQ interface { Delete() (int64, error) Count() (int64, error) Select() ([]DailyQuestion, error) - Get() (*DailyQuestion, error) + SelectByTime() ([]DailyQuestion, error) + Get() (*DailyQuestion, error) + Page(*pgdb.OffsetPageParams) DailyQuestionsQ FilterTodayQuestions(offset int) DailyQuestionsQ FilterByCreatedAtAfter(date time.Time) DailyQuestionsQ FilterByStartsAtAfter(date time.Time) DailyQuestionsQ diff --git a/internal/data/pg/daily_questions.go b/internal/data/pg/daily_questions.go index 47819cb..039499f 100644 --- a/internal/data/pg/daily_questions.go +++ b/internal/data/pg/daily_questions.go @@ -14,20 +14,22 @@ import ( const dailyQuestionsTable = "daily_questions" type dailyQuestionsQ struct { - db *pgdb.DB - selector squirrel.SelectBuilder - updater squirrel.UpdateBuilder - counter squirrel.SelectBuilder - deleter squirrel.DeleteBuilder + db *pgdb.DB + selector squirrel.SelectBuilder + updater squirrel.UpdateBuilder + counter squirrel.SelectBuilder + deleter squirrel.DeleteBuilder + timeSelector squirrel.SelectBuilder } func NewDailyQuestionsQ(db *pgdb.DB) data.DailyQuestionsQ { return &dailyQuestionsQ{ - db: db, - selector: squirrel.Select("*").From(dailyQuestionsTable), - updater: squirrel.Update(dailyQuestionsTable), - deleter: squirrel.Delete(dailyQuestionsTable), - counter: squirrel.Select("COUNT(*) as count").From(dailyQuestionsTable), + db: db, + selector: squirrel.Select("*").From(dailyQuestionsTable), + updater: squirrel.Update(dailyQuestionsTable), + deleter: squirrel.Delete(dailyQuestionsTable), + counter: squirrel.Select("COUNT(*) as count").From(dailyQuestionsTable), + timeSelector: squirrel.Select("*").From(dailyQuestionsTable).OrderBy("starts_at ASC"), } } @@ -94,6 +96,14 @@ func (q *dailyQuestionsQ) Select() ([]data.DailyQuestion, error) { return res, nil } +func (q *dailyQuestionsQ) SelectByTime() ([]data.DailyQuestion, error) { + var res []data.DailyQuestion + if err := q.db.Select(&res, q.timeSelector); err != nil { + return res, fmt.Errorf("select daily questions: %w", err) + } + return res, nil +} + func (q *dailyQuestionsQ) Get() (*data.DailyQuestion, error) { var res data.DailyQuestion @@ -107,6 +117,26 @@ func (q *dailyQuestionsQ) Get() (*data.DailyQuestion, error) { return &res, nil } +func applyDailyQuestionPage(page *pgdb.OffsetPageParams, sql squirrel.SelectBuilder) squirrel.SelectBuilder { + if page.Limit == 0 { + page.Limit = 15 + } + if page.Order == "" { + page.Order = pgdb.OrderTypeDesc + } + + offset := page.Limit * page.PageNumber + + sql = sql.Limit(page.Limit).Offset(offset) + + return sql +} + +func (q *dailyQuestionsQ) Page(page *pgdb.OffsetPageParams) data.DailyQuestionsQ { + q.selector = applyDailyQuestionPage(page, q.selector) + return q +} + func (q *dailyQuestionsQ) FilterByCreatedAtAfter(date time.Time) data.DailyQuestionsQ { return q.applyCondition(squirrel.GtOrEq{"created_at": date}) } @@ -130,12 +160,12 @@ func (q *dailyQuestionsQ) FilterTodayQuestions(offset int) data.DailyQuestionsQ func (q *dailyQuestionsQ) FilterDayQuestions(location *time.Location, day time.Time) data.DailyQuestionsQ { dayInLocation := day.In(location) - dayStart := time.Date(dayInLocation.Year(), dayInLocation.Month(), dayInLocation.Day(), 0, 0, 0, 0, location).UTC() - dayEnd := dayStart.Add(24 * time.Hour).Add(-time.Nanosecond).UTC() + dayStart := time.Date(dayInLocation.Year(), dayInLocation.Month(), dayInLocation.Day(), 0, 0, 0, 0, location) + dayEnd := dayStart.Add(24 * time.Hour) return q.applyCondition(squirrel.And{ squirrel.GtOrEq{"starts_at": dayStart}, - squirrel.LtOrEq{"starts_at": dayEnd}, + squirrel.Lt{"starts_at": dayEnd}, }) } @@ -174,5 +204,6 @@ func (q *dailyQuestionsQ) applyCondition(cond squirrel.Sqlizer) data.DailyQuesti q.updater = q.updater.Where(cond) q.deleter = q.deleter.Where(cond) q.counter = q.counter.Where(cond) + q.timeSelector = q.timeSelector.Where(cond) return q } diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index ee808f8..c983813 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -3,7 +3,7 @@ package handlers import ( "encoding/json" "net/http" - "strconv" + "time" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" @@ -13,29 +13,47 @@ import ( ) func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { + //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + // ape.RenderErr(w, problems.Unauthorized()) + // return + //} + req, err := requests.NewDailyQuestion(r) if err != nil { - Log(r).Errorf("Error get request NewDailyQuestion: %v", err) - ape.RenderErr(w, problems.InternalError()) + Log(r).WithError(err).Error("Error get request NewDailyQuestion: %v") + ape.RenderErr(w, problems.BadRequest(err)...) return } location := DailyQuestions(r).Location - question, err := DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() - if question != nil { - Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) - ape.RenderErr(w, problems.Conflict()) + timeReq, err := time.Parse("2006-01-02", req.StartsAt) + if err != nil { + Log(r).WithError(err).Error("Failed to parse start time") + ape.RenderErr(w, problems.BadRequest(err)...) return } + nowTime := time.Now().UTC() + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { + Log(r).Warnf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) + ape.RenderErr(w, problems.Forbidden()) + return + } + + question, err := DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() if err != nil { - Log(r).Errorf("Error on this day %v", err) + Log(r).WithError(err).Error("Error on this day") ape.RenderErr(w, problems.InternalError()) return } + if question != nil { + Log(r).Infof("Question already exist for date %s, question: %+v", question.StartsAt, question) + ape.RenderErr(w, problems.Conflict()) + return + } answerOptions, err := json.Marshal(req.Options) if err != nil { - Log(r).Errorf("Error marshalling answer options: %v", err) + Log(r).WithError(err).Error("Failed to get questions") ape.RenderErr(w, problems.InternalError()) return } @@ -56,10 +74,10 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { stmt := data.DailyQuestion{ Title: req.Title, TimeForAnswer: req.TimeForAnswer, - Reward: int64(req.Reward), + Reward: req.Reward, AnswerOptions: answerOptions, CorrectAnswer: req.CorrectAnswer, - StartsAt: req.StartsAt, + StartsAt: timeReq, } err = DailyQuestionsQ(r).Insert(stmt) @@ -69,17 +87,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - question, err = DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() - if question == nil { - Log(r).Errorf("Error on this day %v, create question '%v'", req.StartsAt, req.Title) - ape.RenderErr(w, problems.InternalError()) - return - } - if err != nil { - Log(r).Errorf("Error on this day %v", err) - ape.RenderErr(w, problems.InternalError()) - return - } + question, _ = DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() ape.Render(w, NewDailyQuestionCrate(&stmt, req.Options, question.ID)) } @@ -87,17 +95,15 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { func NewDailyQuestionCrate(q *data.DailyQuestion, options []resources.DailyQuestionOptions, ID int64) resources.DailyQuestionDetailsResponse { return resources.DailyQuestionDetailsResponse{ Data: resources.DailyQuestionDetails{ - Key: resources.Key{ - ID: strconv.Itoa(int(ID)), - Type: resources.DAILY_QUESTIONS, - }, + Key: resources.NewKeyInt64(ID, resources.DAILY_QUESTIONS), Attributes: resources.DailyQuestionDetailsAttributes{ - CorrectAnswer: q.CorrectAnswer, + Title: q.Title, Options: options, - Reward: int(q.Reward), - StartsAt: q.StartsAt, + CorrectAnswer: q.CorrectAnswer, + Reward: q.Reward, TimeForAnswer: q.TimeForAnswer, - Title: q.Title, + StartsAt: q.StartsAt.String(), + CreatedAt: time.Now().UTC().String(), }, }, } diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index 69881b0..971d0b2 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -1,23 +1,32 @@ package handlers import ( + "encoding/json" + "fmt" "net/http" "strconv" "strings" + "time" "github.com/go-chi/chi" + "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/resources" "gitlab.com/distributed_lab/ape" "gitlab.com/distributed_lab/ape/problems" ) func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { - IDStr := strings.ToLower(chi.URLParam(r, "ID")) + //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + // ape.RenderErr(w, problems.Unauthorized()) + // return + //} + + IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) Log(r).Infof("ID: %v", ID) if err != nil { Log(r).WithError(err).Error("failed to parse ID") - ape.RenderErr(w, problems.InternalError()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -32,7 +41,15 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.NotFound()) return } - title := question.Title + deletedQuestion := *question + + timeReq := question.StartsAt + nowTime := time.Now().UTC() + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { + Log(r).Warnf("Only questions that start tomorrow or later can be delete: %s", timeReq.String()) + ape.RenderErr(w, problems.Forbidden()) + return + } _, err = DailyQuestionsQ(r).New().FilterByID(ID).Delete() if err != nil { @@ -41,19 +58,34 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - ape.Render(w, NewDailyQuestionDelete(ID, title)) + response, err := NewDailyQuestionDelete(ID, deletedQuestion) + if err != nil { + Log(r).WithError(err).Error("Error deleting daily question") + ape.RenderErr(w, problems.InternalError()) + } + ape.Render(w, response) } -func NewDailyQuestionDelete(ID int64, title string) resources.DailyQuestionDelResponse { - return resources.DailyQuestionDelResponse{ - Data: resources.DailyQuestionDel{ - Key: resources.Key{ - ID: strconv.Itoa(int(ID)), - Type: resources.DAILY_QUESTION_DEL, - }, - Attributes: resources.DailyQuestionDelAttributes{ - Title: title, +func NewDailyQuestionDelete(ID int64, q data.DailyQuestion) (resources.DailyQuestionDetailsResponse, error) { + var options []resources.DailyQuestionOptions + err := json.Unmarshal(q.AnswerOptions, &options) + if err != nil { + err = fmt.Errorf("failed to unmarshal AnswerOptions: %v", err) + return resources.DailyQuestionDetailsResponse{}, err + } + + return resources.DailyQuestionDetailsResponse{ + Data: resources.DailyQuestionDetails{ + Key: resources.NewKeyInt64(ID, resources.DAILY_QUESTIONS), + Attributes: resources.DailyQuestionDetailsAttributes{ + Title: q.Title, + Options: options, + CorrectAnswer: q.CorrectAnswer, + Reward: q.Reward, + TimeForAnswer: q.TimeForAnswer, + StartsAt: q.StartsAt.String(), + CreatedAt: q.CreatedAt.String(), }, }, - } + }, nil } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index a084002..8462465 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -2,9 +2,11 @@ package handlers import ( "encoding/json" + "fmt" "net/http" "strconv" "strings" + "time" "github.com/go-chi/chi" "github.com/rarimo/geo-points-svc/internal/data" @@ -15,11 +17,17 @@ import ( ) func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { - IDStr := strings.ToLower(chi.URLParam(r, "ID")) + //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + // ape.RenderErr(w, problems.Unauthorized()) + // return + //} + + Log(r).Infof("Edit Daily Question") + IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) if err != nil { - Log(r).WithError(err).Error("failed to parse ID") - ape.RenderErr(w, problems.InternalError()) + Log(r).WithError(err).Error("Failed to parse ID") + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -30,72 +38,141 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - location := DailyQuestions(r).Location - question, err := DailyQuestionsQ(r).FilterDayQuestions(location, req.StartsAt).Get() - if question != nil && ID != question.ID { - Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) - ape.RenderErr(w, problems.Conflict()) - return - } + question, err := DailyQuestionsQ(r).FilterByID(ID).Get() if err != nil { - Log(r).Errorf("Error on this day %v", err) + Log(r).WithError(err).Error("Error getting question") ape.RenderErr(w, problems.InternalError()) return } - - answerOptions, err := json.Marshal(req.Options) - if err != nil { - Log(r).Errorf("Error marshalling answer options: %v", err) - ape.RenderErr(w, problems.InternalError()) + if question == nil { + Log(r).Warnf("Question with ID %d not found", ID) + ape.RenderErr(w, problems.NotFound()) return } - correctAnswerFound := false - for _, option := range req.Options { - if option.Id == int(req.CorrectAnswer) { - correctAnswerFound = true - break + requestBody := map[string]any{} + + if req.Title != nil { + requestBody[data.ColDailyQuestionTitle] = *req.Title + } + + if req.StartsAt != nil { + timeReq, err := time.Parse("2006-01-02", *req.StartsAt) + if err != nil { + Log(r).WithError(err).Error("Failed to parse start time") + ape.RenderErr(w, problems.BadRequest(err)...) + return } + nowTime := time.Now().UTC() + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { + Log(r).Warnf("Argument start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) + ape.RenderErr(w, problems.Forbidden()) + return + } + + location := DailyQuestions(r).Location + question, err := DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() + if err != nil { + Log(r).Errorf("Error on this day %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + if question != nil && ID != question.ID { + Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) + ape.RenderErr(w, problems.Conflict()) + return + } + requestBody[data.ColStartAt] = *req.StartsAt } - if !correctAnswerFound { - Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) - ape.RenderErr(w, problems.Forbidden()) - return + + if req.Options != nil { + answerOptions, err := json.Marshal(req.Options) + if err != nil { + Log(r).Errorf("Error marshalling answer options: %v", err) + ape.RenderErr(w, problems.InternalError()) + return + } + correctAnswerFound := false + + for _, option := range *req.Options { + if option.Id == int(*req.CorrectAnswer) { + correctAnswerFound = true + break + } + } + if !correctAnswerFound { + Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody[data.ColAnswerOption] = answerOptions } - err = DailyQuestionsQ(r).FilterByID(ID).Update(map[string]any{ - data.ColDailyQuestionTitle: req.Title, - data.ColTimeForAnswer: req.TimeForAnswer, - data.ColDailyQuestionReward: req.Reward, - data.ColAnswerOption: answerOptions, - data.ColCorrectAnswerId: req.CorrectAnswer, - }) + if req.Reward != nil { + if *req.Reward <= 0 { + Log(r).Error("Invalid Reward") + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody[data.ColReward] = *req.Reward + } + + if req.CorrectAnswer != nil { + l := len(question.AnswerOptions) + if *req.CorrectAnswer < 0 || l <= int(*req.CorrectAnswer) { + Log(r).Error("Invalid CorrectAnswer") + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody[data.ColCorrectAnswerId] = *req.CorrectAnswer + } + + if req.TimeForAnswer != nil { + if *req.TimeForAnswer < 0 { + Log(r).Error("Invalid Time for answer") + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody[data.ColTimeForAnswer] = *req.CorrectAnswer + } + + err = DailyQuestionsQ(r).FilterByID(ID).Update(requestBody) + if err != nil { + Log(r).WithError(err).Error("Error editing daily question") + ape.RenderErr(w, problems.InternalError()) + return + } + questionNew, _ := DailyQuestionsQ(r).FilterByID(ID).Get() + resp, err := NewDailyQuestionEdite(ID, questionNew) if err != nil { Log(r).WithError(err).Error("Error editing daily question") ape.RenderErr(w, problems.InternalError()) return } - ape.Render(w, NewDailyQuestionEdite(ID, req)) + ape.Render(w, resp) } -func NewDailyQuestionEdite(ID int64, req resources.DailyQuestionEditAttributes) resources.DailyQuestionEditResponse { - return resources.DailyQuestionEditResponse{ - Data: resources.DailyQuestionEdit{ - Key: resources.Key{ - ID: strconv.Itoa(int(ID)), - Type: resources.DAILY_QUESTION_EDIT, - }, - Attributes: resources.DailyQuestionEditAttributes{ - CorrectAnswer: req.CorrectAnswer, - Options: req.Options, - Reward: req.Reward, - StartsAt: req.StartsAt, - TimeForAnswer: req.TimeForAnswer, - Title: req.Title, - }, - }, +func NewDailyQuestionEdite(ID int64, q *data.DailyQuestion) (resources.DailyQuestionDetailsResponse, error) { + var options []resources.DailyQuestionOptions + err := json.Unmarshal(q.AnswerOptions, &options) + if err != nil { + err = fmt.Errorf("failed to unmarshal AnswerOptions: %v", err) + return resources.DailyQuestionDetailsResponse{}, err } + return resources.DailyQuestionDetailsResponse{ + Data: resources.DailyQuestionDetails{ + Key: resources.NewKeyInt64(ID, resources.DAILY_QUESTIONS), + Attributes: resources.DailyQuestionDetailsAttributes{ + CorrectAnswer: q.CorrectAnswer, + Options: options, + Reward: q.Reward, + StartsAt: q.StartsAt.String(), + TimeForAnswer: q.TimeForAnswer, + Title: q.Title, + }, + }, + }, nil } diff --git a/internal/service/handlers/daily_questions_filter_date.go b/internal/service/handlers/daily_questions_select.go similarity index 73% rename from internal/service/handlers/daily_questions_filter_date.go rename to internal/service/handlers/daily_questions_select.go index e7935ba..d3d0ede 100644 --- a/internal/service/handlers/daily_questions_filter_date.go +++ b/internal/service/handlers/daily_questions_select.go @@ -4,11 +4,7 @@ import ( "encoding/json" "fmt" "net/http" - "strconv" - "strings" - "time" - "github.com/go-chi/chi" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -17,21 +13,18 @@ import ( ) func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { - dateStr := strings.ToLower(chi.URLParam(r, "date")) + //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + // ape.RenderErr(w, problems.Unauthorized()) + // return + //} + req, err := requests.NewFilterStartAtDailyQuestions(r) if err != nil { Log(r).WithError(err).Error("error creating filter start at daily questions request") - ape.RenderErr(w, problems.InternalError()) - } - - date, err := time.Parse("2006-01-02", dateStr) - if err != nil { - Log(r).WithError(err).Error("Invalid date format, expected YYYY-MM-DD") - ape.RenderErr(w, problems.InternalError()) - return + ape.RenderErr(w, problems.BadRequest(err)...) } - res, err := DailyQuestionsQ(r).FilterByStartsAtAfter(date).Select() + res, err := DailyQuestionsQ(r).Page(&req.OffsetPageParams).SelectByTime() if err != nil { Log(r).WithError(err).Error("Error filtering questions") ape.RenderErr(w, problems.InternalError()) @@ -47,7 +40,7 @@ func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { resp.Links = req.GetLinks(r) if req.Count { - QuestionListCount, err := DailyQuestionsQ(r).FilterByStartsAtAfter(date).Count() + questionListCount, err := DailyQuestionsQ(r).Count() if err != nil { Log(r).WithError(err).Error("Failed to count balances") ape.RenderErr(w, problems.InternalError()) @@ -56,7 +49,7 @@ func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { _ = resp.PutMeta(struct { QuestionCount int64 `json:"question_count"` - }{QuestionListCount}) + }{questionListCount}) } ape.Render(w, resp) } @@ -66,24 +59,21 @@ func NewDailyQuestionModel(question data.DailyQuestion) (resources.DailyQuestion err := json.Unmarshal(question.AnswerOptions, &options) if err != nil { - err := fmt.Errorf("failed to unmarshal AnswerOptions: %v", err) + err = fmt.Errorf("failed to unmarshal AnswerOptions: %v", err) return resources.DailyQuestionDetails{}, err } return resources.DailyQuestionDetails{ - Key: resources.Key{ - ID: strconv.Itoa(int(question.ID)), - Type: resources.DAILY_QUESTION_DETAILS, - }, + Key: resources.NewKeyInt64(question.ID, resources.DAILY_QUESTIONS), Attributes: resources.DailyQuestionDetailsAttributes{ CorrectAnswer: question.CorrectAnswer, - CreatedAt: question.CreatedAt, + CreatedAt: question.CreatedAt.String(), NumAllParticipants: question.NumAllParticipants, NumCorrectAnswers: question.NumCorrectAnswers, NumIncorrectAnswers: question.NumIncorrectAnswers, Options: options, - Reward: int(question.Reward), - StartsAt: question.StartsAt, + Reward: question.Reward, + StartsAt: question.StartsAt.String(), TimeForAnswer: question.TimeForAnswer, Title: question.Title, }, diff --git a/internal/service/requests/daily_question_create.go b/internal/service/requests/daily_question_create.go index 2e0cad9..17c1366 100644 --- a/internal/service/requests/daily_question_create.go +++ b/internal/service/requests/daily_question_create.go @@ -8,18 +8,18 @@ import ( "github.com/rarimo/geo-points-svc/resources" ) -func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionDetailsAttributes, err error) { +func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionCreateAttributes, err error) { if err = json.NewDecoder(r.Body).Decode(&req); err != nil { err = newDecodeError("body", err) return } return req, validation.Errors{ - "time_for_answer": validation.Validate(&req.TimeForAnswer, validation.Required), - "correct_answer": validation.Validate(&req.CorrectAnswer, validation.Required), - "starts_at": validation.Validate(&req.StartsAt, validation.Required), - "options": validation.Validate(&req.Options, validation.Required), - "reward": validation.Validate(&req.Reward, validation.Required), - "title": validation.Validate(&req.Title, validation.Required), + "data/attributes/time_for_answer": validation.Validate(req.TimeForAnswer, validation.Required), + "data/attributes/correct_answer": validation.Validate(req.CorrectAnswer, validation.Required), + "data/attributes/starts_at": validation.Validate(req.StartsAt, validation.Required), + "data/attributes/options": validation.Validate(req.Options, validation.Required), + "data/attributes/reward": validation.Validate(req.Reward, validation.Required), + "data/attributes/title": validation.Validate(req.Title, validation.Required), }.Filter() } diff --git a/internal/service/requests/daily_question_edit.go b/internal/service/requests/daily_question_edit.go index cfa9439..19aa9e3 100644 --- a/internal/service/requests/daily_question_edit.go +++ b/internal/service/requests/daily_question_edit.go @@ -15,11 +15,11 @@ func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditAttri } return req, validation.Errors{ - "time_for_answer": validation.Validate(&req.TimeForAnswer, validation.Required), - "correct_answer": validation.Validate(&req.CorrectAnswer, validation.Required), - "starts_at": validation.Validate(&req.StartsAt, validation.Required), - "options": validation.Validate(&req.Options, validation.Required), - "reward": validation.Validate(&req.Reward, validation.Required), - "title": validation.Validate(&req.Title, validation.Required), + "time_for_answer": validation.Validate(&req.TimeForAnswer), + "correct_answer": validation.Validate(&req.CorrectAnswer), + "starts_at": validation.Validate(&req.StartsAt), + "options": validation.Validate(&req.Options), + "reward": validation.Validate(&req.Reward), + "title": validation.Validate(&req.Title), }.Filter() } diff --git a/internal/service/router.go b/internal/service/router.go index ec509db..a4f1528 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -47,12 +47,14 @@ func Run(ctx context.Context, cfg config.Config) { }) r.Route("/daily_questions", func(r chi.Router) { - r.Post("/create", handlers.CreateDailyQuestion) - r.Delete("/delete/{ID}", handlers.DeleteDailyQuestion) - r.Patch("/edit/{ID}", handlers.EditDailyQuestion) - r.Get("/filter_start_at/{date}", handlers.FilterStartAtDailyQuestions) + r.Route("/admin", func(r chi.Router) { + //r.Use(authMW) + r.Delete("/{question_id}", handlers.DeleteDailyQuestion) + r.Patch("/{question_id}", handlers.EditDailyQuestion) + r.Post("/", handlers.CreateDailyQuestion) + r.Get("/", handlers.FilterStartAtDailyQuestions) + }) r.Route("/{nullifier}", func(r chi.Router) { - r.Use(authMW) r.Get("/status", handlers.GetDailyQuestionsStatus) r.Get("/", handlers.GetDailyQuestion) r.Post("/", handlers.CheckDailyQuestion) diff --git a/resources/model_daily_question_create.go b/resources/model_daily_question_create.go new file mode 100644 index 0000000..32cce87 --- /dev/null +++ b/resources/model_daily_question_create.go @@ -0,0 +1,43 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +import "encoding/json" + +type DailyQuestionCreate struct { + Key + Attributes DailyQuestionCreateAttributes `json:"attributes"` +} +type DailyQuestionCreateResponse struct { + Data DailyQuestionCreate `json:"data"` + Included Included `json:"included"` +} + +type DailyQuestionCreateListResponse struct { + Data []DailyQuestionCreate `json:"data"` + Included Included `json:"included"` + Links *Links `json:"links"` + Meta json.RawMessage `json:"meta,omitempty"` +} + +func (r *DailyQuestionCreateListResponse) PutMeta(v interface{}) (err error) { + r.Meta, err = json.Marshal(v) + return err +} + +func (r *DailyQuestionCreateListResponse) GetMeta(out interface{}) error { + return json.Unmarshal(r.Meta, out) +} + +// MustDailyQuestionCreate - returns DailyQuestionCreate from include collection. +// if entry with specified key does not exist - returns nil +// if entry with specified key exists but type or ID mismatches - panics +func (c *Included) MustDailyQuestionCreate(key Key) *DailyQuestionCreate { + var dailyQuestionCreate DailyQuestionCreate + if c.tryFindEntry(key, &dailyQuestionCreate) { + return &dailyQuestionCreate + } + return nil +} diff --git a/resources/model_daily_question_create_attributes.go b/resources/model_daily_question_create_attributes.go new file mode 100644 index 0000000..fbdb8db --- /dev/null +++ b/resources/model_daily_question_create_attributes.go @@ -0,0 +1,20 @@ +/* + * GENERATED. Do not modify. Your changes might be overwritten! + */ + +package resources + +type DailyQuestionCreateAttributes struct { + // Correct answer ID + CorrectAnswer int64 `json:"correct_answer"` + // Answer options. Minimum 2, maximum 6 + Options []DailyQuestionOptions `json:"options"` + // Reward for a correct answer + Reward int64 `json:"reward"` + // Start date when this question is available, hours and minutes are always 0 + StartsAt string `json:"starts_at"` + // Time for answer + TimeForAnswer int64 `json:"time_for_answer"` + // Question title + Title string `json:"title"` +} diff --git a/resources/model_daily_question_details_attributes.go b/resources/model_daily_question_details_attributes.go index b411725..52b635a 100644 --- a/resources/model_daily_question_details_attributes.go +++ b/resources/model_daily_question_details_attributes.go @@ -4,26 +4,24 @@ package resources -import "time" - type DailyQuestionDetailsAttributes struct { - // right answer index + // Correct answer ID CorrectAnswer int64 `json:"correct_answer"` - // start date when this question was create - CreatedAt time.Time `json:"created_at"` - // users who received the question, those who answered and those who did not answer in the time given to them + // Start date when this question was create + CreatedAt string `json:"created_at"` + // Users who received the question, those who answered and those who did not answer in the time given to them NumAllParticipants int64 `json:"num_all_participants"` - // number of correct answers + // Number of correct answers NumCorrectAnswers int64 `json:"num_correct_answers"` - // number of incorrect answers + // Number of incorrect answers NumIncorrectAnswers int64 `json:"num_incorrect_answers"` // Answer options. Minimum 2, maximum 6 Options []DailyQuestionOptions `json:"options"` - // reward for a correct answer - Reward int `json:"reward"` - // start date when this question is available, hours and minutes are always 0 - StartsAt time.Time `json:"starts_at"` - // time for answer + // Reward for a correct answer + Reward int64 `json:"reward"` + // Start date when this question is available, hours and minutes are always 0 + StartsAt string `json:"starts_at"` + // Time for answer TimeForAnswer int64 `json:"time_for_answer"` // Question title Title string `json:"title"` diff --git a/resources/model_daily_question_edit_attributes.go b/resources/model_daily_question_edit_attributes.go index 9740a47..2dfd737 100644 --- a/resources/model_daily_question_edit_attributes.go +++ b/resources/model_daily_question_edit_attributes.go @@ -4,19 +4,17 @@ package resources -import "time" - type DailyQuestionEditAttributes struct { - // right answer index - CorrectAnswer int64 `json:"correct_answer"` + // Correct answer ID + CorrectAnswer *int64 `json:"correct_answer,omitempty"` // Answer options. Minimum 2, maximum 6 - Options []DailyQuestionOptions `json:"options"` - // reward for a correct answer - Reward int `json:"reward"` - // start date when this question is available, hours and minutes are always 0 - StartsAt time.Time `json:"starts_at"` - // time for answer - TimeForAnswer int64 `json:"time_for_answer"` + Options *[]DailyQuestionOptions `json:"options,omitempty"` + // Reward for a correct answer + Reward *int64 `json:"reward,omitempty"` + // Start date when this question is available, hours and minutes are always 0 + StartsAt *string `json:"starts_at,omitempty"` + // Time for answer + TimeForAnswer *int64 `json:"time_for_answer,omitempty"` // Question title - Title string `json:"title"` + Title *string `json:"title,omitempty"` } diff --git a/resources/model_resource_type.go b/resources/model_resource_type.go index a89c7ee..339c618 100644 --- a/resources/model_resource_type.go +++ b/resources/model_resource_type.go @@ -12,9 +12,6 @@ const ( BALANCE ResourceType = "balance" CLAIM_EVENT ResourceType = "claim_event" CREATE_BALANCE ResourceType = "create_balance" - DAILY_QUESTION_DEL ResourceType = "daily_question_del" - DAILY_QUESTION_DETAILS ResourceType = "daily_question_details" - DAILY_QUESTION_EDIT ResourceType = "daily_question_edit" DAILY_QUESTIONS ResourceType = "daily_questions" DAILY_QUESTIONS_STATUS ResourceType = "daily_questions_status" EVENT_CLAIMING_STATE ResourceType = "event_claiming_state" From b8c90a511232258a687a7dafa9985959e32541cb Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 16:56:32 +0300 Subject: [PATCH 10/29] changed the deadline for editing and fixed daily_questin_select --- .../service/handlers/daily_question_create.go | 2 +- .../service/handlers/daily_question_delete.go | 2 +- .../service/handlers/daily_question_edit.go | 18 ++++++++++++------ 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index c983813..68489f4 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -33,7 +33,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { Log(r).Warnf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index 971d0b2..1a52d8e 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -45,7 +45,7 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { timeReq := question.StartsAt nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { Log(r).Warnf("Only questions that start tomorrow or later can be delete: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 8462465..0b364e5 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -22,7 +22,6 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { // return //} - Log(r).Infof("Edit Daily Question") IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) if err != nil { @@ -50,6 +49,13 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } + nowTime := time.Now().UTC() + if !question.StartsAt.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { + Log(r).Warnf("Cannot change a question id: %v that is available today or in the past", ID) + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody := map[string]any{} if req.Title != nil { @@ -64,8 +70,8 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, time.UTC)) { - Log(r).Warnf("Argument start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { + Log(r).Warnf("Argument start_at must be more or equal tommorow midnoght now its: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return } @@ -133,7 +139,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.Forbidden()) return } - requestBody[data.ColTimeForAnswer] = *req.CorrectAnswer + requestBody[data.ColTimeForAnswer] = *req.TimeForAnswer } err = DailyQuestionsQ(r).FilterByID(ID).Update(requestBody) @@ -142,7 +148,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.InternalError()) return } - + Log(r).Infof("sdkovsaofosa") questionNew, _ := DailyQuestionsQ(r).FilterByID(ID).Get() resp, err := NewDailyQuestionEdite(ID, questionNew) if err != nil { @@ -150,7 +156,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.InternalError()) return } - + Log(r).Infof("sdkovsaofosa") ape.Render(w, resp) } From d56c0a66b91495bbe365f299742e003370998ecc Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 16:58:44 +0300 Subject: [PATCH 11/29] return auth --- internal/service/handlers/daily_question_delete.go | 9 +++++---- internal/service/handlers/daily_question_edit.go | 13 +++++++------ internal/service/handlers/daily_questions_select.go | 9 +++++---- internal/service/router.go | 2 +- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index 1a52d8e..0961efb 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -9,6 +9,7 @@ import ( "time" "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/resources" "gitlab.com/distributed_lab/ape" @@ -16,10 +17,10 @@ import ( ) func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { - //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - // ape.RenderErr(w, problems.Unauthorized()) - // return - //} + if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + ape.RenderErr(w, problems.Unauthorized()) + return + } IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 0b364e5..2c910d9 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -9,6 +9,7 @@ import ( "time" "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/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -17,10 +18,10 @@ import ( ) func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { - //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - // ape.RenderErr(w, problems.Unauthorized()) - // return - //} + if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + ape.RenderErr(w, problems.Unauthorized()) + return + } IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) @@ -148,7 +149,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.InternalError()) return } - Log(r).Infof("sdkovsaofosa") + questionNew, _ := DailyQuestionsQ(r).FilterByID(ID).Get() resp, err := NewDailyQuestionEdite(ID, questionNew) if err != nil { @@ -156,7 +157,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.InternalError()) return } - Log(r).Infof("sdkovsaofosa") + ape.Render(w, resp) } diff --git a/internal/service/handlers/daily_questions_select.go b/internal/service/handlers/daily_questions_select.go index d3d0ede..eed96f1 100644 --- a/internal/service/handlers/daily_questions_select.go +++ b/internal/service/handlers/daily_questions_select.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -13,10 +14,10 @@ import ( ) func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { - //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - // ape.RenderErr(w, problems.Unauthorized()) - // return - //} + if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + ape.RenderErr(w, problems.Unauthorized()) + return + } req, err := requests.NewFilterStartAtDailyQuestions(r) if err != nil { diff --git a/internal/service/router.go b/internal/service/router.go index a4f1528..033f1d9 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -48,7 +48,7 @@ func Run(ctx context.Context, cfg config.Config) { r.Route("/daily_questions", func(r chi.Router) { r.Route("/admin", func(r chi.Router) { - //r.Use(authMW) + r.Use(authMW) r.Delete("/{question_id}", handlers.DeleteDailyQuestion) r.Patch("/{question_id}", handlers.EditDailyQuestion) r.Post("/", handlers.CreateDailyQuestion) From b0870a79fb6718b290bcc875cfd664b4ba011d2e Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 17:32:14 +0300 Subject: [PATCH 12/29] fix bugs with edit question add validate func for options --- .../service/handlers/daily_question_create.go | 39 ++++++++++++++++ .../service/handlers/daily_question_edit.go | 45 ++++++++++++------- internal/service/router.go | 2 +- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index 68489f4..530229b 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -2,7 +2,9 @@ package handlers import ( "encoding/json" + "fmt" "net/http" + "sort" "time" "github.com/rarimo/geo-points-svc/internal/data" @@ -25,6 +27,13 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } + err = ValidateOptions(req.Options) + if err != nil { + Log(r).WithError(err).Error("Error Answer Options") + ape.RenderErr(w, problems.Forbidden()) + return + } + location := DailyQuestions(r).Location timeReq, err := time.Parse("2006-01-02", req.StartsAt) if err != nil { @@ -92,6 +101,36 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.Render(w, NewDailyQuestionCrate(&stmt, req.Options, question.ID)) } +func ValidateOptions(options []resources.DailyQuestionOptions) error { + if len(options) < 2 || len(options) > 6 { + return fmt.Errorf("the number of options must be between 2 and 6") + } + + uniqueTitles := make(map[string]bool) + + for _, option := range options { + if option.Title == "" { + return fmt.Errorf("option titles must not be empty") + } + if _, exists := uniqueTitles[option.Title]; exists { + return fmt.Errorf("option titles must be unique, found duplicate: %s", option.Title) + } + uniqueTitles[option.Title] = true + } + + ids := make([]int, len(options)) + for i, option := range options { + ids[i] = option.Id + } + sort.Ints(ids) + for i := 0; i < len(ids); i++ { + if ids[i] != i { + return fmt.Errorf("option IDs must be sequential and start from 0") + } + } + return nil +} + func NewDailyQuestionCrate(q *data.DailyQuestion, options []resources.DailyQuestionOptions, ID int64) resources.DailyQuestionDetailsResponse { return resources.DailyQuestionDetailsResponse{ Data: resources.DailyQuestionDetails{ diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 2c910d9..0d9b85c 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -9,7 +9,6 @@ import ( "time" "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/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -18,10 +17,10 @@ import ( ) func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { - if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - ape.RenderErr(w, problems.Unauthorized()) - return - } + //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + // ape.RenderErr(w, problems.Unauthorized()) + // return + //} IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) @@ -92,7 +91,24 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { requestBody[data.ColStartAt] = *req.StartsAt } + if req.CorrectAnswer != nil { + l := len(question.AnswerOptions) + if *req.CorrectAnswer < 0 || l <= int(*req.CorrectAnswer) { + Log(r).Error("Invalid CorrectAnswer") + ape.RenderErr(w, problems.Forbidden()) + return + } + requestBody[data.ColCorrectAnswerId] = *req.CorrectAnswer + } + if req.Options != nil { + err = ValidateOptions(*req.Options) + if err != nil { + Log(r).WithError(err).Error("Error Answer Options") + ape.RenderErr(w, problems.Forbidden()) + return + } + answerOptions, err := json.Marshal(req.Options) if err != nil { Log(r).Errorf("Error marshalling answer options: %v", err) @@ -101,14 +117,19 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { } correctAnswerFound := false + var localCorrectAnswer int64 + if req.CorrectAnswer != nil { + localCorrectAnswer = *req.CorrectAnswer + } + for _, option := range *req.Options { - if option.Id == int(*req.CorrectAnswer) { + if option.Id == int(localCorrectAnswer) { correctAnswerFound = true break } } if !correctAnswerFound { - Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) + Log(r).Warnf("Correct answer option out of range: %v", question.CorrectAnswer) ape.RenderErr(w, problems.Forbidden()) return } @@ -124,16 +145,6 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { requestBody[data.ColReward] = *req.Reward } - if req.CorrectAnswer != nil { - l := len(question.AnswerOptions) - if *req.CorrectAnswer < 0 || l <= int(*req.CorrectAnswer) { - Log(r).Error("Invalid CorrectAnswer") - ape.RenderErr(w, problems.Forbidden()) - return - } - requestBody[data.ColCorrectAnswerId] = *req.CorrectAnswer - } - if req.TimeForAnswer != nil { if *req.TimeForAnswer < 0 { Log(r).Error("Invalid Time for answer") diff --git a/internal/service/router.go b/internal/service/router.go index 033f1d9..a4f1528 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -48,7 +48,7 @@ func Run(ctx context.Context, cfg config.Config) { r.Route("/daily_questions", func(r chi.Router) { r.Route("/admin", func(r chi.Router) { - r.Use(authMW) + //r.Use(authMW) r.Delete("/{question_id}", handlers.DeleteDailyQuestion) r.Patch("/{question_id}", handlers.EditDailyQuestion) r.Post("/", handlers.CreateDailyQuestion) From 8bd983a9fbd29b7e4caea53119e48356ecf6e522 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 17:36:34 +0300 Subject: [PATCH 13/29] return auth --- internal/service/handlers/daily_question_create.go | 9 +++++---- internal/service/handlers/daily_question_edit.go | 9 +++++---- internal/service/router.go | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index 530229b..ee44c75 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -7,6 +7,7 @@ import ( "sort" "time" + "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -15,10 +16,10 @@ import ( ) func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { - //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - // ape.RenderErr(w, problems.Unauthorized()) - // return - //} + if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + ape.RenderErr(w, problems.Unauthorized()) + return + } req, err := requests.NewDailyQuestion(r) if err != nil { diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 0d9b85c..0325ccc 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -9,6 +9,7 @@ import ( "time" "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/service/requests" "github.com/rarimo/geo-points-svc/resources" @@ -17,10 +18,10 @@ import ( ) func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { - //if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { - // ape.RenderErr(w, problems.Unauthorized()) - // return - //} + if !auth.Authenticates(UserClaims(r), auth.AdminGrant) { + ape.RenderErr(w, problems.Unauthorized()) + return + } IDStr := strings.ToLower(chi.URLParam(r, "question_id")) ID, err := strconv.ParseInt(IDStr, 10, 64) diff --git a/internal/service/router.go b/internal/service/router.go index a4f1528..033f1d9 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -48,7 +48,7 @@ func Run(ctx context.Context, cfg config.Config) { r.Route("/daily_questions", func(r chi.Router) { r.Route("/admin", func(r chi.Router) { - //r.Use(authMW) + r.Use(authMW) r.Delete("/{question_id}", handlers.DeleteDailyQuestion) r.Patch("/{question_id}", handlers.EditDailyQuestion) r.Post("/", handlers.CreateDailyQuestion) From 9aa59e550aebb36edd957f758496db8bddf4fc9c Mon Sep 17 00:00:00 2001 From: trpdjke Date: Tue, 27 Aug 2024 17:37:16 +0300 Subject: [PATCH 14/29] fix router --- internal/service/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/service/router.go b/internal/service/router.go index 033f1d9..302a1a6 100644 --- a/internal/service/router.go +++ b/internal/service/router.go @@ -47,8 +47,8 @@ func Run(ctx context.Context, cfg config.Config) { }) r.Route("/daily_questions", func(r chi.Router) { + r.Use(authMW) r.Route("/admin", func(r chi.Router) { - r.Use(authMW) r.Delete("/{question_id}", handlers.DeleteDailyQuestion) r.Patch("/{question_id}", handlers.EditDailyQuestion) r.Post("/", handlers.CreateDailyQuestion) From edca683add0ba59e847b419f051b245865aade07 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 12:47:30 +0300 Subject: [PATCH 15/29] add location to DQ admin fix bug with uneditable starts_at --- internal/data/daily_questions.go | 3 +- internal/data/pg/daily_questions.go | 33 +++++++------------ .../service/handlers/daily_question_create.go | 14 ++++---- .../service/handlers/daily_question_delete.go | 6 ++-- .../service/handlers/daily_question_edit.go | 14 ++++---- .../handlers/daily_questions_select.go | 3 +- 6 files changed, 31 insertions(+), 42 deletions(-) diff --git a/internal/data/daily_questions.go b/internal/data/daily_questions.go index 3bfa6fe..dccdb13 100644 --- a/internal/data/daily_questions.go +++ b/internal/data/daily_questions.go @@ -16,7 +16,7 @@ const ( ColAnswerOption = "answer_options" ColCorrectAnswerId = "correct_answer" ColReward = "reward" - ColStartAt = "start_at" + ColStartAt = "starts_at" ColCorrectAnswers = "num_correct_answers" ColIncorrectAnswers = "num_incorrect_answers" ColAllParticipants = "num_all_participants" @@ -43,7 +43,6 @@ type DailyQuestionsQ interface { Delete() (int64, error) Count() (int64, error) Select() ([]DailyQuestion, error) - SelectByTime() ([]DailyQuestion, error) Get() (*DailyQuestion, error) Page(*pgdb.OffsetPageParams) DailyQuestionsQ diff --git a/internal/data/pg/daily_questions.go b/internal/data/pg/daily_questions.go index 039499f..20dd448 100644 --- a/internal/data/pg/daily_questions.go +++ b/internal/data/pg/daily_questions.go @@ -14,22 +14,20 @@ import ( const dailyQuestionsTable = "daily_questions" type dailyQuestionsQ struct { - db *pgdb.DB - selector squirrel.SelectBuilder - updater squirrel.UpdateBuilder - counter squirrel.SelectBuilder - deleter squirrel.DeleteBuilder - timeSelector squirrel.SelectBuilder + db *pgdb.DB + selector squirrel.SelectBuilder + updater squirrel.UpdateBuilder + counter squirrel.SelectBuilder + deleter squirrel.DeleteBuilder } func NewDailyQuestionsQ(db *pgdb.DB) data.DailyQuestionsQ { return &dailyQuestionsQ{ - db: db, - selector: squirrel.Select("*").From(dailyQuestionsTable), - updater: squirrel.Update(dailyQuestionsTable), - deleter: squirrel.Delete(dailyQuestionsTable), - counter: squirrel.Select("COUNT(*) as count").From(dailyQuestionsTable), - timeSelector: squirrel.Select("*").From(dailyQuestionsTable).OrderBy("starts_at ASC"), + db: db, + selector: squirrel.Select("*").From(dailyQuestionsTable), + updater: squirrel.Update(dailyQuestionsTable), + deleter: squirrel.Delete(dailyQuestionsTable), + counter: squirrel.Select("COUNT(*) as count").From(dailyQuestionsTable), } } @@ -96,14 +94,6 @@ func (q *dailyQuestionsQ) Select() ([]data.DailyQuestion, error) { return res, nil } -func (q *dailyQuestionsQ) SelectByTime() ([]data.DailyQuestion, error) { - var res []data.DailyQuestion - if err := q.db.Select(&res, q.timeSelector); err != nil { - return res, fmt.Errorf("select daily questions: %w", err) - } - return res, nil -} - func (q *dailyQuestionsQ) Get() (*data.DailyQuestion, error) { var res data.DailyQuestion @@ -133,7 +123,7 @@ func applyDailyQuestionPage(page *pgdb.OffsetPageParams, sql squirrel.SelectBuil } func (q *dailyQuestionsQ) Page(page *pgdb.OffsetPageParams) data.DailyQuestionsQ { - q.selector = applyDailyQuestionPage(page, q.selector) + q.selector = page.ApplyTo(q.selector, "starts_at") return q } @@ -204,6 +194,5 @@ func (q *dailyQuestionsQ) applyCondition(cond squirrel.Sqlizer) data.DailyQuesti q.updater = q.updater.Where(cond) q.deleter = q.deleter.Where(cond) q.counter = q.counter.Where(cond) - q.timeSelector = q.timeSelector.Where(cond) return q } diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index ee44c75..0aff4b9 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -43,8 +43,8 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { - Log(r).Warnf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { + Log(r).Errorf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return } @@ -56,7 +56,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if question != nil { - Log(r).Infof("Question already exist for date %s, question: %+v", question.StartsAt, question) + Log(r).Errorf("Question already exist for date %s, question: %+v", question.StartsAt, question) ape.RenderErr(w, problems.Conflict()) return } @@ -76,7 +76,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } } if !correctAnswerFound { - Log(r).Warnf("Correct answer option out of range: %v", req.CorrectAnswer) + Log(r).Errorf("Correct answer option out of range: %v", req.CorrectAnswer) ape.RenderErr(w, problems.Forbidden()) return } @@ -92,14 +92,14 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { err = DailyQuestionsQ(r).Insert(stmt) if err != nil { - Log(r).Errorf("Error ger request NewDailyQuestion: %v", err) + Log(r).WithError(err).Error("Error ger request NewDailyQuestion") ape.RenderErr(w, problems.InternalError()) return } question, _ = DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() - ape.Render(w, NewDailyQuestionCrate(&stmt, req.Options, question.ID)) + ape.Render(w, NewDailyQuestionCreate(&stmt, req.Options, question.ID)) } func ValidateOptions(options []resources.DailyQuestionOptions) error { @@ -132,7 +132,7 @@ func ValidateOptions(options []resources.DailyQuestionOptions) error { return nil } -func NewDailyQuestionCrate(q *data.DailyQuestion, options []resources.DailyQuestionOptions, ID int64) resources.DailyQuestionDetailsResponse { +func NewDailyQuestionCreate(q *data.DailyQuestion, options []resources.DailyQuestionOptions, ID int64) resources.DailyQuestionDetailsResponse { return resources.DailyQuestionDetailsResponse{ Data: resources.DailyQuestionDetails{ Key: resources.NewKeyInt64(ID, resources.DAILY_QUESTIONS), diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index 0961efb..a746225 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -38,7 +38,7 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if question == nil { - Log(r).Warnf("Question with ID %d not found", ID) + Log(r).Errorf("Question with ID %d not found", ID) ape.RenderErr(w, problems.NotFound()) return } @@ -46,8 +46,8 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { timeReq := question.StartsAt nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { - Log(r).Warnf("Only questions that start tomorrow or later can be delete: %s", timeReq.String()) + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { + Log(r).Errorf("Only questions that start tomorrow or later can be delete: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 0325ccc..95c3f59 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -45,14 +45,14 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if question == nil { - Log(r).Warnf("Question with ID %d not found", ID) + Log(r).Error("Question with ID %d not found", ID) ape.RenderErr(w, problems.NotFound()) return } nowTime := time.Now().UTC() - if !question.StartsAt.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { - Log(r).Warnf("Cannot change a question id: %v that is available today or in the past", ID) + if !question.StartsAt.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { + Log(r).Errorf("Cannot change a question id: %v that is available today or in the past", ID) ape.RenderErr(w, problems.Forbidden()) return } @@ -71,8 +71,8 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } nowTime := time.Now().UTC() - if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day(), 0, 0, 0, 0, time.UTC)) { - Log(r).Warnf("Argument start_at must be more or equal tommorow midnoght now its: %s", timeReq.String()) + if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { + Log(r).Errorf("Argument start_at must be more or equal tommorow midnoght now its: %s", timeReq.String()) ape.RenderErr(w, problems.Forbidden()) return } @@ -80,7 +80,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { location := DailyQuestions(r).Location question, err := DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() if err != nil { - Log(r).Errorf("Error on this day %v", err) + Log(r).WithError(err).Error("Error on this day") ape.RenderErr(w, problems.InternalError()) return } @@ -89,7 +89,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.Conflict()) return } - requestBody[data.ColStartAt] = *req.StartsAt + requestBody[data.ColStartAt] = req.StartsAt } if req.CorrectAnswer != nil { diff --git a/internal/service/handlers/daily_questions_select.go b/internal/service/handlers/daily_questions_select.go index eed96f1..500007e 100644 --- a/internal/service/handlers/daily_questions_select.go +++ b/internal/service/handlers/daily_questions_select.go @@ -23,9 +23,10 @@ func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { if err != nil { Log(r).WithError(err).Error("error creating filter start at daily questions request") ape.RenderErr(w, problems.BadRequest(err)...) + return } - res, err := DailyQuestionsQ(r).Page(&req.OffsetPageParams).SelectByTime() + res, err := DailyQuestionsQ(r).Page(&req.OffsetPageParams).Select() if err != nil { Log(r).WithError(err).Error("Error filtering questions") ape.RenderErr(w, problems.InternalError()) From f76efa5e0a9582369c8a2a549164518c87aca45f Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:06:47 +0300 Subject: [PATCH 16/29] before megre main --- go.sum | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/go.sum b/go.sum index 6469af2..9b8b772 100644 --- a/go.sum +++ b/go.sum @@ -1996,6 +1996,8 @@ github.com/nats-io/nkeys v0.4.5/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5s github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077 h1:A804awGqaW7i61y8KnbtHmh3scqbNuTJqcycq3u5ZAU= +github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077/go.mod h1:sZZi9x5aHXGZ/RRp7Ne5rkvtDxZb7pd7vgVA+gmE35A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -2338,6 +2340,8 @@ gitlab.com/distributed_lab/running v1.6.0 h1:O2GQHgYkpNRDbobR11atOCH2rhS1okNKyad gitlab.com/distributed_lab/running v1.6.0/go.mod h1:4TnADX84dQjQMRHKIMPCVL0L97rD/Jxv0xDbrN6aKzk= gitlab.com/distributed_lab/urlval/v4 v4.0.3 h1:ZgdSBcvaoHBYmgze/u0bYfvq5Xx47pGTdfFmMYxn27s= gitlab.com/distributed_lab/urlval/v4 v4.0.3/go.mod h1:IdRM8gOyzpXNoAkIKWVwN+dChh6+1TioS/SVhTGvRFA= +gitlab.com/tokend/go v3.16.0+incompatible h1:WKpbEsqOxf7L7fc0GGx9hoPy2dCph+AtPBd1JCbGWKk= +gitlab.com/tokend/go v3.16.0+incompatible/go.mod h1:qz38YBc7VL29H2gnXbpRzcEVw2d1NUZ5SVDzIehqDCA= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= From ffb898a87066bf782af0cab9f0d4108eab682eaa Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:17:09 +0300 Subject: [PATCH 17/29] megre to origin main --- ...ints-svc@v1@public@daily_questions@admin.yaml | 6 ------ ...blic@daily_questions@admin@{question_id}.yaml | 6 ------ .../service/handlers/daily_question_create.go | 6 +++--- .../service/handlers/daily_question_delete.go | 2 +- internal/service/handlers/daily_question_edit.go | 14 +++++++------- .../service/handlers/daily_questions_select.go | 16 +++++++--------- 6 files changed, 18 insertions(+), 32 deletions(-) diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml index 2302e91..fec2cc3 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin.yaml @@ -33,12 +33,6 @@ post: $ref: '#/components/responses/invalidParameter' 401: $ref: '#/components/responses/invalidAuth' - 403: - description: Correct answer option outside the range of answer options - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/Errors' 409: description: On this day, the daily question already exists content: diff --git a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml index 561809e..6a41a74 100644 --- a/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml +++ b/docs/spec/paths/integrations@geo-points-svc@v1@public@daily_questions@admin@{question_id}.yaml @@ -73,11 +73,5 @@ patch: application/vnd.api+json: schema: $ref: '#/components/schemas/Errors' - 403: - description: Correct answer option outside the range of answer options - content: - application/vnd.api+json: - schema: - $ref: '#/components/schemas/Errors' 500: $ref: '#/components/responses/internalError' \ No newline at end of file diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index 0aff4b9..7319101 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -31,7 +31,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { err = ValidateOptions(req.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -45,7 +45,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -77,7 +77,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } if !correctAnswerFound { Log(r).Errorf("Correct answer option out of range: %v", req.CorrectAnswer) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index a746225..f0fb6ee 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -48,7 +48,7 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Only questions that start tomorrow or later can be delete: %s", timeReq.String()) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 95c3f59..dbff67a 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -53,7 +53,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { nowTime := time.Now().UTC() if !question.StartsAt.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Cannot change a question id: %v that is available today or in the past", ID) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -73,7 +73,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Argument start_at must be more or equal tommorow midnoght now its: %s", timeReq.String()) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -96,7 +96,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { l := len(question.AnswerOptions) if *req.CorrectAnswer < 0 || l <= int(*req.CorrectAnswer) { Log(r).Error("Invalid CorrectAnswer") - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } requestBody[data.ColCorrectAnswerId] = *req.CorrectAnswer @@ -106,7 +106,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { err = ValidateOptions(*req.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -131,7 +131,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { } if !correctAnswerFound { Log(r).Warnf("Correct answer option out of range: %v", question.CorrectAnswer) - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } requestBody[data.ColAnswerOption] = answerOptions @@ -140,7 +140,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { if req.Reward != nil { if *req.Reward <= 0 { Log(r).Error("Invalid Reward") - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } requestBody[data.ColReward] = *req.Reward @@ -149,7 +149,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { if req.TimeForAnswer != nil { if *req.TimeForAnswer < 0 { Log(r).Error("Invalid Time for answer") - ape.RenderErr(w, problems.Forbidden()) + ape.RenderErr(w, problems.BadRequest(err)...) return } requestBody[data.ColTimeForAnswer] = *req.TimeForAnswer diff --git a/internal/service/handlers/daily_questions_select.go b/internal/service/handlers/daily_questions_select.go index 500007e..43f3203 100644 --- a/internal/service/handlers/daily_questions_select.go +++ b/internal/service/handlers/daily_questions_select.go @@ -39,16 +39,14 @@ func FilterStartAtDailyQuestions(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.InternalError()) return } - - resp.Links = req.GetLinks(r) + questionListCount, err := DailyQuestionsQ(r).Count() + if err != nil { + Log(r).WithError(err).Error("Failed to count balances") + ape.RenderErr(w, problems.InternalError()) + return + } + resp.Links = req.GetLinks(r, uint64(questionListCount)) if req.Count { - questionListCount, err := DailyQuestionsQ(r).Count() - if err != nil { - Log(r).WithError(err).Error("Failed to count balances") - ape.RenderErr(w, problems.InternalError()) - return - } - _ = resp.PutMeta(struct { QuestionCount int64 `json:"question_count"` }{questionListCount}) From 91f7041cc0b1497c334e0e5c7e79fb3a530c1448 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:22:59 +0300 Subject: [PATCH 18/29] clean DQ --- internal/data/daily_questions.go | 2 +- internal/data/pg/daily_questions.go | 15 --------------- .../service/handlers/daily_question_create.go | 4 ++-- internal/service/handlers/daily_question_edit.go | 6 +++--- 4 files changed, 6 insertions(+), 21 deletions(-) diff --git a/internal/data/daily_questions.go b/internal/data/daily_questions.go index dccdb13..78e4ad4 100644 --- a/internal/data/daily_questions.go +++ b/internal/data/daily_questions.go @@ -14,7 +14,7 @@ const ( ColDailyQuestionTitle = "title" ColTimeForAnswer = "time_for_answer" ColAnswerOption = "answer_options" - ColCorrectAnswerId = "correct_answer" + ColCorrectAnswerID = "correct_answer" ColReward = "reward" ColStartAt = "starts_at" ColCorrectAnswers = "num_correct_answers" diff --git a/internal/data/pg/daily_questions.go b/internal/data/pg/daily_questions.go index 34f8832..8a76bd4 100644 --- a/internal/data/pg/daily_questions.go +++ b/internal/data/pg/daily_questions.go @@ -106,21 +106,6 @@ func (q *dailyQuestionsQ) Get() (*data.DailyQuestion, error) { return &res, nil } -func applyDailyQuestionPage(page *pgdb.OffsetPageParams, sql squirrel.SelectBuilder) squirrel.SelectBuilder { - if page.Limit == 0 { - page.Limit = 15 - } - if page.Order == "" { - page.Order = pgdb.OrderTypeDesc - } - - offset := page.Limit * page.PageNumber - - sql = sql.Limit(page.Limit).Offset(offset) - - return sql -} - func (q *dailyQuestionsQ) Page(page *pgdb.OffsetPageParams) data.DailyQuestionsQ { q.selector = page.ApplyTo(q.selector, "starts_at") return q diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index 7319101..aaeaaaa 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -23,7 +23,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { req, err := requests.NewDailyQuestion(r) if err != nil { - Log(r).WithError(err).Error("Error get request NewDailyQuestion: %v") + Log(r).WithError(err).Error("Error get request NewDailyQuestion") ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -44,7 +44,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { - Log(r).Errorf("Arg start_at must be more or equal tommorow midnoght noe: %s", timeReq.String()) + Log(r).Errorf("Arg start_at must be more or equal tomorow midnoght noe: %s", timeReq.String()) ape.RenderErr(w, problems.BadRequest(err)...) return } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index dbff67a..7ebd61b 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -45,7 +45,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if question == nil { - Log(r).Error("Question with ID %d not found", ID) + Log(r).Errorf("Question with ID %d not found", ID) ape.RenderErr(w, problems.NotFound()) return } @@ -72,7 +72,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { } nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { - Log(r).Errorf("Argument start_at must be more or equal tommorow midnoght now its: %s", timeReq.String()) + Log(r).Errorf("Argument start_at must be more or equal tomorow midnoght now its: %s", timeReq.String()) ape.RenderErr(w, problems.BadRequest(err)...) return } @@ -99,7 +99,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.BadRequest(err)...) return } - requestBody[data.ColCorrectAnswerId] = *req.CorrectAnswer + requestBody[data.ColCorrectAnswerID] = *req.CorrectAnswer } if req.Options != nil { From 1ce367422046e96e476fef1381bd1a1dd421fead Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:28:58 +0300 Subject: [PATCH 19/29] add go.sum go.buil --- go.mod | 2 -- go.sum | 6 ------ 2 files changed, 8 deletions(-) diff --git a/go.mod b/go.mod index 95915f4..1e7dcf0 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,6 @@ require ( gitlab.com/distributed_lab/logan v3.8.1+incompatible gitlab.com/distributed_lab/running v1.6.0 gitlab.com/distributed_lab/urlval/v4 v4.0.3 - gitlab.com/tokend/go v3.16.0+incompatible ) require ( @@ -111,7 +110,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/mtibben/percent v0.2.1 // indirect - github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pelletier/go-toml/v2 v2.1.1 // indirect github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect diff --git a/go.sum b/go.sum index 31de4a6..761f336 100644 --- a/go.sum +++ b/go.sum @@ -1996,8 +1996,6 @@ github.com/nats-io/nkeys v0.4.5/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5s github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077 h1:A804awGqaW7i61y8KnbtHmh3scqbNuTJqcycq3u5ZAU= -github.com/nullstyle/go-xdr v0.0.0-20180726165426-f4c839f75077/go.mod h1:sZZi9x5aHXGZ/RRp7Ne5rkvtDxZb7pd7vgVA+gmE35A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -2119,8 +2117,6 @@ github.com/rarimo/geo-auth-svc v1.1.0 h1:3k1tTWAjtCBsnzlMb3aB+xgsFLEPUSmB3woME+q github.com/rarimo/geo-auth-svc v1.1.0/go.mod h1:JrpCGdT0xtAcWIKgPhxPHf7QCW4h845BXuh6M7NdQFw= github.com/rarimo/saver-grpc-lib v1.0.0 h1:MGUVjYg7unmodYczVsLqlqZNkT4CIgKqdo6aQtL1qdE= github.com/rarimo/saver-grpc-lib v1.0.0/go.mod h1:DpugWK5B7Hi0bdC3MPe/9FD2zCxaRwsyykdwxtF1Zgg= -github.com/rarimo/zkverifier-kit v1.2.0 h1:Qsdcq+jMBEkdTlbqGT7InQhNI39lZyCX9PXgqzb1ozM= -github.com/rarimo/zkverifier-kit v1.2.0/go.mod h1:3YDg5dTkDRr4IdfaDHGYetopd6gS/2SuwSeseYTWwNw= github.com/rarimo/zkverifier-kit v1.2.2 h1:pezzkNNjz6gSzfiR5eP+fcvB/UOkYWPaRdnkA7RaGPE= github.com/rarimo/zkverifier-kit v1.2.2/go.mod h1:3YDg5dTkDRr4IdfaDHGYetopd6gS/2SuwSeseYTWwNw= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= @@ -2342,8 +2338,6 @@ gitlab.com/distributed_lab/running v1.6.0 h1:O2GQHgYkpNRDbobR11atOCH2rhS1okNKyad gitlab.com/distributed_lab/running v1.6.0/go.mod h1:4TnADX84dQjQMRHKIMPCVL0L97rD/Jxv0xDbrN6aKzk= gitlab.com/distributed_lab/urlval/v4 v4.0.3 h1:ZgdSBcvaoHBYmgze/u0bYfvq5Xx47pGTdfFmMYxn27s= gitlab.com/distributed_lab/urlval/v4 v4.0.3/go.mod h1:IdRM8gOyzpXNoAkIKWVwN+dChh6+1TioS/SVhTGvRFA= -gitlab.com/tokend/go v3.16.0+incompatible h1:WKpbEsqOxf7L7fc0GGx9hoPy2dCph+AtPBd1JCbGWKk= -gitlab.com/tokend/go v3.16.0+incompatible/go.mod h1:qz38YBc7VL29H2gnXbpRzcEVw2d1NUZ5SVDzIehqDCA= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= go.etcd.io/etcd/api/v3 v3.5.10/go.mod h1:TidfmT4Uycad3NM/o25fG3J07odo4GBB9hoxaodFCtI= From 59defa7c53a3e8a8fb509f89edba647181bf2405 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:34:00 +0300 Subject: [PATCH 20/29] fix resp err add return --- internal/service/handlers/daily_question_delete.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/service/handlers/daily_question_delete.go b/internal/service/handlers/daily_question_delete.go index f0fb6ee..8c0ad09 100644 --- a/internal/service/handlers/daily_question_delete.go +++ b/internal/service/handlers/daily_question_delete.go @@ -63,6 +63,7 @@ func DeleteDailyQuestion(w http.ResponseWriter, r *http.Request) { if err != nil { Log(r).WithError(err).Error("Error deleting daily question") ape.RenderErr(w, problems.InternalError()) + return } ape.Render(w, response) } From 1cbcf9689a06d751f50b164b6b52afd7d4c249ae Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:41:55 +0300 Subject: [PATCH 21/29] handle error daily_question_edit --- internal/service/handlers/daily_question_edit.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 7ebd61b..29545fa 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -162,7 +162,18 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - questionNew, _ := DailyQuestionsQ(r).FilterByID(ID).Get() + questionNew, err := DailyQuestionsQ(r).FilterByID(ID).Get() + if err != nil { + Log(r).WithError(err).Error("Error on this day") + ape.RenderErr(w, problems.InternalError()) + return + } + if questionNew == nil { + Log(r).Errorf("Error get qurstion for response") + ape.RenderErr(w, problems.InternalError()) + return + } + resp, err := NewDailyQuestionEdite(ID, questionNew) if err != nil { Log(r).WithError(err).Error("Error editing daily question") From d76b8c9878ed3ac1735da3e797e00b6d5b8023db Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 18:51:02 +0300 Subject: [PATCH 22/29] handle error daily_question_create --- internal/service/handlers/daily_question_create.go | 12 +++++++++++- internal/service/handlers/daily_question_edit.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index aaeaaaa..6be5fd3 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -97,7 +97,17 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - question, _ = DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() + question, err = DailyQuestionsQ(r).FilterDayQuestions(location, timeReq).Get() + if err != nil { + Log(r).WithError(err).Error("Error on this day") + ape.RenderErr(w, problems.InternalError()) + return + } + if question == nil { + Log(r).Errorf("Error get question for response") + ape.RenderErr(w, problems.InternalError()) + return + } ape.Render(w, NewDailyQuestionCreate(&stmt, req.Options, question.ID)) } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 29545fa..86eaf0d 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -169,7 +169,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if questionNew == nil { - Log(r).Errorf("Error get qurstion for response") + Log(r).Errorf("Error get question for response") ape.RenderErr(w, problems.InternalError()) return } From 19056e7a348739d4b4dfe77d07de3d265faa0059 Mon Sep 17 00:00:00 2001 From: Zaptoss Date: Wed, 28 Aug 2024 19:27:25 +0300 Subject: [PATCH 23/29] Fix options unmarshaling --- internal/data/daily_questions.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/data/daily_questions.go b/internal/data/daily_questions.go index 78e4ad4..b9e32d5 100644 --- a/internal/data/daily_questions.go +++ b/internal/data/daily_questions.go @@ -58,13 +58,11 @@ type DailyQuestionsQ interface { } func (q *DailyQuestion) ExtractOptions() ([]resources.DailyQuestionOptions, error) { - var options struct { - Options []resources.DailyQuestionOptions `json:"options"` - } + var options []resources.DailyQuestionOptions err := json.NewDecoder(bytes.NewReader(q.AnswerOptions)).Decode(&options) if err != nil { return nil, fmt.Errorf("failed to unmarshal question options: %w", err) } - return options.Options, nil + return options, nil } From afcbebfb03cf138f753ee7b35aae3fa1907ca627 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 21:36:07 +0300 Subject: [PATCH 24/29] fix req --- .../service/handlers/daily_question_create.go | 23 +++++----- .../service/handlers/daily_question_edit.go | 42 ++++++++++--------- .../handlers/daily_questions_select.go | 2 +- 3 files changed, 35 insertions(+), 32 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index 6be5fd3..fe3b042 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -27,8 +27,9 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.BadRequest(err)...) return } + attributes := req.Data.Attributes - err = ValidateOptions(req.Options) + err = ValidateOptions(attributes.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") ape.RenderErr(w, problems.BadRequest(err)...) @@ -36,7 +37,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } location := DailyQuestions(r).Location - timeReq, err := time.Parse("2006-01-02", req.StartsAt) + timeReq, err := time.Parse("2006-01-02", attributes.StartsAt) if err != nil { Log(r).WithError(err).Error("Failed to parse start time") ape.RenderErr(w, problems.BadRequest(err)...) @@ -61,7 +62,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - answerOptions, err := json.Marshal(req.Options) + answerOptions, err := json.Marshal(attributes.Options) if err != nil { Log(r).WithError(err).Error("Failed to get questions") ape.RenderErr(w, problems.InternalError()) @@ -69,24 +70,24 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } correctAnswerFound := false - for _, option := range req.Options { - if option.Id == int(req.CorrectAnswer) { + for _, option := range attributes.Options { + if option.Id == int(attributes.CorrectAnswer) { correctAnswerFound = true break } } if !correctAnswerFound { - Log(r).Errorf("Correct answer option out of range: %v", req.CorrectAnswer) + Log(r).Errorf("Correct answer option out of range: %v", attributes.CorrectAnswer) ape.RenderErr(w, problems.BadRequest(err)...) return } stmt := data.DailyQuestion{ - Title: req.Title, - TimeForAnswer: req.TimeForAnswer, - Reward: req.Reward, + Title: attributes.Title, + TimeForAnswer: attributes.TimeForAnswer, + Reward: attributes.Reward, AnswerOptions: answerOptions, - CorrectAnswer: req.CorrectAnswer, + CorrectAnswer: attributes.CorrectAnswer, StartsAt: timeReq, } @@ -109,7 +110,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { return } - ape.Render(w, NewDailyQuestionCreate(&stmt, req.Options, question.ID)) + ape.Render(w, NewDailyQuestionCreate(&stmt, attributes.Options, question.ID)) } func ValidateOptions(options []resources.DailyQuestionOptions) error { diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 86eaf0d..85c284d 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -33,10 +33,12 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { req, err := requests.NewDailyQuestionEdit(r) if err != nil { + Log(r).WithError(err).Error("Error creating daily question edit request") ape.RenderErr(w, problems.InternalError()) return } + attributes := req.Data.Attributes question, err := DailyQuestionsQ(r).FilterByID(ID).Get() if err != nil { @@ -59,12 +61,12 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { requestBody := map[string]any{} - if req.Title != nil { - requestBody[data.ColDailyQuestionTitle] = *req.Title + if attributes.Title != nil { + requestBody[data.ColDailyQuestionTitle] = *attributes.Title } - if req.StartsAt != nil { - timeReq, err := time.Parse("2006-01-02", *req.StartsAt) + if attributes.StartsAt != nil { + timeReq, err := time.Parse("2006-01-02", *attributes.StartsAt) if err != nil { Log(r).WithError(err).Error("Failed to parse start time") ape.RenderErr(w, problems.BadRequest(err)...) @@ -89,28 +91,28 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ape.RenderErr(w, problems.Conflict()) return } - requestBody[data.ColStartAt] = req.StartsAt + requestBody[data.ColStartAt] = attributes.StartsAt } - if req.CorrectAnswer != nil { + if attributes.CorrectAnswer != nil { l := len(question.AnswerOptions) - if *req.CorrectAnswer < 0 || l <= int(*req.CorrectAnswer) { + if *attributes.CorrectAnswer < 0 || l <= int(*attributes.CorrectAnswer) { Log(r).Error("Invalid CorrectAnswer") ape.RenderErr(w, problems.BadRequest(err)...) return } - requestBody[data.ColCorrectAnswerID] = *req.CorrectAnswer + requestBody[data.ColCorrectAnswerID] = *attributes.CorrectAnswer } - if req.Options != nil { - err = ValidateOptions(*req.Options) + if attributes.Options != nil { + err = ValidateOptions(*attributes.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") ape.RenderErr(w, problems.BadRequest(err)...) return } - answerOptions, err := json.Marshal(req.Options) + answerOptions, err := json.Marshal(attributes.Options) if err != nil { Log(r).Errorf("Error marshalling answer options: %v", err) ape.RenderErr(w, problems.InternalError()) @@ -119,11 +121,11 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { correctAnswerFound := false var localCorrectAnswer int64 - if req.CorrectAnswer != nil { - localCorrectAnswer = *req.CorrectAnswer + if attributes.CorrectAnswer != nil { + localCorrectAnswer = *attributes.CorrectAnswer } - for _, option := range *req.Options { + for _, option := range *attributes.Options { if option.Id == int(localCorrectAnswer) { correctAnswerFound = true break @@ -137,22 +139,22 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { requestBody[data.ColAnswerOption] = answerOptions } - if req.Reward != nil { - if *req.Reward <= 0 { + if attributes.Reward != nil { + if *attributes.Reward <= 0 { Log(r).Error("Invalid Reward") ape.RenderErr(w, problems.BadRequest(err)...) return } - requestBody[data.ColReward] = *req.Reward + requestBody[data.ColReward] = *attributes.Reward } - if req.TimeForAnswer != nil { - if *req.TimeForAnswer < 0 { + if attributes.TimeForAnswer != nil { + if *attributes.TimeForAnswer < 0 { Log(r).Error("Invalid Time for answer") ape.RenderErr(w, problems.BadRequest(err)...) return } - requestBody[data.ColTimeForAnswer] = *req.TimeForAnswer + requestBody[data.ColTimeForAnswer] = *attributes.TimeForAnswer } err = DailyQuestionsQ(r).FilterByID(ID).Update(requestBody) diff --git a/internal/service/handlers/daily_questions_select.go b/internal/service/handlers/daily_questions_select.go index 43f3203..f11a009 100644 --- a/internal/service/handlers/daily_questions_select.go +++ b/internal/service/handlers/daily_questions_select.go @@ -85,7 +85,7 @@ func NewDailyQuestionsFilterDate(questions []data.DailyQuestion) (resources.Dail for i, q := range questions { qModel, err := NewDailyQuestionModel(q) if err != nil { - return resources.DailyQuestionDetailsListResponse{}, fmt.Errorf("error make daily question model, %s", err) + return resources.DailyQuestionDetailsListResponse{}, fmt.Errorf("error make %s daily question model, %s", q, err) } list[i] = qModel } From 864e0bc4837c2dd60c668fd5b864a83e03ac95f8 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Wed, 28 Aug 2024 21:38:18 +0300 Subject: [PATCH 25/29] fix fix req --- internal/service/requests/daily_question_create.go | 14 +++++++------- internal/service/requests/daily_question_edit.go | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/internal/service/requests/daily_question_create.go b/internal/service/requests/daily_question_create.go index 17c1366..5c101e7 100644 --- a/internal/service/requests/daily_question_create.go +++ b/internal/service/requests/daily_question_create.go @@ -8,18 +8,18 @@ import ( "github.com/rarimo/geo-points-svc/resources" ) -func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionCreateAttributes, err error) { +func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionCreateResponse, err error) { if err = json.NewDecoder(r.Body).Decode(&req); err != nil { err = newDecodeError("body", err) return } return req, validation.Errors{ - "data/attributes/time_for_answer": validation.Validate(req.TimeForAnswer, validation.Required), - "data/attributes/correct_answer": validation.Validate(req.CorrectAnswer, validation.Required), - "data/attributes/starts_at": validation.Validate(req.StartsAt, validation.Required), - "data/attributes/options": validation.Validate(req.Options, validation.Required), - "data/attributes/reward": validation.Validate(req.Reward, validation.Required), - "data/attributes/title": validation.Validate(req.Title, validation.Required), + "data/attributes/time_for_answer": validation.Validate(req.Data.Attributes.TimeForAnswer, validation.Required), + "data/attributes/correct_answer": validation.Validate(req.Data.Attributes.CorrectAnswer, validation.Required), + "data/attributes/starts_at": validation.Validate(req.Data.Attributes.StartsAt, validation.Required), + "data/attributes/options": validation.Validate(req.Data.Attributes.Options, validation.Required), + "data/attributes/reward": validation.Validate(req.Data.Attributes.Reward, validation.Required), + "data/attributes/title": validation.Validate(req.Data.Attributes.Title, validation.Required), }.Filter() } diff --git a/internal/service/requests/daily_question_edit.go b/internal/service/requests/daily_question_edit.go index 19aa9e3..6bb6e2f 100644 --- a/internal/service/requests/daily_question_edit.go +++ b/internal/service/requests/daily_question_edit.go @@ -8,18 +8,18 @@ import ( "github.com/rarimo/geo-points-svc/resources" ) -func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditAttributes, err error) { +func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditResponse, err error) { if err = json.NewDecoder(r.Body).Decode(&req); err != nil { err = newDecodeError("body", err) return } return req, validation.Errors{ - "time_for_answer": validation.Validate(&req.TimeForAnswer), - "correct_answer": validation.Validate(&req.CorrectAnswer), - "starts_at": validation.Validate(&req.StartsAt), - "options": validation.Validate(&req.Options), - "reward": validation.Validate(&req.Reward), - "title": validation.Validate(&req.Title), + "time_for_answer": validation.Validate(&req.Data.Attributes.TimeForAnswer), + "correct_answer": validation.Validate(&req.Data.Attributes.CorrectAnswer), + "starts_at": validation.Validate(&req.Data.Attributes.StartsAt), + "options": validation.Validate(&req.Data.Attributes.Options), + "reward": validation.Validate(&req.Data.Attributes.Reward), + "title": validation.Validate(&req.Data.Attributes.Title), }.Filter() } From 543a0360d731f7c100305e53afaaa65a22557efc Mon Sep 17 00:00:00 2001 From: trpdjke Date: Thu, 29 Aug 2024 03:15:52 +0300 Subject: [PATCH 26/29] change requests --- internal/service/handlers/daily_question_edit.go | 3 +-- internal/service/requests/daily_question_create.go | 9 +++------ internal/service/requests/daily_question_edit.go | 9 +++------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 85c284d..b1ae6b7 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -33,7 +33,6 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { req, err := requests.NewDailyQuestionEdit(r) if err != nil { - Log(r).WithError(err).Error("Error creating daily question edit request") ape.RenderErr(w, problems.InternalError()) return @@ -87,7 +86,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } if question != nil && ID != question.ID { - Log(r).Errorf("Error on this day %v, the daily question already has %v", question.StartsAt, question) + Log(r).Errorf("Error on this day %s, the daily question already has %s", question.StartsAt.String(), question) ape.RenderErr(w, problems.Conflict()) return } diff --git a/internal/service/requests/daily_question_create.go b/internal/service/requests/daily_question_create.go index 5c101e7..f844d59 100644 --- a/internal/service/requests/daily_question_create.go +++ b/internal/service/requests/daily_question_create.go @@ -15,11 +15,8 @@ func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionCreateRespons } return req, validation.Errors{ - "data/attributes/time_for_answer": validation.Validate(req.Data.Attributes.TimeForAnswer, validation.Required), - "data/attributes/correct_answer": validation.Validate(req.Data.Attributes.CorrectAnswer, validation.Required), - "data/attributes/starts_at": validation.Validate(req.Data.Attributes.StartsAt, validation.Required), - "data/attributes/options": validation.Validate(req.Data.Attributes.Options, validation.Required), - "data/attributes/reward": validation.Validate(req.Data.Attributes.Reward, validation.Required), - "data/attributes/title": validation.Validate(req.Data.Attributes.Title, validation.Required), + "data/id": validation.Validate(&req.Data.ID, validation.Required), + "data/type": validation.Validate(&req.Data.Type, validation.Required), + "data/attributes": validation.Validate(&req.Data.Attributes, validation.Required), }.Filter() } diff --git a/internal/service/requests/daily_question_edit.go b/internal/service/requests/daily_question_edit.go index 6bb6e2f..2b43e93 100644 --- a/internal/service/requests/daily_question_edit.go +++ b/internal/service/requests/daily_question_edit.go @@ -15,11 +15,8 @@ func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditRespo } return req, validation.Errors{ - "time_for_answer": validation.Validate(&req.Data.Attributes.TimeForAnswer), - "correct_answer": validation.Validate(&req.Data.Attributes.CorrectAnswer), - "starts_at": validation.Validate(&req.Data.Attributes.StartsAt), - "options": validation.Validate(&req.Data.Attributes.Options), - "reward": validation.Validate(&req.Data.Attributes.Reward), - "title": validation.Validate(&req.Data.Attributes.Title), + "data/id": validation.Validate(&req.Data.ID, validation.Required), + "data/type": validation.Validate(&req.Data.Type, validation.Required), + "data/attributes": validation.Validate(&req.Data.Attributes, validation.Required), }.Filter() } From 1382486afc87764010b5380ddd7db69bdd40b5ce Mon Sep 17 00:00:00 2001 From: trpdjke Date: Thu, 29 Aug 2024 12:28:02 +0300 Subject: [PATCH 27/29] validate type for req in DQ --- internal/service/handlers/daily_question_create.go | 7 +++++++ internal/service/handlers/daily_question_edit.go | 6 ++++++ internal/service/requests/daily_question_create.go | 2 +- internal/service/requests/daily_question_edit.go | 2 +- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index fe3b042..a1ddaec 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -29,6 +29,13 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } attributes := req.Data.Attributes + if req.Data.Type != resources.DAILY_QUESTIONS { + err := fmt.Errorf("invalid request data type %s", req.Data.Type) + Log(r).WithError(err).Error("Invalid data type") + ape.RenderErr(w, problems.BadRequest(err)...) + return + } + err = ValidateOptions(attributes.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index b1ae6b7..64ecf18 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -38,6 +38,12 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { return } attributes := req.Data.Attributes + if req.Data.Type != resources.DAILY_QUESTIONS { + err := fmt.Errorf("invalid request data type %s", req.Data.Type) + Log(r).WithError(err).Error("Invalid data type") + ape.RenderErr(w, problems.BadRequest(err)...) + return + } question, err := DailyQuestionsQ(r).FilterByID(ID).Get() if err != nil { diff --git a/internal/service/requests/daily_question_create.go b/internal/service/requests/daily_question_create.go index f844d59..7ddf96d 100644 --- a/internal/service/requests/daily_question_create.go +++ b/internal/service/requests/daily_question_create.go @@ -15,7 +15,7 @@ func NewDailyQuestion(r *http.Request) (req resources.DailyQuestionCreateRespons } return req, validation.Errors{ - "data/id": validation.Validate(&req.Data.ID, validation.Required), + "data/id": validation.Validate(&req.Data.ID), "data/type": validation.Validate(&req.Data.Type, validation.Required), "data/attributes": validation.Validate(&req.Data.Attributes, validation.Required), }.Filter() diff --git a/internal/service/requests/daily_question_edit.go b/internal/service/requests/daily_question_edit.go index 2b43e93..9017b4d 100644 --- a/internal/service/requests/daily_question_edit.go +++ b/internal/service/requests/daily_question_edit.go @@ -15,7 +15,7 @@ func NewDailyQuestionEdit(r *http.Request) (req resources.DailyQuestionEditRespo } return req, validation.Errors{ - "data/id": validation.Validate(&req.Data.ID, validation.Required), + "data/id": validation.Validate(&req.Data.ID), "data/type": validation.Validate(&req.Data.Type, validation.Required), "data/attributes": validation.Validate(&req.Data.Attributes, validation.Required), }.Filter() From 44f57f9d7f5c987de8e991a06ddc7ba3760e7fac Mon Sep 17 00:00:00 2001 From: trpdjke Date: Thu, 29 Aug 2024 16:41:31 +0300 Subject: [PATCH 28/29] fix the bug with -reward, add description for bad request --- .../service/handlers/daily_question_create.go | 31 ++++++++++--- .../service/handlers/daily_question_edit.go | 44 ++++++++++++++----- 2 files changed, 59 insertions(+), 16 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index a1ddaec..dbedd37 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -7,6 +7,7 @@ import ( "sort" "time" + validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" @@ -32,14 +33,18 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { if req.Data.Type != resources.DAILY_QUESTIONS { err := fmt.Errorf("invalid request data type %s", req.Data.Type) Log(r).WithError(err).Error("Invalid data type") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "type": fmt.Errorf("%v not alowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), + })...) return } err = ValidateOptions(attributes.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "options": fmt.Errorf("invalid options: %v, err: %s", attributes.Options, err), + })...) return } @@ -47,13 +52,17 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { timeReq, err := time.Parse("2006-01-02", attributes.StartsAt) if err != nil { Log(r).WithError(err).Error("Failed to parse start time") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "starts_at": fmt.Errorf("failed to parse start time %s err: %s", attributes.StartsAt, err), + })...) return } nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Arg start_at must be more or equal tomorow midnoght noe: %s", timeReq.String()) - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "starts_at": fmt.Errorf("argument start_at must be more or equal tomorow midnoght now its: %s", timeReq.String()), + })...) return } @@ -85,7 +94,19 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { } if !correctAnswerFound { Log(r).Errorf("Correct answer option out of range: %v", attributes.CorrectAnswer) - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest( + validation.Errors{ + "correct_answer": fmt.Errorf("correct answer option out of range %v", attributes.CorrectAnswer), + })...) + return + } + + if attributes.Reward <= 0 { + Log(r).Errorf("Reward option out of range: %v", attributes.Reward) + ape.RenderErr(w, problems.BadRequest( + validation.Errors{ + "reward": fmt.Errorf("reward less than or equal to 0 reward: %v", attributes.Reward), + })...) return } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index 64ecf18..dee1f6a 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -9,6 +9,7 @@ import ( "time" "github.com/go-chi/chi" + validation "github.com/go-ozzo/ozzo-validation/v4" "github.com/rarimo/geo-auth-svc/pkg/auth" "github.com/rarimo/geo-points-svc/internal/data" "github.com/rarimo/geo-points-svc/internal/service/requests" @@ -27,21 +28,25 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { ID, err := strconv.ParseInt(IDStr, 10, 64) if err != nil { Log(r).WithError(err).Error("Failed to parse ID") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "query": fmt.Errorf("failed to parse ID: %v, err: %s", ID, err), + })...) return } req, err := requests.NewDailyQuestionEdit(r) if err != nil { Log(r).WithError(err).Error("Error creating daily question edit request") - ape.RenderErr(w, problems.InternalError()) + ape.RenderErr(w, problems.BadRequest(err)...) return } attributes := req.Data.Attributes if req.Data.Type != resources.DAILY_QUESTIONS { err := fmt.Errorf("invalid request data type %s", req.Data.Type) Log(r).WithError(err).Error("Invalid data type") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "type": fmt.Errorf("%v not alowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), + })...) return } @@ -60,7 +65,9 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { nowTime := time.Now().UTC() if !question.StartsAt.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Cannot change a question id: %v that is available today or in the past", ID) - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "starts_at": fmt.Errorf("cannot change a question id: %v that is available today or in the past", ID), + })...) return } @@ -74,13 +81,17 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { timeReq, err := time.Parse("2006-01-02", *attributes.StartsAt) if err != nil { Log(r).WithError(err).Error("Failed to parse start time") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "starts_at": fmt.Errorf("failed to parse start time %s err: %s", *attributes.StartsAt, err), + })...) return } nowTime := time.Now().UTC() if !timeReq.After(time.Date(nowTime.Year(), nowTime.Month(), nowTime.Day()+1, 0, 0, 0, 0, DailyQuestions(r).Location)) { Log(r).Errorf("Argument start_at must be more or equal tomorow midnoght now its: %s", timeReq.String()) - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "starts_at": fmt.Errorf("argument start_at must be more or equal tomorow midnoght now its: %s", timeReq.String()), + })...) return } @@ -103,7 +114,9 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { l := len(question.AnswerOptions) if *attributes.CorrectAnswer < 0 || l <= int(*attributes.CorrectAnswer) { Log(r).Error("Invalid CorrectAnswer") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "correct_answer": fmt.Errorf("invalid value for correct_answer: %v", *attributes.CorrectAnswer), + })...) return } requestBody[data.ColCorrectAnswerID] = *attributes.CorrectAnswer @@ -113,7 +126,9 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { err = ValidateOptions(*attributes.Options) if err != nil { Log(r).WithError(err).Error("Error Answer Options") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "options": fmt.Errorf("invalid options: %v, err: %s", *attributes.Options, err), + })...) return } @@ -138,7 +153,10 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { } if !correctAnswerFound { Log(r).Warnf("Correct answer option out of range: %v", question.CorrectAnswer) - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest( + validation.Errors{ + "correct_answer": fmt.Errorf("correct answer option out of range %v", localCorrectAnswer), + })...) return } requestBody[data.ColAnswerOption] = answerOptions @@ -147,7 +165,9 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { if attributes.Reward != nil { if *attributes.Reward <= 0 { Log(r).Error("Invalid Reward") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "reward": fmt.Errorf("reward less than or equal to 0 reward: %v", attributes.Reward), + })...) return } requestBody[data.ColReward] = *attributes.Reward @@ -156,7 +176,9 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { if attributes.TimeForAnswer != nil { if *attributes.TimeForAnswer < 0 { Log(r).Error("Invalid Time for answer") - ape.RenderErr(w, problems.BadRequest(err)...) + ape.RenderErr(w, problems.BadRequest(validation.Errors{ + "time_for_answer": fmt.Errorf("invalid value for time_for_answer: %v", *attributes.TimeForAnswer), + })...) return } requestBody[data.ColTimeForAnswer] = *attributes.TimeForAnswer From 394e269c039284f072105fa68eac9d213f1b20b1 Mon Sep 17 00:00:00 2001 From: trpdjke Date: Thu, 29 Aug 2024 16:46:15 +0300 Subject: [PATCH 29/29] fix misspell --- internal/service/handlers/daily_question_create.go | 2 +- internal/service/handlers/daily_question_edit.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/service/handlers/daily_question_create.go b/internal/service/handlers/daily_question_create.go index dbedd37..3fd5d1b 100644 --- a/internal/service/handlers/daily_question_create.go +++ b/internal/service/handlers/daily_question_create.go @@ -34,7 +34,7 @@ func CreateDailyQuestion(w http.ResponseWriter, r *http.Request) { err := fmt.Errorf("invalid request data type %s", req.Data.Type) Log(r).WithError(err).Error("Invalid data type") ape.RenderErr(w, problems.BadRequest(validation.Errors{ - "type": fmt.Errorf("%v not alowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), + "type": fmt.Errorf("%v not allowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), })...) return } diff --git a/internal/service/handlers/daily_question_edit.go b/internal/service/handlers/daily_question_edit.go index dee1f6a..19380b6 100644 --- a/internal/service/handlers/daily_question_edit.go +++ b/internal/service/handlers/daily_question_edit.go @@ -45,7 +45,7 @@ func EditDailyQuestion(w http.ResponseWriter, r *http.Request) { err := fmt.Errorf("invalid request data type %s", req.Data.Type) Log(r).WithError(err).Error("Invalid data type") ape.RenderErr(w, problems.BadRequest(validation.Errors{ - "type": fmt.Errorf("%v not alowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), + "type": fmt.Errorf("%v not allowed for this endpoint, must be %v err: %s", req.Data.Type, resources.DAILY_QUESTIONS, err), })...) return }