/
gnomdeployer
/
VUZPlus
Обзор
Документация
Войти
/
gnomdeployer
/
VUZPlus
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
server/internal/api/handler/test_task.go
890 строк
41 KB
Gewinn
Фикс отзывов и тестовых заданий
11 май 2025, 23:02
11 май 2025, 23:02
2c81ee0
Код
Авторство
О чём код?
package handler import ( "context" "fmt" "github.com/gofiber/fiber/v2" "os" "path/filepath" "strconv" "time" "vuzplus/internal/shared/entities" "vuzplus/internal/shared/log" "vuzplus/util" ) // CreateTestTask // @Summary Создание тестового задания // @Description Создает тестовое задание в виде файла и/или текста. // @Tags Task // @Accept multipart/form-data // @Produce json // @Param program_id formData int true "ID направления" // @Param type formData string true "Тип задания (essay, task)" // @Param task_text formData string true "Текст задания" // @Param task_file formData file false "Файл с заданием" // @Success 200 {object} entities.Response "ID созданного задания" // @Failure 400 {object} entities.Error "Некорректный запрос" // @Failure 403 {object} entities.Error "Недостаточно прав" // @Failure 500 {object} entities.Error "Внутренняя ошибка" // @Router /auth/tasks [post] // @Security ApiKeyAuth func (h *Handler) CreateTestTask(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if (userStatus.Role != "delegate" && userStatus.Role != "admin") || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } var testTask entities.TestTask if err := c.BodyParser(&testTask); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": err.Error()}) } if testTask.Type != "essay" && testTask.Type != "task" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("invalid task type") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid task type"}) } form, err := c.MultipartForm() if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: err.Error()}) } taskFile := form.File["task_file"] if len(taskFile) != 0 { ext := filepath.Ext(taskFile[0].Filename) randomFileName := util.GenerateRandomFileName(ext) testTask.TaskFile = randomFileName filePath := fmt.Sprintf("./tmp/%s", randomFileName) c.SaveFile(taskFile[0], filePath) err = h.repository.S3.FPutObject(context.Background(), "task-media", randomFileName, filePath, taskFile[0].Header.Get("Content-Type")) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(fmt.Sprintf("error creating file %s in minio: %s", randomFileName, err.Error())) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: fmt.Sprintf("error creating file %s in minio: %s", randomFileName, err.Error())}) } err = os.Remove(filePath) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(fmt.Sprintf("error removing file %s: %s", randomFileName, err.Error())) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: fmt.Sprintf("error removing file %s: %s", randomFileName, err.Error())}) } } h.logger.Debug().Msg("call h.repository.DB.DBCreateTestTask") taskID, err := h.repository.DB.DBCreateTestTask(&testTask) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Info", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusOK}) logEvent.Msg("successful creating test task") return c.Status(fiber.StatusOK).JSON(fiber.Map{"id": taskID}) } // GetTestTask // @Summary Получить список тестовых заданий по направлению // @Description Возвращает список тестовых заданий по ID направления (если указан) // @Tags Task // @Accept json // @Produce json // @Param program_id query int false "ID направления" // @Success 200 {array} entities.TestTask // @Failure 500 {object} entities.Error // @Router /auth/tasks [get] // @Security ApiKeyAuth func (h *Handler) GetTestTask(c *fiber.Ctx) error { programID := c.QueryInt("program_id", 0) h.logger.Debug().Msg("call h.repository.DB.DBGetTasksByProgramID") tasks, err := h.repository.DB.DBGetTasksByProgramID(programID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } for i := range *tasks { url, _ := h.repository.S3.PresignedGetObject( context.Background(), "task-media", (*tasks)[i].TaskFile, 7*24*time.Hour, ) (*tasks)[i].TaskFile = url.String() } logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Info", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusOK}) logEvent.Msg("successful getting test tasks") return c.Status(fiber.StatusOK).JSON(tasks) } // DeleteTestTask // @Summary Удалить тестовое задание // @Description Удаляет тестовое задание по ID, а также удаляет файл из MinIO // @Tags Task // @Accept json // @Produce json // @Param task_id query int true "ID задания" // @Success 200 {string} entities.Response "Task deleted successfully" // @Failure 400 {object} entities.Error // @Failure 500 {object} entities.Error // @Router /auth/tasks [delete] // @Security ApiKeyAuth func (h *Handler) DeleteTestTask(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if (userStatus.Role != "delegate" && userStatus.Role != "admin") || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } taskID := c.QueryInt("task_id") if taskID == 0 { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: invalid task id or diretion id") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid task id or diretion id}"}) } h.logger.Debug().Msg("call h.repository.DB.DBCheckTestTaskExists") exists, err := h.repository.DB.DBCheckTestTaskExists(taskID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("DB error while checking task existence: " + err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "error checking task existence"}) } if !exists { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Warning", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusNotFound}) logEvent.Msg("task not found") return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "task not found"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetTestTaskByID") task, err := h.repository.DB.DBGetTestTaskByID(taskID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } // Удаляем файл из MinIO err = h.repository.S3.RemoveObject(context.Background(), "task-media", task.TaskFile) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } // Удаляем запись из БД h.logger.Debug().Msg("call h.repository.DB.DBDeleteTestTaskByID") err = h.repository.DB.DBDeleteTestTaskByID(taskID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } h.logger.Info().Msgf("Task %d successfully deleted", taskID) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Task deleted successfully"}) } // UpdateTestTask // @Summary Обновить тестовое задание // @Description Обновляет текст и/или файл тестового задания по ID // @Tags Task // @Accept multipart/form-data // @Produce json // @Param task_id formData int true "ID задания" // @Param type formData string false "Тип задания (essay, task)" // @Param task_text formData string false "Текст задания" // @Param task_file formData file false "Файл с заданием" // @Success 200 {object} entities.Response // @Failure 400 {object} entities.Error // @Failure 403 {object} entities.Error // @Failure 500 {object} entities.Error // @Router /auth/tasks [put] // @Security ApiKeyAuth func (h *Handler) UpdateTestTask(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if (userStatus.Role != "delegate" && userStatus.Role != "admin") || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } taskID := c.FormValue("task_id") if taskID == "" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("error: task_id is required") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "task_id is required"}) } taskIDInt, _ := strconv.Atoi(taskID) if taskIDInt == 0 { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("error: invalid task_id") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid task_id"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetTestTaskByID") task, err := h.repository.DB.DBGetTestTaskByID(taskIDInt) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } task.Type = c.FormValue("type", task.Type) task.TaskText = c.FormValue("task_text", task.TaskText) form, err := c.MultipartForm() if err == nil && form.File["task_file"] != nil { file := form.File["task_file"][0] ext := filepath.Ext(file.Filename) newFileName := util.GenerateRandomFileName(ext) tmpPath := fmt.Sprintf("./tmp/%s", newFileName) if err := c.SaveFile(file, tmpPath); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } err = h.repository.S3.FPutObject(context.Background(), "task-media", newFileName, tmpPath, file.Header.Get("Content-Type")) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if task.TaskFile != "" { _ = h.repository.S3.RemoveObject(context.Background(), "task-media", task.TaskFile) } task.TaskFile = newFileName _ = os.Remove(tmpPath) } h.logger.Debug().Msg("call h.repository.DB.DBUpdateTestTask") err = h.repository.DB.DBUpdateTestTask(taskIDInt, task) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } h.logger.Info().Msgf("Task %d successfully updated", taskID) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": "Task updated successfully"}) } // UploadTaskSolution // @Summary Загрузка решения тестового задания // @Description Студент загружает решение тестового задания. Можно прикрепить текст и/или файл. // @Tags Task // @Accept multipart/form-data // @Produce json // @Param task_id formData int true "ID тестового задания" // @Param solution_text formData string false "Текстовое решение" // @Param solution_file formData file false "Файл с решением" // @Success 200 {object} entities.Response "Успех" // @Failure 400 {object} entities.Error "Ошибка на стороне клиента" // @Failure 403 {object} entities.Error "Недостаточно прав" // @Failure 500 {object} entities.Error "Ошибка на стороне сервера" // @Router /auth/tasks/solution [post] // @Security ApiKeyAuth func (h *Handler) UploadTaskSolution(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if userStatus.Role != "student" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: invalid role") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "invalid role"}) } if userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: user not verified") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "user not verified"}) } var req entities.TestTaskSolution if err := c.BodyParser(&req); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: error parsing request body") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "error parsing request body"}) } req.UserID = userID h.logger.Debug().Msg("call h.repository.DB.DBCheckTestTaskExists") exists, err := h.repository.DB.DBCheckTestTaskExists(req.TaskID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if !exists { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("task not found") return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "task not found"}) } form, err := c.MultipartForm() if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: err.Error()}) } solutionFile := form.File["solution_file"] if len(solutionFile) != 0 { ext := filepath.Ext(solutionFile[0].Filename) randomFileName := util.GenerateRandomFileName(ext) req.SolutionFile = randomFileName filePath := fmt.Sprintf("./tmp/%s", randomFileName) c.SaveFile(solutionFile[0], filePath) err = h.repository.S3.FPutObject(context.Background(), "task-media", randomFileName, filePath, solutionFile[0].Header.Get("Content-Type")) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(fmt.Sprintf("error creating file %s in minio: %s", randomFileName, err.Error())) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: fmt.Sprintf("error creating file %s in minio: %s", randomFileName, err.Error())}) } err = os.Remove(filePath) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(fmt.Sprintf("error removing file %s: %s", randomFileName, err.Error())) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: fmt.Sprintf("error removing file %s: %s", randomFileName, err.Error())}) } } h.logger.Debug().Msg("call h.repository.DB.DBCreateTaskSolution") taskID, err := h.repository.DB.DBCreateTaskSolution(&req) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } h.logger.Info().Msg("Solution successfully created") return c.Status(fiber.StatusOK).JSON(fiber.Map{"id": taskID}) } func (h *Handler) DeleteTaskSolution(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if userStatus.Role != "student" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: invalid role") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "invalid role"}) } if userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: user not verified") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "user not verified"}) } solutionID := c.QueryInt("solution_id") h.logger.Debug().Msg("call h.repository.DB.DBCheckSolutionExists") exists, err := h.repository.DB.DBCheckSolutionExists(solutionID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if !exists { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusNotFound}) logEvent.Msg("solution not found") return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "solution not found"}) } // Получаем имя файла h.logger.Debug().Msg("call h.repository.DB.DBGetSolutionFileName") fileName, err := h.repository.DB.DBGetSolutionFileName(solutionID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(fmt.Sprintf("error getting file name: %s", err.Error())) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to get file name"}) } // Удаляем файл из MinIO if fileName != "" { h.logger.Debug().Msgf("removing file %s from minio", fileName) err = h.repository.S3.RemoveObject(context.Background(), "task-media", fileName) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(fmt.Sprintf("error removing file from minio: %s", err.Error())) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to remove file from storage"}) } } // Удаляем запись из базы h.logger.Debug().Msg("call h.repository.DB.DBDeleteSolutionByID") err = h.repository.DB.DBDeleteSolutionByID(solutionID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } h.logger.Info().Msgf("solution %d successfully deleted", solutionID) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": fmt.Sprintf("solution %d successfully deleted", solutionID)}) } // GetStudentSolution godoc // @Summary Получить решения студента // @Description Представитель вуза или администратор могут получить решения конкретного студента по направлению // @Tags Task // @Accept json // @Produce json // @Param student_id query int true "ID студента" // @Param program_id query int true "ID направления" // @Success 200 {array} []entities.TestTaskSolution // @Failure 400 {object} entities.Error // @Failure 403 {object} entities.Error // @Failure 500 {object} entities.Error // @Router /auth/tasks/solution [get] // @Security ApiKeyAuth func (h *Handler) GetStudentSolution(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if (userStatus.Role != "delegate" && userStatus.Role != "admin") || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } studentID := c.QueryInt("student_id") programID := c.QueryInt("program_id") h.logger.Debug().Msg("call h.repository.DB.DBGetStudentSolutions") solutions, err := h.repository.DB.DBGetStudentSolutions(studentID, programID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } for i := range *solutions { if (*solutions)[i].SolutionFile != "" { url, err := h.repository.S3.PresignedGetObject(context.Background(), "task-media", (*solutions)[i].SolutionFile, 7*24*time.Hour) if err != nil { h.logger.Error().Msgf("error generating presigned URL for file %s: %s", (*solutions)[i].SolutionFile, err.Error()) continue } (*solutions)[i].SolutionFile = url.String() } } h.logger.Info().Msg("Solutions successfully received") return c.Status(fiber.StatusOK).JSON(solutions) } // GetMySolution // @Summary Получить свои решения // @Description Студент может получить свои решения для определенного направления // @Tags Task // @Accept json // @Produce json // @Param program_id query int true "ID направления" // @Success 200 {array} []entities.TestTaskSolution // @Failure 400 {object} entities.Error // @Failure 403 {object} entities.Error // @Failure 500 {object} entities.Error // @Router /auth/tasks/solution/my [get] // @Security ApiKeyAuth func (h *Handler) GetMySolution(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if userStatus.Role != "student" || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } programID := c.QueryInt("program_id") h.logger.Debug().Msg("call h.repository.DB.DBGetStudentSolutions") solutions, err := h.repository.DB.DBGetStudentSolutions(userID, programID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } for i := range *solutions { if (*solutions)[i].SolutionFile != "" { url, err := h.repository.S3.PresignedGetObject(context.Background(), "task-media", (*solutions)[i].SolutionFile, 7*24*time.Hour) if err != nil { h.logger.Error().Msgf("error generating presigned URL for file %s: %s", (*solutions)[i].SolutionFile, err.Error()) continue } (*solutions)[i].SolutionFile = url.String() } } h.logger.Info().Msg("Solutions successfully received") return c.Status(fiber.StatusOK).JSON(solutions) } // UpdateTaskSolution // @Summary Обновление решения тестового задания // @Description Студент обновляет своё решение: можно изменить текст и/или файл // @Tags Task // @Accept multipart/form-data // @Produce json // @Param solution_id formData int true "ID решения" // @Param solution_text formData string false "Новый текст решения" // @Param solution_file formData file false "Новый файл решения" // @Success 200 {object} entities.Response "Успех" // @Failure 400 {object} entities.Error "Ошибка на стороне клиента" // @Failure 403 {object} entities.Error "Недостаточно прав" // @Failure 404 {object} entities.Error "Решение не найдено" // @Failure 500 {object} entities.Error "Ошибка на стороне сервера" // @Router /auth/tasks/solution [put] // @Security ApiKeyAuth func (h *Handler) UpdateTaskSolution(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: err.Error()}) } if userStatus.Role != "student" || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: invalid role or unverified user") return c.Status(fiber.StatusForbidden).JSON(entities.Error{Error: "invalid role or unverified user"}) } form, err := c.MultipartForm() if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: err.Error()}) } solutionIDStr := form.Value["solution_id"] if len(solutionIDStr) == 0 { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: solution_id is required") return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: "solution_id is required"}) } solutionID, err := strconv.Atoi(solutionIDStr[0]) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: invalid solution_id") return c.Status(fiber.StatusBadRequest).JSON(entities.Error{Error: "invalid solution_id"}) } // Проверяем существование решения h.logger.Debug().Msg("call h.repository.DB.DBCheckSolutionExists") exists, err := h.repository.DB.DBCheckSolutionExists(solutionID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: err.Error()}) } if !exists { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusNotFound}) logEvent.Msg("error: solution not found") return c.Status(fiber.StatusNotFound).JSON(entities.Error{Error: "solution not found"}) } // Получаем старое имя файла h.logger.Debug().Msg("call h.repository.DB.DBGetSolutionFileName") oldFileName, err := h.repository.DB.DBGetSolutionFileName(solutionID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: err.Error()}) } var solutionText string // Обновляем текст решения только если он был передан и не пустой if textArr, ok := form.Value["solution_text"]; ok && len(textArr) > 0 { solutionText = textArr[0] } var newFileName string if files := form.File["solution_file"]; len(files) > 0 { // Удаляем старый файл, если был if oldFileName != "" { if err := h.repository.S3.RemoveObject(context.Background(), "task-media", oldFileName); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("failed to remove old file from storage: " + err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: "failed to remove old file from storage"}) } } // Загружаем новый файл ext := filepath.Ext(files[0].Filename) newFileName = util.GenerateRandomFileName(ext) filePath := fmt.Sprintf("./tmp/%s", newFileName) if err := c.SaveFile(files[0], filePath); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("failed to save new file: " + err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: "failed to save new file"}) } defer os.Remove(filePath) if err := h.repository.S3.FPutObject(context.Background(), "task-media", newFileName, filePath, files[0].Header.Get("Content-Type")); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("failed to upload new file to storage: " + err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: "failed to upload new file to storage"}) } } // Обновляем запись в БД только с новым текстом и/или файлом h.logger.Debug().Msg("call h.repository.DB.DBUpdateTaskSolution") if err := h.repository.DB.DBUpdateTaskSolution(solutionID, solutionText, newFileName); err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg("error: failed to update task solution: " + err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(entities.Error{Error: err.Error()}) } h.logger.Info().Msgf("Solution id=%v successfully updated", solutionID) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": fmt.Sprintf("Solution id=%v successfully updated", solutionID)}) } // UpdateSolutionStatus godoc // @Summary Обновление статуса задания // @Description Обновление статуса задания // @Tags Task // @Accept json // @Produce json // @Param solution_id query int true "ID решения" // @Param status query string true "Новый статус решения (in check, approved, rejected)" // @Success 200 {object} entities.Response "Успех" // @Failure 400 {object} entities.Error "Ошибка на стороне клиента" // @Failure 403 {object} entities.Error "Недостаточно прав" // @Failure 500 {object} entities.Error "Ошибка на стороне сервера" // @Router /auth/tasks/solution/status [put] // @Security ApiKeyAuth func (h *Handler) UpdateSolutionStatus(c *fiber.Ctx) error { userID, ok := c.Locals("id").(int) if !ok { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: cant get id from token") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "cant get id from token"}) } h.logger.Debug().Msg("call h.repository.DB.DBGetRoleById") userStatus, err := h.repository.DB.DBGetRoleById(userID) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } if (userStatus.Role != "delegate" && userStatus.Role != "admin") || userStatus.Status != "verified" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusForbidden}) logEvent.Msg("error: not enough rights for this action") return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "not enough rights for this action"}) } solutionID := c.QueryInt("solution_id") status := c.Query("status") if status != "in check" && status != "approved" && status != "rejected" { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusBadRequest}) logEvent.Msg("error: invalid status") return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "invalid status"}) } h.logger.Debug().Msg("call h.repository.DB.DBUpdateSolutionStatus") err = h.repository.DB.DBUpdateSolutionStatus(solutionID, status) if err != nil { logEvent := log.CreateLog(h.logger, log.LogsField{Level: "Error", Method: c.Method(), Url: c.OriginalURL(), Status: fiber.StatusInternalServerError}) logEvent.Msg(err.Error()) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": err.Error()}) } h.logger.Info().Msgf("Solution id=%v status successfully updated", solutionID) return c.Status(fiber.StatusOK).JSON(fiber.Map{"message": fmt.Sprintf("Solution id=%v status successfully updated", solutionID)}) }