diff --git a/docs/docs.go b/docs/docs.go index e996242b584017791f99aa699f8d1fda341115c3..74678c232c7ed71bfdf9f00f8a77e1fc1b14475c 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -2225,6 +2225,39 @@ const docTemplate = `{ } } }, + "/quiz": { + "put": { + "description": "New Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "New Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } + } + }, "/quiz/{id}": { "get": { "description": "Get Quiz Detail", @@ -2268,6 +2301,37 @@ const docTemplate = `{ } } } + }, + "delete": { + "description": "Delete Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "Delete Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } } }, "/quiz/{id}/finish": { @@ -2613,7 +2677,6 @@ const docTemplate = `{ "description": "Information that should be available when you add a course", "type": "object", "required": [ - "abbreviation", "email", "id", "name" diff --git a/docs/swagger.json b/docs/swagger.json index 59ed537c3af3c9c5b38d3f1c98a12421d3128acd..7d7802bc413765ed1066d2f8d08270a03e5b116a 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -2216,6 +2216,39 @@ } } }, + "/quiz": { + "put": { + "description": "New Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "New Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } + } + }, "/quiz/{id}": { "get": { "description": "Get Quiz Detail", @@ -2259,6 +2292,37 @@ } } } + }, + "delete": { + "description": "Delete Quiz", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "quiz" + ], + "summary": "Delete Quiz", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Quiz id", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/web.BaseResponse" + } + } + } } }, "/quiz/{id}/finish": { @@ -2604,7 +2668,6 @@ "description": "Information that should be available when you add a course", "type": "object", "required": [ - "abbreviation", "email", "id", "name" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 7c62c7b01c3e2db940405858fe0ea0d9c9eb5ba2..b39336da946758345aadf855e855615703b341ab 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -85,7 +85,6 @@ definitions: description: Course Name type: string required: - - abbreviation - email - id - name @@ -1986,7 +1985,50 @@ paths: summary: Delete Content tags: - content + /quiz: + put: + consumes: + - application/json + description: New Quiz + parameters: + - description: Quiz id + format: uuid + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/web.BaseResponse' + summary: New Quiz + tags: + - quiz /quiz/{id}: + delete: + consumes: + - application/json + description: Delete Quiz + parameters: + - description: Quiz id + format: uuid + in: path + name: id + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/web.BaseResponse' + summary: Delete Quiz + tags: + - quiz get: consumes: - application/json diff --git a/handler/quiz/delete.go b/handler/quiz/delete.go new file mode 100644 index 0000000000000000000000000000000000000000..642e1661fa478b38a3b591b74c616acd0e4d6b26 --- /dev/null +++ b/handler/quiz/delete.go @@ -0,0 +1,86 @@ +package quiz + +import ( + "net/http" + "strings" + + "github.com/go-chi/chi/v5" + "github.com/google/uuid" + "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" + "gitlab.informatika.org/ocw/ocw-backend/model/web" +) + +// Index godoc +// +// @Tags quiz +// @Summary Delete Quiz +// @Description Delete Quiz +// @Produce json +// @Accept json +// @Param id path string true "Quiz id" Format(uuid) +// @Success 200 {object} web.BaseResponse +// @Router /quiz/{id} [delete] +func (m QuizHandlerImpl) DeleteQuiz(w http.ResponseWriter, r *http.Request) { + payload := quiz.DeleteRequestPayload{} + 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.DeleteToken = token[1] + payload.ID = id + + err = m.QuizService.DeleteQuiz(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(nil) + m.HttpUtil.WriteSuccessJson(w, responsePayload) + +} \ No newline at end of file diff --git a/handler/quiz/get.go b/handler/quiz/get.go index 799ca822a991240a0c6febccb29640720f1b5e67..b88bde2955671872d7ad047c2be17541e450b491 100644 --- a/handler/quiz/get.go +++ b/handler/quiz/get.go @@ -2,10 +2,12 @@ package quiz import ( "net/http" + "strings" "github.com/go-chi/chi/v5" "github.com/google/uuid" "gitlab.informatika.org/ocw/ocw-backend/model/web" + "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" ) // Index godoc @@ -55,3 +57,77 @@ func (m QuizHandlerImpl) GetQuizDetail(w http.ResponseWriter, r *http.Request) { m.HttpUtil.WriteSuccessJson(w, responsePayload) } + +// Index godoc +// +// @Tags quiz +// @Summary Get Quiz Link +// @Description Get Quiz Link +// @Produce json +// @Accept json +// @Param id path string true "Quiz id" Format(uuid) +// @Success 200 {object} web.BaseResponse +// @Router /quiz/link/{id} [get] + +func (m QuizHandlerImpl) GetQuizLink(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.GetQuiz(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/new.go b/handler/quiz/new.go new file mode 100644 index 0000000000000000000000000000000000000000..ca9ce5ed0b1d32b4d8cab8f568ec5a24221bb175 --- /dev/null +++ b/handler/quiz/new.go @@ -0,0 +1,94 @@ +package quiz + +import ( + "net/http" + "strings" + + "github.com/go-playground/validator/v10" + "gitlab.informatika.org/ocw/ocw-backend/model/web" + "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" +) + +// Index godoc +// +// @Tags quiz +// @Summary New Quiz +// @Description New Quiz +// @Produce json +// @Accept json +// @Param id path string true "Quiz id" Format(uuid) +// @Success 200 {object} web.BaseResponse +// @Router /quiz [put] +func (m QuizHandlerImpl) NewQuiz(w http.ResponseWriter, r *http.Request) { + payload := quiz.AddQuizRequestPayload{} + + // Validate payload + if r.Header.Get("Content-Type") != "application/json" { + payload := m.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil) + m.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload) + return + } + + if err := m.HttpUtil.ParseJson(r, &payload); err != nil { + payload := m.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error()) + m.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload) + return + } + + validate := validator.New() + if err := validate.Struct(payload); err != nil { + if _, ok := err.(*validator.InvalidValidationError); ok { + payload := m.WrapperUtil.ErrorResponseWrap(err.Error(), nil) + m.HttpUtil.WriteJson(w, http.StatusBadRequest, payload) + return + } + + errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors)) + payload := m.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload) + 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.AddQuizToken = token[1] + + response, err := m.QuizService.NewQuiz(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) +} \ No newline at end of file diff --git a/handler/quiz/type.go b/handler/quiz/type.go index 8c3bf6f5567cf7f04be9e42b67558279a0f6159d..306cc813840a08bbbe4e7379ad436e268aba38cc 100644 --- a/handler/quiz/type.go +++ b/handler/quiz/type.go @@ -9,4 +9,7 @@ type QuizHandler interface { TakeQuiz(w http.ResponseWriter, r *http.Request) GetQuizSolution(w http.ResponseWriter, r *http.Request) FinishQuiz(w http.ResponseWriter, r *http.Request) + NewQuiz(w http.ResponseWriter, r *http.Request) + GetQuizLink(w http.ResponseWriter, r *http.Request) + DeleteQuiz(w http.ResponseWriter, r *http.Request) } diff --git a/model/web/course/request.go b/model/web/course/request.go index 2604b105be490f5b0da70ce19369984e6bca1d61..20b446d8e06a7f4acb316d52e1c16d9e4b4f6607 100644 --- a/model/web/course/request.go +++ b/model/web/course/request.go @@ -27,7 +27,7 @@ type AddCourseRequestPayload struct { Email string `json:"email" validate:"required,email" example:"someone@example.com"` // Course Name Abbreviation - Abbreviation string `json:"abbreviation" validate:"required"` + Abbreviation string `json:"abbreviation"` } // DeleteCourse Request Payload diff --git a/model/web/error_code.go b/model/web/error_code.go index eb88b0837fa767fbd56e0aa114b4f85811bdfbb7..f150f3f1bf7d5211713144d3c5bfaaac3a11c618 100644 --- a/model/web/error_code.go +++ b/model/web/error_code.go @@ -3,18 +3,19 @@ package web const ( InvalidInput string = "INVALID_INPUT" - InvalidLogin string = "INVALID_LOGIN" - UnauthorizedAccess string = "UNAUTHORIZED" - InactiveUser string = "INACTIVE_ACCOUNT" - EmailExist string = "EMAIL_EXIST" - EmailNotExist string = "EMAIL_NOT_EXIST" - LinkNotAvailable string = "LINK_NOT_AVAILABLE" - FacultyNotExist string = "FACULTY_NOT_EXIST" - MajorNotExist string = "MAJOR_NOT_EXIST" - CourseNotExist string = "COURSE_NOT_EXIST" - LessonNotExist string = "LESSON_NOT_EXIST" + InvalidLogin string = "INVALID_LOGIN" + UnauthorizedAccess string = "UNAUTHORIZED" + InactiveUser string = "INACTIVE_ACCOUNT" + EmailExist string = "EMAIL_EXIST" + EmailNotExist string = "EMAIL_NOT_EXIST" + LinkNotAvailable string = "LINK_NOT_AVAILABLE" + FacultyNotExist string = "FACULTY_NOT_EXIST" + MajorNotExist string = "MAJOR_NOT_EXIST" + CourseNotExist string = "COURSE_NOT_EXIST" + LessonNotExist string = "LESSON_NOT_EXIST" LessonMaterialNotExist string = "LESSON_MATERIAL_NOT_EXIST" - IDExists string = "ID_ALREADY_EXISTS" + IDExists string = "ID_ALREADY_EXISTS" + NotExist string = "NOT_EXIST" TokenError string = "TOKEN_ERROR" ) diff --git a/model/web/quiz/request.go b/model/web/quiz/request.go new file mode 100644 index 0000000000000000000000000000000000000000..7aa66274179f131c3094b5630c7a0c448e9913fe --- /dev/null +++ b/model/web/quiz/request.go @@ -0,0 +1,57 @@ +package quiz + +import ( + "github.com/google/uuid" +) + +// AddQuiz Request Payload +// +// @Description Information that should be available when you add a quiz +type AddQuizRequestPayload struct { + // Web Token that was appended to the link + AddQuizToken string + + // Quiz Name + Name string `json:"name" validate:"required"` + + // Course ID + CourseID string `json:"course_id" validate:"required"` +} + +// UpdateQuiz Request Payload +// +// @Description Information that should be available when you update a quiz +type UpdateQuizRequestPayload struct { + // Web Token that was appended to the link + UpdateQuizToken string + + // Quiz ID, Set by param + ID uuid.UUID `json:"id"` +} + +// DeleteQuiz Request Payload +// +// @Description Information that should be available when you delete using uuid +type DeleteRequestPayload struct { + // Web Token that was appended to the link + DeleteToken string + + // Quiz ID, Set by param + ID uuid.UUID +} + +// GetUUID Request Payload +// +// @Description Information that should be available when you get using uuid +type GetRequestPayload struct { + // Quiz/Problem/Answer ID, provided by query + ID uuid.UUID +} + +// Link Response Payload +// +// @Description Information that you will get upon successful request +type LinkResponse struct { + UploadLink string `json:"upload_link"` +} + diff --git a/repository/quiz/impl.go b/repository/quiz/impl.go index 72ff3800d9dba38ab9285c9bcea6c5f22ed25118..e43a00e03f3fe6348d69ed92a88cee1b7aad8706 100644 --- a/repository/quiz/impl.go +++ b/repository/quiz/impl.go @@ -5,6 +5,7 @@ import ( "time" "github.com/google/uuid" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/course" "gitlab.informatika.org/ocw/ocw-backend/model/domain/quiz" "gitlab.informatika.org/ocw/ocw-backend/model/web" "gitlab.informatika.org/ocw/ocw-backend/provider/db" @@ -65,6 +66,35 @@ func (q *QuizRepositoryImpl) NewTake(quizId uuid.UUID, userEmail string) (uuid.U return id, err } +func (q *QuizRepositoryImpl) IsUserContributor(id string, email string) (bool, error) { + err := q.db.Where("id = ? AND email = ?", id, email).Find(&course.Course{}).Error + + if err != nil { + return false, err + } + + return true, nil +} + +func(q *QuizRepositoryImpl) NewQuiz(quiz quiz.Quiz) error { + return q.db.Create(&quiz).Error +} + +func(q *QuizRepositoryImpl) GetQuizPath(quizId uuid.UUID) (string, error) { + result := quiz.Quiz{} + err := q.db.Where("id = ?", quizId).Find(&result).Error + + if err != nil { + return "", err + } + + return result.QuizPath, nil +} + +func(q *QuizRepositoryImpl) Delete(quizId uuid.UUID) error { + return q.db.Delete(&quiz.Quiz{}, quizId).Error +} + func (q *QuizRepositoryImpl) IsActiveTake(quizId uuid.UUID, userEmail string) (bool, error) { var result int64 = 0 err := q.db. diff --git a/repository/quiz/type.go b/repository/quiz/type.go index 1b4e0cbeca391ec136dfc8c696d1c058d49e55e4..c558fc0554b39c81c890d36150cd8d8ee80daf6a 100644 --- a/repository/quiz/type.go +++ b/repository/quiz/type.go @@ -10,6 +10,10 @@ type QuizRepository interface { GetQuizDetail(quizId uuid.UUID) (*quiz.Quiz, error) UpdateScore(takeId uuid.UUID, score int) error NewTake(quizId uuid.UUID, userEmail string) (uuid.UUID, error) + IsUserContributor(id string, email string) (bool, error) + NewQuiz(quiz quiz.Quiz) error + GetQuizPath(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) GetLastTake(quizId uuid.UUID, userEmail string) (*quiz.QuizTake, error) diff --git a/routes/quiz/route.go b/routes/quiz/route.go index b89269412c9add8ccc39a190a965f187a13c15b9..e706bb19fd348df8e3a0e8d62146eb457f09165a 100644 --- a/routes/quiz/route.go +++ b/routes/quiz/route.go @@ -14,7 +14,6 @@ type QuizRoutes struct { func (q QuizRoutes) Register(r chi.Router) { r.Get("/course/{id}/quiz", q.QuizHandler.GetAllQuizes) - r.Get("/quiz/{id}", q.QuizHandler.GetQuizDetail) guard := q.GuardBuilder.Build( user.Student, @@ -36,4 +35,18 @@ func (q QuizRoutes) Register(r chi.Router) { r.Use(guard) r.Get("/", q.QuizHandler.GetQuizSolution) }) + + r.Route("/quiz/{id}", func(r chi.Router) { + r.Get("/", q.QuizHandler.GetQuizDetail) + r.Route("/", func(r chi.Router) { + r.Use(guard) + r.Put("/", q.QuizHandler.NewQuiz) + r.Delete("/", q.QuizHandler.DeleteQuiz) + }) + }) + + r.Route("/quiz/link/{id}", func(r chi.Router) { + r.Use(guard) + r.Get("/", q.QuizHandler.GetQuizLink) + }) } diff --git a/service/quiz/impl.go b/service/quiz/impl.go index 3898cd448978c6604f71f68e7561077c9c396a00..e6ca76ee036d0601b2e771c3a42666d2e41c277f 100644 --- a/service/quiz/impl.go +++ b/service/quiz/impl.go @@ -4,19 +4,34 @@ import ( "bytes" "context" "encoding/json" + "errors" + "fmt" + "strings" "github.com/google/uuid" "gitlab.informatika.org/ocw/ocw-backend/model/domain/quiz" + "gitlab.informatika.org/ocw/ocw-backend/model/domain/user" "gitlab.informatika.org/ocw/ocw-backend/model/web" + model "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" + atoken "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token" "gitlab.informatika.org/ocw/ocw-backend/provider/storage" quizRepo "gitlab.informatika.org/ocw/ocw-backend/repository/quiz" + "gitlab.informatika.org/ocw/ocw-backend/service/logger" + "gitlab.informatika.org/ocw/ocw-backend/utils/env" + "gitlab.informatika.org/ocw/ocw-backend/utils/token" + "gorm.io/gorm" ) type QuizServiceImpl struct { quizRepo.QuizRepository storage.Storage + token.TokenUtil + logger.Logger + *env.Environment } + +// TODO: should be for admins, make ones for users which doesnt expose minio link func (q QuizServiceImpl) ListAllQuiz(courseId string) ([]quiz.Quiz, error) { return q.QuizRepository.GetQuizes(courseId) } @@ -170,3 +185,115 @@ func (q QuizServiceImpl) DoFinishQuiz(ctx context.Context, quizId uuid.UUID, ema return data, nil } + +func (q QuizServiceImpl) isQuizContributor(courseId string, email string) error { + _, err := q.QuizRepository.IsUserContributor(courseId, email) + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return web.NewResponseError("course and user combination not found", "NOT_OWNER") + } + + return err + } + + return nil +} + +func (q QuizServiceImpl) NewQuiz(payload model.AddQuizRequestPayload) (*model.LinkResponse, error) { + // Validate Role + claim, err := q.TokenUtil.Validate(payload.AddQuizToken, atoken.Access) + + // Invalid Token + if err != nil { + return &model.LinkResponse{}, web.NewResponseErrorFromError(err, web.TokenError) + } + + // Unauthorized Role + if claim.Role == user.Student { + return &model.LinkResponse{}, web.NewResponseErrorFromError(err, web.UnauthorizedAccess) + } + + // Validate Ownership + if err := q.isQuizContributor(payload.CourseID, claim.Email); err != nil { + return &model.LinkResponse{}, err + } + + path := fmt.Sprintf("%s/%s.json", q.BucketQuizBasePath, strings.ReplaceAll(uuid.New().String(), "-", "")) + uploadLink, err := q.Storage.CreatePutSignedLink(context.Background(), path) + + if err != nil { + q.Logger.Error("Some error happened when generate link") + q.Logger.Error(err.Error()) + return &model.LinkResponse{}, err + } + + return &model.LinkResponse{UploadLink: uploadLink}, nil +} + +func (q QuizServiceImpl) GetQuiz(payload model.UpdateQuizRequestPayload) (*model.LinkResponse, error) { + // Validate Role + claim, err := q.TokenUtil.Validate(payload.UpdateQuizToken, atoken.Access) + + // Invalid Token + if err != nil { + return &model.LinkResponse{}, web.NewResponseErrorFromError(err, web.TokenError) + } + + // Unauthorized Role + if claim.Role == user.Student { + return &model.LinkResponse{}, web.NewResponseErrorFromError(err, web.UnauthorizedAccess) + } + + // Get Quiz Detail + quiz, err := q.QuizRepository.GetQuizDetail(payload.ID) + + if err != nil { + return &model.LinkResponse{}, err + } + + // Validate Ownership + if err := q.isQuizContributor(quiz.CourseId, claim.Email); err != nil { + return &model.LinkResponse{}, err + } + + uploadLink, err := q.QuizRepository.GetQuizPath(payload.ID) + + if err != nil { + q.Logger.Error("Some error happened when retrieving link") + q.Logger.Error(err.Error()) + return &model.LinkResponse{}, err + } + + return &model.LinkResponse{UploadLink: uploadLink}, nil +} + +func (q QuizServiceImpl) DeleteQuiz(payload model.DeleteRequestPayload) error { + // Validate Role + claim, err := q.TokenUtil.Validate(payload.DeleteToken, atoken.Access) + + // Invalid Token + if err != nil { + return web.NewResponseErrorFromError(err, web.TokenError) + } + + // Unauthorized Role + if claim.Role == user.Student { + return web.NewResponseErrorFromError(err, web.UnauthorizedAccess) + } + + // Get Quiz Detail + quiz, err := q.QuizRepository.GetQuizDetail(payload.ID) + + if err != nil { + return err + } + + // Validate Ownership + if err := q.isQuizContributor(quiz.CourseId, claim.Email); err != nil { + return err + } + + return q.QuizRepository.Delete(payload.ID) +} + diff --git a/service/quiz/type.go b/service/quiz/type.go index 7c97e4965500fb25f75c19a4117c536559fcf9a8..616546c025bfb4d3d1c65896678fc40efa359df0 100644 --- a/service/quiz/type.go +++ b/service/quiz/type.go @@ -5,6 +5,7 @@ import ( "github.com/google/uuid" "gitlab.informatika.org/ocw/ocw-backend/model/domain/quiz" + model "gitlab.informatika.org/ocw/ocw-backend/model/web/quiz" ) type QuizService interface { @@ -14,4 +15,8 @@ type QuizService interface { DoTakeQuiz(ctx context.Context, quizId uuid.UUID, email string) (*quiz.QuizDetail, error) DoFinishQuiz(ctx context.Context, quizId uuid.UUID, email string, studentAnswer []quiz.Response) (*quiz.QuizTake, error) GetSolutionQuiz(ctx context.Context, quizId uuid.UUID, email string) (*quiz.QuizDetail, error) + isQuizContributor(courseId string, email string) error + NewQuiz(payload model.AddQuizRequestPayload) (*model.LinkResponse, error) + GetQuiz(payload model.UpdateQuizRequestPayload) (*model.LinkResponse, error) + DeleteQuiz(payload model.DeleteRequestPayload) error } diff --git a/utils/env/env.go b/utils/env/env.go index 85aa99986a2c0a45cb68f07927cbe954137a16e3..50c73820d065affd17ec2d93bf420f994281bdcd 100644 --- a/utils/env/env.go +++ b/utils/env/env.go @@ -65,6 +65,7 @@ type Environment struct { BucketSignedGetDuration int64 `env:"BUCKET_SIGNED_GET_DURATION_S" envDefault:"1800"` BucketMaterialBasePath string `env:"BUCKET_MATERIAL_BASE_PATH" envDefault:"materials"` + BucketQuizBasePath string `env:"BUCKET_MATERIAL_BASE_PATH" envDefault:"quiz"` UseBucket bool `env:"USE_BUCKET" envDefault:"true"` }