diff --git a/cmd/aides-repo-api/main.go b/cmd/aides-repo-api/main.go index 8ff30a0..bd476bb 100644 --- a/cmd/aides-repo-api/main.go +++ b/cmd/aides-repo-api/main.go @@ -4,16 +4,19 @@ import ( "log" "net/http" - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/router" ) func main() { - config := config.New() + app, err := app.New() + if err != nil { + panic(err) + } // Конфигурация сервера - router := router.New(config).Setup() + router := router.New(app).Setup() - log.Printf("Сервер запущен на порту: %s", config.Port) - http.ListenAndServe(":"+config.Port, router) + log.Printf("Сервер запущен на порту: %s", app.Config.Port) + http.ListenAndServe(":"+app.Config.Port, router) } diff --git a/go.mod b/go.mod index fc13160..fcc8a8c 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,12 @@ require ( github.com/go-chi/render v1.0.3 ) -require github.com/ajg/form v1.5.1 // indirect +require ( + github.com/ajg/form v1.5.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.24 // indirect + golang.org/x/text v0.21.0 // indirect + gorm.io/driver/sqlite v1.5.7 // indirect + gorm.io/gorm v1.25.12 // indirect +) diff --git a/go.sum b/go.sum index 55110ff..5acd7b7 100644 --- a/go.sum +++ b/go.sum @@ -6,3 +6,15 @@ 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= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I= +gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4= +gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= +gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= diff --git a/internal/app/app.go b/internal/app/app.go new file mode 100644 index 0000000..4bb8cac --- /dev/null +++ b/internal/app/app.go @@ -0,0 +1,31 @@ +package app + +import ( + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +type App struct { + Db *gorm.DB + Config *config.Config +} + +func New() (*App, error) { + db, err := gorm.Open(sqlite.Open("app.db"), &gorm.Config{}) + if err != nil { + return nil, err + } + + db.AutoMigrate(&models.Task{}, &models.GitRepoAltRepoTask{}) + + db.FirstOrCreate(&models.ALTRepo{ + Name: "Sisyphus", + }) + + return &App{ + Config: config.New(), + Db: db, + }, nil +} diff --git a/internal/controllers/taskcontroller/controller.go b/internal/controllers/taskcontroller/controller.go index 24702e4..46efa1b 100644 --- a/internal/controllers/taskcontroller/controller.go +++ b/internal/controllers/taskcontroller/controller.go @@ -1,98 +1,18 @@ package taskcontroller import ( - "net/http" - - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/common/errors" - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/taskservice" - "github.com/go-chi/chi/v5" - "github.com/go-chi/render" ) -type TaskUploadResponse struct { - TaskID string `json:"taskID"` - Repo string `json:"repo"` - StatusText string `json:"status"` -} - -func (rd *TaskUploadResponse) Render(w http.ResponseWriter, r *http.Request) error { - render.Status(r, http.StatusOK) - return nil -} - type TaskController struct { - config *config.Config + app *app.App taskService *taskservice.Service } -func New(cfg *config.Config, taskService *taskservice.Service) *TaskController { +func New(app *app.App, taskService *taskservice.Service) *TaskController { return &TaskController{ - config: cfg, + app: app, taskService: taskService, } } - -func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { - taskID := chi.URLParam(r, "taskID") - if taskID == "" { - render.Render(w, r, &errors.ErrResponse{ - HTTPStatusCode: http.StatusBadRequest, - StatusText: "taskID is required", - }) - return - } - - err := r.ParseMultipartForm(10240 << 20) - if err != nil { - render.Render(w, r, &errors.ErrResponse{ - HTTPStatusCode: http.StatusBadRequest, - StatusText: "Bad Request", - }) - return - } - - repo := r.FormValue("repo") - if repo == "" { - render.Render(w, r, &errors.ErrResponse{ - HTTPStatusCode: http.StatusBadRequest, - StatusText: "Missing required repo field", - }) - return - } - - files := r.MultipartForm.File["files"] - for _, fileHeader := range files { - if fileHeader.Size > (1024 << 20) { // Limit each file size to 10MB - render.Render(w, r, &errors.ErrResponse{ - HTTPStatusCode: http.StatusBadRequest, - StatusText: "File too large", - }) - return - } - } - - err = c.taskService.Upload(&taskservice.TaskUploadInput{ - TaskID: taskID, - Repo: repo, - Files: files, - }) - if err != nil { - render.Render(w, r, &errors.ErrResponse{ - HTTPStatusCode: http.StatusInternalServerError, - StatusText: "Internal Server Error", - Err: err, - }) - } - - response := TaskUploadResponse{ - TaskID: taskID, - Repo: repo, - StatusText: "Success!", - } - - if err := render.Render(w, r, &response); err != nil { - render.Render(w, r, errors.ErrRender(err)) - return - } -} diff --git a/internal/controllers/taskcontroller/create.go b/internal/controllers/taskcontroller/create.go new file mode 100644 index 0000000..8184a86 --- /dev/null +++ b/internal/controllers/taskcontroller/create.go @@ -0,0 +1,56 @@ +package taskcontroller + +import ( + "encoding/json" + "net/http" + + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/common/errors" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" + "github.com/go-chi/render" +) + +type CreateTaskDTO struct { + Repo string +} + +type CreateTaskResponse struct { + TaskID int `json:"taskID"` + Status models.TaskStatus `json:"status"` +} + +func (c *CreateTaskResponse) Render(w http.ResponseWriter, r *http.Request) error { + return nil +} + +func (c *TaskController) Create(w http.ResponseWriter, r *http.Request) { + createTaskDto := CreateTaskDTO{} + + if err := json.NewDecoder(r.Body).Decode(&createTaskDto); err != nil { + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: "Invalid JSON", + Err: err, + }) + return + } + + task, err := c.taskService.Create(createTaskDto.Repo) + if err != nil { + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusInternalServerError, + StatusText: "Internal Server Error", + Err: err, + }) + return + } + + response := CreateTaskResponse{ + TaskID: task.ID, + Status: task.Status, + } + + if err := render.Render(w, r, &response); err != nil { + render.Render(w, r, errors.ErrRender(err)) + return + } +} diff --git a/internal/controllers/taskcontroller/upload.go b/internal/controllers/taskcontroller/upload.go new file mode 100644 index 0000000..9700716 --- /dev/null +++ b/internal/controllers/taskcontroller/upload.go @@ -0,0 +1,87 @@ +package taskcontroller + +import ( + "fmt" + "net/http" + + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/common/errors" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/taskservice" + "github.com/go-chi/chi/v5" + "github.com/go-chi/render" +) + +type TaskUploadResponse struct { + TaskID string `json:"taskID"` + Repo string `json:"repo"` + StatusText string `json:"status"` +} + +func (rd *TaskUploadResponse) Render(w http.ResponseWriter, r *http.Request) error { + return nil +} + +func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { + taskID := chi.URLParam(r, "taskID") + if taskID == "" { + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: "taskID is required", + }) + return + } + + err := r.ParseMultipartForm(10240 << 20) + if err != nil { + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: "Bad Request", + }) + return + } + + repo := r.FormValue("repo") + if repo == "" { + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: "Missing required repo field", + }) + return + } + + files := r.MultipartForm.File["files"] + for _, fileHeader := range files { + if fileHeader.Size > (1024 << 20) { // Limit each file size to 10MB + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: "File too large", + }) + return + } + } + + err = c.taskService.Upload(&taskservice.TaskUploadInput{ + TaskID: taskID, + Repo: repo, + Files: files, + }) + if err != nil { + fmt.Println(err) + render.Render(w, r, &errors.ErrResponse{ + HTTPStatusCode: http.StatusInternalServerError, + StatusText: "Internal Server Error", + Err: err, + }) + return + } + + response := TaskUploadResponse{ + TaskID: taskID, + Repo: repo, + StatusText: "Success!", + } + + if err := render.Render(w, r, &response); err != nil { + render.Render(w, r, errors.ErrRender(err)) + return + } +} diff --git a/internal/models/db.go b/internal/models/db.go new file mode 100644 index 0000000..0a95f9b --- /dev/null +++ b/internal/models/db.go @@ -0,0 +1,57 @@ +package models + +import "gorm.io/gorm" + +type ALTRepo struct { + gorm.Model + + ID int + Name string `gorm:"uniqueIndex"` +} + +type GitRepo struct { + ID int + Name string `gorm:"uniqueIndex"` +} + +type RPMFiles struct { + TaskID int + Name string +} + +type TaskStatus int + +const ( + StatusPending TaskStatus = iota // 0 + StatusInProgress // 1 + StatusCompleted // 2 + StatusFailed // 3 + StatusCancelled // 4 +) + +type Task struct { + gorm.Model + + ID int + Status TaskStatus + + RepoID int + Repo GitRepo + ALTRepoID int + ALTRepo ALTRepo + + RPMFiles []RPMFiles +} + +type GitRepoAltRepoTask struct { + gorm.Model + + ID int + + RepoID int `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` + Repo GitRepo + ALTRepoID int `gorm:"uniqueIndex:idx_gr_ar_gitaltrepotask"` + ALTRepo ALTRepo + TaskID int + Task Task +} diff --git a/internal/models/models.go b/internal/models/models.go deleted file mode 100644 index 5d0d593..0000000 --- a/internal/models/models.go +++ /dev/null @@ -1,12 +0,0 @@ -package models - -type FileUpload struct { - TaskID string - - FileName string -} - -type Task struct { - TaskID string `json:"task_id"` - Link string `json:"link"` -} diff --git a/internal/router/router.go b/internal/router/router.go index b27a9a2..85d1bfb 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -4,19 +4,19 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/controllers/taskcontroller" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/middlewares" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/taskservice" ) type Router struct { - config *config.Config + app *app.App } -func New(config *config.Config) *Router { +func New(app *app.App) *Router { return &Router{ - config: config, + app: app, } } @@ -25,16 +25,21 @@ func (r *Router) Setup() *chi.Mux { router.Use(middleware.Logger) taskService := taskservice.New( - r.config, + r.app, ) taskController := taskcontroller.New( - r.config, + r.app, taskService, ) - router.Route("/task/{taskID}", func(cr chi.Router) { - cr.With(middlewares.CreateAuthGuard(r.config)).Post("/upload", taskController.Upload) + authGuard := middlewares.CreateAuthGuard(r.app.Config) + + router.Route("/tasks", func(taskRouter chi.Router) { + taskRouter.With(authGuard).Post("/", taskController.Create) + taskRouter.Route("/{taskID}", func(sTaskRouter chi.Router) { + sTaskRouter.With(authGuard).Post("/upload", taskController.Upload) + }) }) return router diff --git a/internal/services/taskservice/create.go b/internal/services/taskservice/create.go new file mode 100644 index 0000000..8b32f12 --- /dev/null +++ b/internal/services/taskservice/create.go @@ -0,0 +1,24 @@ +package taskservice + +import "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" + +func (s *Service) Create(repo string) (*models.Task, error) { + taskRepo := models.GitRepo{ + Name: repo, + } + s.app.Db.FirstOrCreate(&taskRepo) + + altRepo := models.ALTRepo{ + Name: "Sisyphus", + } + s.app.Db.FirstOrCreate(&altRepo) + + task := models.Task{ + RepoID: taskRepo.ID, + ALTRepo: altRepo, + } + + result := s.app.Db.Create(&task) + + return &task, result.Error +} diff --git a/internal/services/taskservice/service.go b/internal/services/taskservice/service.go index 6f0f292..68a0065 100644 --- a/internal/services/taskservice/service.go +++ b/internal/services/taskservice/service.go @@ -1,16 +1,19 @@ package taskservice import ( + "fmt" "io" "mime/multipart" "os" + "os/exec" "path" + "strings" - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/config" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/app" ) type Service struct { - config *config.Config + app *app.App } type TaskUploadInput struct { @@ -20,19 +23,30 @@ type TaskUploadInput struct { Files []*multipart.FileHeader } -func New(cfg *config.Config) *Service { +func New(app *app.App) *Service { return &Service{ - config: cfg, + app: app, } } +func getRPMArchitecture(filePath string) (string, error) { + cmd := exec.Command("rpm", "-qp", "--queryformat", "%{ARCH}", filePath) + + output, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("ошибка при выполнении команды rpm: %v", err) + } + + arch := strings.TrimSpace(string(output)) + return arch, nil +} + func (s *Service) Upload(input *TaskUploadInput) error { - repo := input.Repo taskID := input.TaskID files := input.Files - localPath := path.Join(repo, "task", taskID) - taskFolderPath := path.Join(s.config.UploadDir, "extra", localPath) + localPath := path.Join(taskID) + taskFolderPath := path.Join(s.app.Config.UploadDir, "tasks", localPath) os.MkdirAll(taskFolderPath, os.ModePerm) for _, fileHeader := range files { @@ -63,9 +77,15 @@ func (s *Service) Upload(input *TaskUploadInput) error { if err != nil { return err } + + arch, err := getRPMArchitecture(filePath) + if err != nil { + return err + } + // Символическая ссылка - targetPath := path.Join("../extra/", localPath, fileHeader.Filename) - symLink := path.Join(s.config.UploadDir, "out", fileHeader.Filename) + targetPath := path.Join("../../../../tasks/", localPath, fileHeader.Filename) + symLink := path.Join(s.app.Config.UploadDir, "repo/Sisyphus", arch, "RPMS.aides", fileHeader.Filename) err = createSymlink(targetPath, symLink) if err != nil { return err