Maxim Slipenko
d02e0d122c
See: https://github.com/golang-standards/project-layout Reviewed-on: https://code.alt-gnome.ru/aides-infra/aides-repo-api/pulls/5 Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com> Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
135 lines
4.9 KiB
Go
135 lines
4.9 KiB
Go
package router
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"os"
|
||
"path"
|
||
"strconv"
|
||
|
||
"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 models.Config
|
||
}
|
||
|
||
// Создает Роутер
|
||
func NewRouter(cfg models.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)
|
||
}
|