From 3215646485f9973d97655f193b868322afadada9 Mon Sep 17 00:00:00 2001 From: Vladimir Pestov Date: Sun, 21 Apr 2024 14:14:13 +0500 Subject: [PATCH] feat: Add penalty endpoint --- src/cmd/questspace/main.go | 1 + src/docs/docs.go | 106 +++++++++++++++++++-------- src/docs/swagger.json | 106 +++++++++++++++++++-------- src/docs/swagger.yaml | 70 ++++++++++++------ src/internal/handlers/play/play.go | 48 ++++++++++++ src/internal/questspace/game/game.go | 23 ++++++ 6 files changed, 268 insertions(+), 86 deletions(-) diff --git a/src/cmd/questspace/main.go b/src/cmd/questspace/main.go index 930fe5a..8ee20bd 100644 --- a/src/cmd/questspace/main.go +++ b/src/cmd/questspace/main.go @@ -143,6 +143,7 @@ func Init(app application.App) error { questGroup.POST("/:id/answer", jwt.AuthMiddlewareStrict(jwtParser), application.AsGinHandler(playHandler.HandleTryAnswer)) questGroup.GET("/:id/table", jwt.AuthMiddlewareStrict(jwtParser), application.AsGinHandler(playHandler.HandleGetTableResults)) questGroup.GET("/:id/leaderboard", application.AsGinHandler(playHandler.HandleLeaderboard)) + questGroup.POST("/:id/penalty", jwt.AuthMiddlewareStrict(jwtParser), application.AsGinHandler(playHandler.HandleAddPenalty)) app.Router().GET("/swagger/*any", ginswagger.WrapHandler(swaggerfiles.Handler)) return nil diff --git a/src/docs/docs.go b/src/docs/docs.go index d7ad307..93272a9 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -351,6 +351,54 @@ const docTemplate = `{ } } }, + "/quest/{id}/penalty": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PlayMode" + ], + "summary": "Add penalty to team", + "parameters": [ + { + "type": "string", + "description": "Quest ID", + "name": "quest_id", + "in": "path", + "required": true + }, + { + "description": "Data to set penalty", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/game.AddPenaltyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not Found" + }, + "406": { + "description": "Not Acceptable" + } + } + } + }, "/quest/{id}/play": { "get": { "security": [ @@ -1251,6 +1299,17 @@ const docTemplate = `{ } } }, + "game.AddPenaltyRequest": { + "type": "object", + "properties": { + "penalty": { + "type": "integer" + }, + "team_id": { + "type": "string" + } + } + }, "game.AnswerDataResponse": { "type": "object", "properties": { @@ -1376,32 +1435,9 @@ const docTemplate = `{ } } }, - "game.TaskGroupResult": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/game.TaskResult" - } - } - } - }, "game.TaskResult": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, "score": { "type": "integer" } @@ -1410,22 +1446,22 @@ const docTemplate = `{ "game.TeamResult": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, "penalty": { "type": "integer" }, - "task_groups": { + "taskResults": { "type": "array", "items": { - "$ref": "#/definitions/game.TaskGroupResult" + "$ref": "#/definitions/game.TaskResult" } }, - "total_score": { + "teamID": { + "type": "string" + }, + "teamName": { + "type": "string" + }, + "totalScore": { "type": "integer" } } @@ -1438,6 +1474,12 @@ const docTemplate = `{ "items": { "$ref": "#/definitions/game.TeamResult" } + }, + "task_groups": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.TaskGroup" + } } } }, diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 74d8bf8..45e2bbb 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -340,6 +340,54 @@ } } }, + "/quest/{id}/penalty": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "tags": [ + "PlayMode" + ], + "summary": "Add penalty to team", + "parameters": [ + { + "type": "string", + "description": "Quest ID", + "name": "quest_id", + "in": "path", + "required": true + }, + { + "description": "Data to set penalty", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/game.AddPenaltyRequest" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "400": { + "description": "Bad Request" + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not Found" + }, + "406": { + "description": "Not Acceptable" + } + } + } + }, "/quest/{id}/play": { "get": { "security": [ @@ -1240,6 +1288,17 @@ } } }, + "game.AddPenaltyRequest": { + "type": "object", + "properties": { + "penalty": { + "type": "integer" + }, + "team_id": { + "type": "string" + } + } + }, "game.AnswerDataResponse": { "type": "object", "properties": { @@ -1365,32 +1424,9 @@ } } }, - "game.TaskGroupResult": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "tasks": { - "type": "array", - "items": { - "$ref": "#/definitions/game.TaskResult" - } - } - } - }, "game.TaskResult": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, "score": { "type": "integer" } @@ -1399,22 +1435,22 @@ "game.TeamResult": { "type": "object", "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, "penalty": { "type": "integer" }, - "task_groups": { + "taskResults": { "type": "array", "items": { - "$ref": "#/definitions/game.TaskGroupResult" + "$ref": "#/definitions/game.TaskResult" } }, - "total_score": { + "teamID": { + "type": "string" + }, + "teamName": { + "type": "string" + }, + "totalScore": { "type": "integer" } } @@ -1427,6 +1463,12 @@ "items": { "$ref": "#/definitions/game.TeamResult" } + }, + "task_groups": { + "type": "array", + "items": { + "$ref": "#/definitions/storage.TaskGroup" + } } } }, diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index 26ce783..3d44d63 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -13,6 +13,13 @@ definitions: username: type: string type: object + game.AddPenaltyRequest: + properties: + penalty: + type: integer + team_id: + type: string + type: object game.AnswerDataResponse: properties: quest: @@ -93,39 +100,24 @@ definitions: team_name: type: string type: object - game.TaskGroupResult: - properties: - id: - type: string - name: - type: string - tasks: - items: - $ref: '#/definitions/game.TaskResult' - type: array - type: object game.TaskResult: properties: - id: - type: string - name: - type: string score: type: integer type: object game.TeamResult: properties: - id: - type: string - name: - type: string penalty: type: integer - task_groups: + taskResults: items: - $ref: '#/definitions/game.TaskGroupResult' + $ref: '#/definitions/game.TaskResult' type: array - total_score: + teamID: + type: string + teamName: + type: string + totalScore: type: integer type: object game.TeamResults: @@ -134,6 +126,10 @@ definitions: items: $ref: '#/definitions/game.TeamResult' type: array + task_groups: + items: + $ref: '#/definitions/storage.TaskGroup' + type: array type: object game.TryAnswerResponse: properties: @@ -802,6 +798,36 @@ paths: summary: Get leaderboard table with final results tags: - PlayMode + /quest/{id}/penalty: + post: + parameters: + - description: Quest ID + in: path + name: quest_id + required: true + type: string + - description: Data to set penalty + in: body + name: request + required: true + schema: + $ref: '#/definitions/game.AddPenaltyRequest' + responses: + "200": + description: OK + "400": + description: Bad Request + "401": + description: Unauthorized + "404": + description: Not Found + "406": + description: Not Acceptable + security: + - ApiKeyAuth: [] + summary: Add penalty to team + tags: + - PlayMode /quest/{id}/play: get: parameters: diff --git a/src/internal/handlers/play/play.go b/src/internal/handlers/play/play.go index 3eac1be..6bcfb6d 100644 --- a/src/internal/handlers/play/play.go +++ b/src/internal/handlers/play/play.go @@ -299,3 +299,51 @@ func (h *Handler) HandleLeaderboard(c *gin.Context) error { c.JSON(http.StatusOK, leaderBoard) return nil } + +// HandleAddPenalty handles POST quest/:id/penalty request +// +// @Summary Add penalty to team +// @Tags PlayMode +// @Param quest_id path string true "Quest ID" +// @Param request body game.AddPenaltyRequest true "Data to set penalty" +// @Success 200 +// @Failure 400 +// @Failure 401 +// @Failure 404 +// @Failure 406 +// @Router /quest/{id}/penalty [post] +// @Security ApiKeyAuth +func (h *Handler) HandleAddPenalty(c *gin.Context) error { + questID := c.Param("id") + req, err := transport.UnmarshalRequestData[game.AddPenaltyRequest](c.Request) + if err != nil { + return xerrors.Errorf("%w", err) + } + req.QuestID = questID + uauth, err := jwt.GetUserFromContext(c) + if err != nil { + return xerrors.Errorf("%w", err) + } + + s, err := h.clientFactory.NewStorage(c, dbnode.Alive) + if err != nil { + return xerrors.Errorf("get storage: %w", err) + } + quest, err := s.GetQuest(c, &storage.GetQuestRequest{ID: questID}) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return httperrors.Errorf(http.StatusNotFound, "quest %q not found", questID) + } + return xerrors.Errorf("get quest: %w", err) + } + if quest.Creator.ID != uauth.ID { + return httperrors.New(http.StatusForbidden, "only creator can add penalty to teams") + } + + srv := game.NewService(s, s, s, s) + if err := srv.AddPenalty(c, req); err != nil { + return xerrors.Errorf("add penalty: %w", err) + } + c.Status(http.StatusOK) + return nil +} diff --git a/src/internal/questspace/game/game.go b/src/internal/questspace/game/game.go index 42eea3a..75993fe 100644 --- a/src/internal/questspace/game/game.go +++ b/src/internal/questspace/game/game.go @@ -409,3 +409,26 @@ func (s *Service) TryAnswer(ctx context.Context, user *storage.User, req *TryAns return &TryAnswerResponse{Accepted: true, Text: req.Text, Score: score}, nil } + +type AddPenaltyRequest struct { + QuestID string `json:"-"` + TeamID string `json:"team_id"` + Penalty int `json:"penalty"` +} + +func (s *Service) AddPenalty(ctx context.Context, req *AddPenaltyRequest) error { + team, err := s.tms.GetTeam(ctx, &storage.GetTeamRequest{ID: req.TeamID}) + if err != nil { + if errors.Is(err, storage.ErrNotFound) { + return httperrors.Errorf(http.StatusNotFound, "team %q not found", req.TeamID) + } + return xerrors.Errorf("get team: %w", err) + } + if team.Quest.ID != req.QuestID { + return httperrors.Errorf(http.StatusForbidden, "team %q belongs to quest %q", req.TeamID, req.QuestID) + } + if err := s.ah.CreatePenalty(ctx, &storage.CreatePenaltyRequest{TeamID: req.TeamID, Penalty: req.Penalty}); err != nil { + return xerrors.Errorf("create penalty: %w", err) + } + return nil +}