package router import ( "encoding/json" "fmt" "io" "net/http" "os" "path" "strconv" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" "github.com/go-chi/render" "github.com/go-chi/chi/v5" //Импорт пакета chi для маршрутизации "github.com/go-chi/chi/v5/middleware" // Импорт middleware для логирования ) func createSymlink(target, link string) error { if _, err := os.Lstat(link); err == nil { if err := os.Remove(link); err != nil { return fmt.Errorf("failed to remove existing file or symlink: %w", err) } } if err := os.Symlink(target, link); err != nil { return fmt.Errorf("failed to create symlink: %w", err) } return nil } type Router struct { Config config.Config } // Создает Роутер func NewRouter(cfg config.Config) *Router { return &Router{Config: cfg} } // Метод настройки маршрутов для Роутера func (r *Router) SetupRoutes() *chi.Mux { router := chi.NewRouter() router.Use(middleware.Logger) // Создаем директорию для загрузки os.MkdirAll(path.Join(r.Config.UploadDir, "out"), os.ModePerm) // Определяем маршрут для загрузки файлов("/upload/{executionID}" путь, по которому будет доступен данный маршрут. //Путь включает переменную часть {executionID}, которая позволяет извлекать динамические параметры из URL.) router.Post("/upload/{repo}/task/{taskID}", r.uploadHandler) return router } // Метод отвечает за загрузку файлов func (r *Router) uploadHandler(w http.ResponseWriter, req *http.Request) { // Проверка авторизации if req.Header.Get("Authorization") != "Bearer "+r.Config.Token { render.JSON(w, req, models.ErrResponse{Message: "Не авторизованный", Code: http.StatusUnauthorized}) return } // Извлекаем параметр taskID из URL и проверяем его наличие taskID := chi.URLParam(req, "taskID") repo := chi.URLParam(req, "repo") if taskID == "" || repo == "" { render.JSON(w, req, models.ErrResponse{Message: "Требуются параметры [repo] и [taskID]", Code: http.StatusBadRequest}) return } // Чтение файлов из запроса err := req.ParseMultipartForm(r.Config.MaxSizeUpload) // Лимит 100 MB if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Парсинг не удался", Code: http.StatusBadRequest}) return } // При успешном парсинге извлекаем файлы files := req.MultipartForm.File["files"] // Карта где ключами являются имена полей формы, а значениями — массивы заголовков файлов. localPath := path.Join(repo, "task", taskID) taskFolderPath := path.Join(r.Config.UploadDir, "extra", localPath) os.MkdirAll(taskFolderPath, os.ModePerm) for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Не удается открыть файл", Code: http.StatusInternalServerError}) return } defer file.Close() // Полный путь для файла filePath := path.Join(taskFolderPath, fileHeader.Filename) //Удаляем файл если такой уже существует if _, err := os.Stat(filePath); err == nil { err = os.Remove(filePath) if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Не удалось удалить файл", Code: http.StatusInternalServerError}) return } } // Сохранение файла на сервере outFile, err := os.Create(filePath) if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Не удается создать файл", Code: http.StatusInternalServerError}) return } defer outFile.Close() _, err = io.Copy(outFile, file) if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Не удалось сохранить файл", Code: http.StatusInternalServerError}) return } // Символическая ссылка targetPath := path.Join("../extra/", localPath, fileHeader.Filename) symLink := path.Join(r.Config.UploadDir, "out", fileHeader.Filename) err = createSymlink(targetPath, symLink) if err != nil { render.JSON(w, req, models.ErrResponse{Message: "Не удается создать ссылку", Code: http.StatusInternalServerError}) return } } //Ответы в формате JSON resp := map[string]string{ "taskID": taskID, "repository": repo, "message": "Файлы успешно загружены", "fileCount": strconv.Itoa(len(files)), } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(resp) }