From dfa46190a44e695013a7eb3e6affc628ebbef33a Mon Sep 17 00:00:00 2001 From: nart4hire <13520129@std.stei.itb.ac.id> Date: Thu, 4 May 2023 21:34:14 +0700 Subject: [PATCH] fix(quiz): added both update and get routes --- docs/docs.go | 54 ++++++++++++++++++ docs/swagger.json | 54 ++++++++++++++++++ docs/swagger.yaml | 37 ++++++++++++ handler/quiz/get.go | 6 +- handler/quiz/{new.go => management.go} | 78 +++++++++++++++++++++++++- handler/quiz/type.go | 1 + model/web/quiz/request.go | 11 +++- repository/quiz/impl.go | 11 ++++ repository/quiz/type.go | 1 + routes/quiz/route.go | 1 + service/quiz/impl.go | 29 ++++++++++ service/quiz/type.go | 1 + 12 files changed, 279 insertions(+), 5 deletions(-) rename handler/quiz/{new.go => management.go} (56%) diff --git a/docs/docs.go b/docs/docs.go index 871b248..f8ef591 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -2340,6 +2340,46 @@ const docTemplate = `{ } } } + }, + "patch": { + "description": "Update Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "Update Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Quiz payload", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/quiz.UpdateQuizRequestPayload" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } } }, "/quiz/{id}/finish": { @@ -3285,6 +3325,20 @@ const docTemplate = `{ } } }, + "quiz.UpdateQuizRequestPayload": { + "description": "Information that should be available when you update a quiz", + "type": "object", + "properties": { + "id": { + "description": "Quiz ID, Set by param", + "type": "string" + }, + "updateQuizToken": { + "description": "Web Token that was appended to the link", + "type": "string" + } + } + }, "refresh.RefreshResponsePayload": { "description": "Refresh endpoint response when process success", "type": "object", diff --git a/docs/swagger.json b/docs/swagger.json index c9f33d2..ce06490 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2332,6 +2332,46 @@ } } } + }, + "patch": { + "description": "Update Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "Update Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Update Quiz payload", + "name": "data", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/quiz.UpdateQuizRequestPayload" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } } }, "/quiz/{id}/finish": { @@ -3277,6 +3317,20 @@ } } }, + "quiz.UpdateQuizRequestPayload": { + "description": "Information that should be available when you update a quiz", + "type": "object", + "properties": { + "id": { + "description": "Quiz ID, Set by param", + "type": "string" + }, + "updateQuizToken": { + "description": "Web Token that was appended to the link", + "type": "string" + } + } + }, "refresh.RefreshResponsePayload": { "description": "Refresh endpoint response when process success", "type": "object", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 3735fa3..56b3a2e 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -483,6 +483,16 @@ definitions: problem_id: type: string type: object + quiz.UpdateQuizRequestPayload: + description: Information that should be available when you update a quiz + properties: + id: + description: Quiz ID, Set by param + type: string + updateQuizToken: + description: Web Token that was appended to the link + type: string + type: object refresh.RefreshResponsePayload: description: Refresh endpoint response when process success properties: @@ -2081,6 +2091,33 @@ paths: summary: Get Quiz Detail tags: - quiz + patch: + consumes: + - application/json + description: Update Quiz + parameters: + - description: Quiz id + format: uuid + in: path + name: id + required: true + type: string + - description: Update Quiz payload + in: body + name: data + required: true + schema: + $ref: '#/definitions/quiz.UpdateQuizRequestPayload' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/web.BaseResponse' + summary: Update Quiz + tags: + - quiz /quiz/{id}/finish: post: consumes: diff --git a/handler/quiz/get.go b/handler/quiz/get.go index acf7d55..45918bc 100644 --- a/handler/quiz/get.go +++ b/handler/quiz/get.go @@ -70,7 +70,7 @@ func (m QuizHandlerImpl) GetQuizDetail(w http.ResponseWriter, r *http.Request) { // @Router /quiz/link/{id} [get] func (m QuizHandlerImpl) GetQuizLink(w http.ResponseWriter, r *http.Request) { - payload := quiz.UpdateQuizRequestPayload{} + payload := quiz.GetRequestPayload{} quizId := chi.URLParam(r, "id") if quizId == "" { @@ -111,9 +111,9 @@ func (m QuizHandlerImpl) GetQuizLink(w http.ResponseWriter, r *http.Request) { return } - payload.UpdateQuizToken = token[1] + payload.GetToken = token[1] payload.ID = id - response, err := m.QuizService.UpdateQuiz(payload) + response, err := m.QuizService.GetQuizLink(payload) if err != nil { respErr, ok := err.(web.ResponseError) diff --git a/handler/quiz/new.go b/handler/quiz/management.go similarity index 56% rename from handler/quiz/new.go rename to handler/quiz/management.go index 2a9b65f..c53a731 100644 --- a/handler/quiz/new.go +++ b/handler/quiz/management.go @@ -4,7 +4,9 @@ import ( "net/http" "strings" + "github.com/go-chi/chi/v5" "github.com/go-playground/validator/v10" + "github.com/google/uuid" "gitlab.informatika.org/ocw/ocw-backend/model/web" "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" ) @@ -92,4 +94,78 @@ func (m QuizHandlerImpl) NewQuiz(w http.ResponseWriter, r *http.Request) { responsePayload := m.WrapperUtil.SuccessResponseWrap(response) m.HttpUtil.WriteSuccessJson(w, responsePayload) -} \ No newline at end of file +} + +// Index godoc +// +// @Tags quiz +// @Summary Update Quiz +// @Description Update Quiz +// @Produce json +// @Accept json +// @Param id path string true "Quiz id" Format(uuid) +// @Param data body quiz.UpdateQuizRequestPayload true "Update Quiz payload" +// @Success 200 {object} web.BaseResponse +// @Router /quiz/{id} [patch] +func (m QuizHandlerImpl) UpdateQuiz(w http.ResponseWriter, r *http.Request) { + payload := quiz.UpdateQuizRequestPayload{} + quizId := chi.URLParam(r, "id") + + if quizId == "" { + payload := m.WrapperUtil.ErrorResponseWrap("quiz id is required", nil) + m.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload) + return + } + + id, err := uuid.Parse(quizId) + + if err != nil { + // invalid uuid + payload := m.WrapperUtil.ErrorResponseWrap(err.Error(), nil) + m.HttpUtil.WriteJson(w, http.StatusBadRequest, payload) + return + } + + // Confirm Valid Website Token + validateTokenHeader := r.Header.Get("Authorization") + + if validateTokenHeader == "" { + payload := m.WrapperUtil.ErrorResponseWrap("token is required", nil) + m.HttpUtil.WriteJson(w, http.StatusForbidden, payload) + return + } + + token := strings.Split(validateTokenHeader, " ") + + if len(token) != 2 { + payload := m.WrapperUtil.ErrorResponseWrap("invalid token", nil) + m.HttpUtil.WriteJson(w, http.StatusBadRequest, payload) + return + } + + if token[0] != "Bearer" { + payload := m.WrapperUtil.ErrorResponseWrap("invalid token", nil) + m.HttpUtil.WriteJson(w, http.StatusBadRequest, payload) + return + } + + payload.UpdateQuizToken = token[1] + payload.ID = id + response, err := m.QuizService.UpdateQuiz(payload) + + if err != nil { + respErr, ok := err.(web.ResponseError) + if ok { + payload := m.WrapperUtil.ErrorResponseWrap(respErr.Error(), respErr) + + m.HttpUtil.WriteJson(w, http.StatusBadRequest, payload) + } else { + payload := m.WrapperUtil.ErrorResponseWrap("internal server error", nil) + m.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload) + } + return + } + + responsePayload := m.WrapperUtil.SuccessResponseWrap(response) + m.HttpUtil.WriteSuccessJson(w, responsePayload) +} diff --git a/handler/quiz/type.go b/handler/quiz/type.go index 306cc81..4d0e1dc 100644 --- a/handler/quiz/type.go +++ b/handler/quiz/type.go @@ -11,5 +11,6 @@ type QuizHandler interface { FinishQuiz(w http.ResponseWriter, r *http.Request) NewQuiz(w http.ResponseWriter, r *http.Request) GetQuizLink(w http.ResponseWriter, r *http.Request) + UpdateQuiz(w http.ResponseWriter, r *http.Request) DeleteQuiz(w http.ResponseWriter, r *http.Request) } diff --git a/model/web/quiz/request.go b/model/web/quiz/request.go index 7aa6627..634967c 100644 --- a/model/web/quiz/request.go +++ b/model/web/quiz/request.go @@ -44,14 +44,23 @@ type DeleteRequestPayload struct { // // @Description Information that should be available when you get using uuid type GetRequestPayload struct { + // Web Token that was appended to the link + GetToken string + // Quiz/Problem/Answer ID, provided by query ID uuid.UUID } // Link Response Payload // -// @Description Information that you will get upon successful request +// @Description Information that you will get upon successful add/update request type LinkResponse struct { UploadLink string `json:"upload_link"` } +// Path Response Payload +// +// @Description Information that you will get upon successful get request +type PathResponse struct { + Path string `json:"path"` +} \ No newline at end of file diff --git a/repository/quiz/impl.go b/repository/quiz/impl.go index 8f593bc..d9b003a 100644 --- a/repository/quiz/impl.go +++ b/repository/quiz/impl.go @@ -84,6 +84,17 @@ func(q *QuizRepositoryImpl) UpdateQuiz(quiz quiz.Quiz) error { return q.db.Save(quiz).Error } +func(q *QuizRepositoryImpl) GetQuizLink(quizId uuid.UUID) (string, error) { + result := &quiz.Quiz{} + err := q.db.Where("id = ?", quizId).First(result).Error + + if errors.Is(err, gorm.ErrRecordNotFound) { + return "", web.NewResponseError("Record not found", "ERR_NOT_FOUND") + } + + return result.QuizPath, nil +} + func(q *QuizRepositoryImpl) Delete(quizId uuid.UUID) error { return q.db.Delete(&quiz.Quiz{}, quizId).Error } diff --git a/repository/quiz/type.go b/repository/quiz/type.go index 72d7e0f..c302233 100644 --- a/repository/quiz/type.go +++ b/repository/quiz/type.go @@ -13,6 +13,7 @@ type QuizRepository interface { IsUserContributor(id string, email string) (bool, error) NewQuiz(quiz quiz.Quiz) error UpdateQuiz(quiz quiz.Quiz) error + GetQuizLink(quizId uuid.UUID) (string, error) Delete(quizId uuid.UUID) error IsActiveTake(quizId uuid.UUID, userEmail string) (bool, error) GetAllTake(quizId uuid.UUID, userEmail string) ([]quiz.QuizTake, error) diff --git a/routes/quiz/route.go b/routes/quiz/route.go index b4b1ce3..d75c278 100644 --- a/routes/quiz/route.go +++ b/routes/quiz/route.go @@ -40,6 +40,7 @@ func (q QuizRoutes) Register(r chi.Router) { r.Get("/", q.QuizHandler.GetQuizDetail) r.Route("/", func(r chi.Router) { r.Use(guard) + r.Patch("/", q.QuizHandler.UpdateQuiz) r.Delete("/", q.QuizHandler.DeleteQuiz) }) }) diff --git a/service/quiz/impl.go b/service/quiz/impl.go index 52befa2..bea57db 100644 --- a/service/quiz/impl.go +++ b/service/quiz/impl.go @@ -300,6 +300,35 @@ func (q QuizServiceImpl) UpdateQuiz(payload model.UpdateQuizRequestPayload) (*mo return &model.LinkResponse{UploadLink: uploadLink}, nil } +func (q QuizServiceImpl) GetQuizLink(payload model.GetRequestPayload) (*model.PathResponse, error) { + // Validate Role + claim, err := q.TokenUtil.Validate(payload.GetToken, token.Access) + + // Invalid Token + if err != nil { + return &model.PathResponse{}, web.NewResponseErrorFromError(err, web.TokenError) + } + + // Unauthorized Role + if claim.Role == userDomain.Student { + return &model.PathResponse{}, web.NewResponseErrorFromError(err, web.UnauthorizedAccess) + } + + // Get Quiz Detail + quiz, err := q.QuizRepository.GetQuizDetail(payload.ID) + + if err != nil { + return &model.PathResponse{}, err + } + + // Validate Ownership + if err := q.isQuizContributor(quiz.CourseId, claim.Email); err != nil { + return &model.PathResponse{}, err + } + + return &model.PathResponse{Path: quiz.QuizPath}, nil +} + func (q QuizServiceImpl) DeleteQuiz(payload model.DeleteRequestPayload) error { // Validate Role claim, err := q.TokenUtil.Validate(payload.DeleteToken, token.Access) diff --git a/service/quiz/type.go b/service/quiz/type.go index 229d724..f06407a 100644 --- a/service/quiz/type.go +++ b/service/quiz/type.go @@ -19,5 +19,6 @@ type QuizService interface { isQuizContributor(courseId string, email string) error NewQuiz(payload model.AddQuizRequestPayload) (*model.LinkResponse, error) UpdateQuiz(payload model.UpdateQuizRequestPayload) (*model.LinkResponse, error) + GetQuizLink(payload model.GetRequestPayload) (*model.PathResponse, error) DeleteQuiz(payload model.DeleteRequestPayload) error } -- GitLab