Initial commit
This commit is contained in:
commit
e9585bef50
11 changed files with 370 additions and 0 deletions
132
.gitignore
vendored
Normal file
132
.gitignore
vendored
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
### Go ###
|
||||||
|
|
||||||
|
# Двоичные файлы для программ и плагинов
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Тестовый двоичный файл, созданный с помощью `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Вывод инструмента покрытия кода go, в частности при использовании с LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Каталоги зависимостей (удалите комментарий ниже, чтобы включить их)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Файл рабочей области Go
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### GoLand ###
|
||||||
|
|
||||||
|
# Пользовательские настройки
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# Файлы, относящиеся к пользователю AWS
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Сгенерированные файлы
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Конфиденциальные или часто изменяемые файлы
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle и Maven с автоматическим импортом
|
||||||
|
# При использовании Gradle или Maven с автоматическим импортом следует исключить файлы модулей,
|
||||||
|
# так как они будут воссоздаваться и могут вызывать сбои. Раскомментируйте, если используете
|
||||||
|
# автоматический импорт.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# плагин Mongo Explorer
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# Формат проекта на основе файлов
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# плагин mpeltonen/sbt-idea
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# Плагин JIRA
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Плагин Cursive Clojure
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# Плагин SonarLint
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Плагин Crashlytics (для Android Studio и IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Клиент REST на основе редактора
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Файл сериализованного кэша Android Studio 3.1+
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### Исправление для GoLand ###
|
||||||
|
# Причина комментария: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
|
||||||
|
|
||||||
|
# *.iml
|
||||||
|
# modules.xml
|
||||||
|
# .idea/misc.xml
|
||||||
|
# Плагин Sonarlint
|
||||||
|
# https://plugins.jetbrains.com/plugin/7973-sonarlint
|
||||||
|
.idea/**/sonarlint/
|
||||||
|
|
||||||
|
# Плагин SonarQube
|
||||||
|
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
|
||||||
|
.idea/**/sonarIssues.xml
|
||||||
|
|
||||||
|
# Плагин Markdown Navigator
|
||||||
|
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
|
||||||
|
.idea/**/markdown-navigator.xml
|
||||||
|
.idea/**/markdown-navigator-enh.xml
|
||||||
|
.idea/**/markdown-navigator/
|
||||||
|
|
||||||
|
# Ошибка при создании файла кэша
|
||||||
|
# См. https://youtrack.jetbrains.com/issue/JBR-2257
|
||||||
|
.idea/$CACHE_FILE$
|
||||||
|
|
||||||
|
# Плагин CodeStream
|
||||||
|
# https://plugins.jetbrains.com/plugin/12206-codestream
|
||||||
|
.idea/codestream.xml
|
||||||
|
|
||||||
|
# Плагин Azure Toolkit для IntelliJ
|
||||||
|
# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij
|
||||||
|
.idea/**/azureSettings.xml
|
||||||
|
|
||||||
|
# Игнорируем папку uploads
|
||||||
|
uploads
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
9
.idea/aides-repo-api.iml
Normal file
9
.idea/aides-repo-api.iml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="Go" enabled="true" />
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/aides-repo-api.iml" filepath="$PROJECT_DIR$/.idea/aides-repo-api.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# Первый этап: сборка приложения
|
||||||
|
FROM golang:latest AS builder
|
||||||
|
|
||||||
|
WORKDIR /rest-app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN go build -o alt-storage
|
||||||
|
|
||||||
|
# Второй этап: создание финального образа
|
||||||
|
FROM registry.altlinux.org/alt/alt:sisyphus
|
||||||
|
|
||||||
|
COPY --from=builder /rest-app/alt-storage /bin/main
|
||||||
|
ENTRYPOINT ["/bin/main"]
|
9
go.mod
Normal file
9
go.mod
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
module rest_api
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/ajg/form v1.5.1 // indirect
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0 // indirect
|
||||||
|
github.com/go-chi/render v1.0.3 // indirect
|
||||||
|
)
|
6
go.sum
Normal file
6
go.sum
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
|
||||||
|
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw=
|
||||||
|
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||||
|
github.com/go-chi/render v1.0.3 h1:AsXqd2a1/INaIfUSKq3G5uA8weYx20FOsM7uSoCyyt4=
|
||||||
|
github.com/go-chi/render v1.0.3/go.mod h1:/gr3hVkmYR0YlEy3LxCuVRFzEu9Ruok+gFqbIofjao0=
|
23
main.go
Normal file
23
main.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"rest_api/models"
|
||||||
|
"rest_api/router"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
config := models.Config{
|
||||||
|
Token: "Alt",
|
||||||
|
UploadDir: "./uploads", // Папка для сохранения файлов
|
||||||
|
MaxSizeUpload: 100 * 1024 * 1024, // Max размер файла
|
||||||
|
TaskDir: "extra",
|
||||||
|
}
|
||||||
|
|
||||||
|
router := router.NewRouter(config).SetupRoutes()
|
||||||
|
|
||||||
|
log.Println("Сервер запущен на :8080")
|
||||||
|
http.ListenAndServe(":8080", router)
|
||||||
|
|
||||||
|
}
|
21
models/models.go
Normal file
21
models/models.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package models
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Token string
|
||||||
|
Repo string
|
||||||
|
UploadDir string
|
||||||
|
TaskDir string
|
||||||
|
SymLink string
|
||||||
|
MaxSizeUpload int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileUpload struct {
|
||||||
|
TaskID string
|
||||||
|
|
||||||
|
FileName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
Code int `json:"code"`
|
||||||
|
}
|
133
router/router.go
Normal file
133
router/router.go
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
package router
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"rest_api/models" // Импорт пакета с моделями, где определены структуры конфигурации
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"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)
|
||||||
|
}
|
Loading…
Reference in a new issue