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