Initial commit

This commit is contained in:
Dmitriy 2024-12-04 14:46:42 +03:00
commit e9585bef50
11 changed files with 370 additions and 0 deletions

132
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}