diff --git a/.forgejo/workflows/format.yaml b/.forgejo/workflows/format-lint-test.yaml similarity index 60% rename from .forgejo/workflows/format.yaml rename to .forgejo/workflows/format-lint-test.yaml index f6289ac..e87c328 100644 --- a/.forgejo/workflows/format.yaml +++ b/.forgejo/workflows/format-lint-test.yaml @@ -23,3 +23,19 @@ jobs: - name: Run Format Check run: | make format + + test: + runs-on: docker + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.23.3' + + - name: Run Tests + run: | + make test-coverage diff --git a/Makefile b/Makefile index 818b0c7..2ff2e4a 100644 --- a/Makefile +++ b/Makefile @@ -38,3 +38,6 @@ build-docker: -t ghcr.io/aides-infra/aides-repo-api:${DOCKER_TAG}\ . +test-coverage: + go test ./... -v -coverpkg=./... -coverprofile=coverage.out + bash scripts/coverage-badge.sh diff --git a/README.md b/README.md index 6224fdb..ec3df60 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ # Aides repo API -[![Go Report Card](https://goreportcard.com/badge/code.alt-gnome.ru/aides-infra/aides-repo-api)](https://goreportcard.com/report/code.alt-gnome.ru/aides-infra/aides-repo-api) \ No newline at end of file +[![Go Report Card](https://goreportcard.com/badge/code.alt-gnome.ru/aides-infra/aides-repo-api)](https://goreportcard.com/report/code.alt-gnome.ru/aides-infra/aides-repo-api) + +![Coverage](./coverage-badge.svg) \ No newline at end of file diff --git a/cmd/aides-repo-api/main.go b/cmd/aides-repo-api/main.go index 61ce37c..bec75cf 100644 --- a/cmd/aides-repo-api/main.go +++ b/cmd/aides-repo-api/main.go @@ -8,13 +8,20 @@ import ( // // @title Aides Repo API // @description API For Aides repo -// @schemes https +// @schemes http https // // @license.name GPL-3.0 // @license.url https://www.gnu.org/licenses/gpl-3.0-standalone.html // // @tag.name tasks // @tag.description Work with tags +// + +// @securityDefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization +// @description Type "Bearer" followed by a space and token. + func main() { app, err := app.New() if err != nil { diff --git a/coverage-badge.svg b/coverage-badge.svg new file mode 100644 index 0000000..f305a5c --- /dev/null +++ b/coverage-badge.svg @@ -0,0 +1,6 @@ + + + + coverage + 22.3% + diff --git a/docs/docs.go b/docs/docs.go index 8b0bbb7..cff3a83 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -21,6 +21,11 @@ const docTemplate = `{ "paths": { "/tasks": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "description": "Create a new task for a specific repository", "consumes": [ "application/json" @@ -67,6 +72,11 @@ const docTemplate = `{ }, "/tasks/{taskID}/upload": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "description": "Upload multiple files associated with a specific task ID. Each file must be less than 10MB.", "consumes": [ "multipart/form-data" @@ -190,6 +200,14 @@ const docTemplate = `{ } } }, + "securityDefinitions": { + "ApiKeyAuth": { + "description": "Type \"Bearer\" followed by a space and token.", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, "tags": [ { "description": "Work with tags", @@ -203,7 +221,7 @@ var SwaggerInfo = &swag.Spec{ Version: "", Host: "", BasePath: "", - Schemes: []string{"https"}, + Schemes: []string{"http", "https"}, Title: "Aides Repo API", Description: "API For Aides repo", InfoInstanceName: "swagger", diff --git a/docs/swagger.json b/docs/swagger.json index 7c89391..385c2a0 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1,5 +1,6 @@ { "schemes": [ + "http", "https" ], "swagger": "2.0", @@ -15,6 +16,11 @@ "paths": { "/tasks": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "description": "Create a new task for a specific repository", "consumes": [ "application/json" @@ -61,6 +67,11 @@ }, "/tasks/{taskID}/upload": { "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], "description": "Upload multiple files associated with a specific task ID. Each file must be less than 10MB.", "consumes": [ "multipart/form-data" @@ -184,6 +195,14 @@ } } }, + "securityDefinitions": { + "ApiKeyAuth": { + "description": "Type \"Bearer\" followed by a space and token.", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, "tags": [ { "description": "Work with tags", diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 77ebdd3..82675ff 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -85,6 +85,8 @@ paths: description: Internal server error schema: $ref: '#/definitions/errors.ErrResponse' + security: + - ApiKeyAuth: [] summary: Create a new task tags: - tasks @@ -120,11 +122,20 @@ paths: description: Internal Server Error schema: $ref: '#/definitions/errors.ErrResponse' + security: + - ApiKeyAuth: [] summary: Upload files to a task tags: - tasks schemes: +- http - https +securityDefinitions: + ApiKeyAuth: + description: Type "Bearer" followed by a space and token. + in: header + name: Authorization + type: apiKey swagger: "2.0" tags: - description: Work with tags diff --git a/go.mod b/go.mod index 0fde56f..ee88673 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,21 @@ go 1.23.3 require ( github.com/caarlos0/env/v11 v11.2.2 + github.com/cavaliergopher/rpm v1.2.0 github.com/go-chi/chi/v5 v5.1.0 github.com/go-chi/render v1.0.3 + github.com/stretchr/testify v1.10.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect ) require ( github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.2.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect github.com/ajg/form v1.5.1 // indirect - github.com/go-co-op/gocron/v2 v2.13.0 // indirect + github.com/go-co-op/gocron/v2 v2.13.0 github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/spec v0.21.0 // indirect @@ -31,20 +36,19 @@ require ( github.com/mattn/go-sqlite3 v1.14.24 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/swaggo/files v1.0.1 // indirect - github.com/swaggo/http-swagger v1.3.4 // indirect - github.com/swaggo/swag v1.16.4 // indirect + github.com/swaggo/http-swagger v1.3.4 + github.com/swaggo/swag v1.16.4 go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.0 golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 // indirect golang.org/x/net v0.32.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.28.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.5.11 // indirect - gorm.io/driver/sqlite v1.5.7 // indirect - gorm.io/gorm v1.25.12 // indirect + gorm.io/driver/postgres v1.5.11 + gorm.io/driver/sqlite v1.5.7 + gorm.io/gorm v1.25.12 moul.io/zapgorm2 v1.3.0 ) diff --git a/go.sum b/go.sum index 95b2111..ec51ca0 100644 --- a/go.sum +++ b/go.sum @@ -1,15 +1,14 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/PuerkitoBio/purell v1.2.1 h1:QsZ4TjvwiMpat6gBCBxEQI0rcS9ehtkKtSpiUnd9N28= -github.com/PuerkitoBio/purell v1.2.1/go.mod h1:ZwHcC/82TOaovDi//J/804umJFFmbOHPngi8iYYv/Eo= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/caarlos0/env/v11 v11.2.2 h1:95fApNrUyueipoZN/EhA8mMxiNxrBwDa+oAZrMWl3Kg= github.com/caarlos0/env/v11 v11.2.2/go.mod h1:JBfcdeQiBoI3Zh1QRAWfe+tpiNTmDtcCj/hHHHMx0vc= +github.com/cavaliergopher/rpm v1.2.0 h1:s0h+QeVK252QFTolkhGiMeQ1f+tMeIMhGl8B1HUmGUc= +github.com/cavaliergopher/rpm v1.2.0/go.mod h1:R0q3vTqa7RUvPofAZYrnjJ63hh2vngjFfphuXiExVos= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -45,20 +44,28 @@ github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 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= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= @@ -69,6 +76,8 @@ github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -86,6 +95,8 @@ golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXy golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -130,9 +141,9 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/controllers/taskcontroller/create.go b/internal/controllers/taskcontroller/create.go index baad0b8..25324fa 100644 --- a/internal/controllers/taskcontroller/create.go +++ b/internal/controllers/taskcontroller/create.go @@ -28,6 +28,7 @@ func (c *CreateTaskResponse) Render(w http.ResponseWriter, r *http.Request) erro // @Summary Create a new task // @Description Create a new task for a specific repository // @Tags tasks +// @Security ApiKeyAuth // @Accept json // @Produce json // @Param body body CreateTaskDTO true "Request body to create a task" diff --git a/internal/controllers/taskcontroller/upload.go b/internal/controllers/taskcontroller/upload.go index 7d4c2eb..e6c8a86 100644 --- a/internal/controllers/taskcontroller/upload.go +++ b/internal/controllers/taskcontroller/upload.go @@ -1,12 +1,14 @@ package taskcontroller import ( + "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/go-chi/render" - "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/common/errors" + commonErrors "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/common/errors" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/logger" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/services/taskservice" ) @@ -25,6 +27,7 @@ func (rd *TaskUploadResponse) Render(w http.ResponseWriter, r *http.Request) err // @Summary Upload files to a task // @Description Upload multiple files associated with a specific task ID. Each file must be less than 10MB. // @Tags tasks +// @Security ApiKeyAuth // @Accept multipart/form-data // @Produce json // @Param taskID path string true "Task ID" @@ -36,7 +39,7 @@ func (rd *TaskUploadResponse) Render(w http.ResponseWriter, r *http.Request) err func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { taskID := chi.URLParam(r, "taskID") if taskID == "" { - render.Render(w, r, &errors.ErrResponse{ + render.Render(w, r, &commonErrors.ErrResponse{ HTTPStatusCode: http.StatusBadRequest, StatusText: "taskID is required", }) @@ -45,7 +48,7 @@ func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { err := r.ParseMultipartForm(10240 << 20) if err != nil { - render.Render(w, r, &errors.ErrResponse{ + render.Render(w, r, &commonErrors.ErrResponse{ HTTPStatusCode: http.StatusBadRequest, StatusText: "Bad Request", }) @@ -55,7 +58,7 @@ func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { files := r.MultipartForm.File["files"] for _, fileHeader := range files { if fileHeader.Size > (1024 << 20) { - render.Render(w, r, &errors.ErrResponse{ + render.Render(w, r, &commonErrors.ErrResponse{ HTTPStatusCode: http.StatusBadRequest, StatusText: "File too large", }) @@ -72,7 +75,18 @@ func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { log.Error("Error while upload task", map[string]interface{}{ "err": err, }) - render.Render(w, r, &errors.ErrResponse{ + + var e *taskservice.TooOldError + if errors.As(err, &e) { + render.Render(w, r, &commonErrors.ErrResponse{ + HTTPStatusCode: http.StatusBadRequest, + StatusText: err.Error(), + Err: err, + }) + return + } + + render.Render(w, r, &commonErrors.ErrResponse{ HTTPStatusCode: http.StatusInternalServerError, StatusText: "Internal Server Error", Err: err, @@ -86,7 +100,7 @@ func (c *TaskController) Upload(w http.ResponseWriter, r *http.Request) { } if err := render.Render(w, r, &response); err != nil { - render.Render(w, r, errors.ErrRender(err)) + render.Render(w, r, commonErrors.ErrRender(err)) return } } diff --git a/internal/models/db.go b/internal/models/db.go index 0def57a..950a1f9 100644 --- a/internal/models/db.go +++ b/internal/models/db.go @@ -22,8 +22,13 @@ type RPMFile struct { TaskID int Task Task - Name string - Arch string + Filename string + + Name string + Arch string + Version string + Release string + Epoch int } type TaskStatus int diff --git a/internal/services/reposervice/service.go b/internal/services/reposervice/service.go index 0548672..a987dd1 100644 --- a/internal/services/reposervice/service.go +++ b/internal/services/reposervice/service.go @@ -123,13 +123,13 @@ func (s *Service) ForceUpdate() error { for _, el := range tasks { for _, fileInfo := range el.Files { localFilePath := path.Join( - strconv.FormatUint(uint64(el.ID), 10), fileInfo.Name, + strconv.FormatUint(uint64(el.ID), 10), fileInfo.Filename, ) symLink := path.Join( futureRepoPath, fileInfo.Arch, "RPMS.aides", - fileInfo.Name, + fileInfo.Filename, ) targetPath := path.Join("../../../../tasks/", localFilePath) err := createSymlink(targetPath, symLink) diff --git a/internal/services/taskservice/create.go b/internal/services/taskservice/create.go index 91d8cef..208e67d 100644 --- a/internal/services/taskservice/create.go +++ b/internal/services/taskservice/create.go @@ -23,6 +23,7 @@ func (s *Service) Create(repo string) (*models.Task, error) { RepoID: taskRepo.ID, ALTRepo: altRepo, Type: models.TypeUpsert, + Status: models.StatusPending, } result := s.db.Create(&task) diff --git a/internal/services/taskservice/errors.go b/internal/services/taskservice/errors.go new file mode 100644 index 0000000..e36f74f --- /dev/null +++ b/internal/services/taskservice/errors.go @@ -0,0 +1,11 @@ +package taskservice + +import "fmt" + +type TooOldError struct { + Name string +} + +func (e TooOldError) Error() string { + return fmt.Sprintf("%s is too old!", e.Name) +} diff --git a/internal/services/taskservice/upload.go b/internal/services/taskservice/upload.go index 2f27fef..df2efa3 100644 --- a/internal/services/taskservice/upload.go +++ b/internal/services/taskservice/upload.go @@ -6,13 +6,14 @@ import ( "io" "mime/multipart" "os" - "os/exec" "path" "strconv" "strings" + "github.com/cavaliergopher/rpm" "gorm.io/gorm" + "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/logger" "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" ) @@ -22,19 +23,9 @@ type TaskUploadInput struct { Files []*multipart.FileHeader } -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 { + log := logger.GetLogger() + taskID, err := strconv.Atoi(input.TaskID) if err != nil { return err @@ -42,12 +33,11 @@ func (s *Service) Upload(input *TaskUploadInput) error { files := input.Files task := models.Task{} - result := s.db.Preload("Repo").Where( - "id = ?", taskID, - ).First(&task) - /*.Where( - "status = ?", models.StatusPending, - )*/ + result := s.db. + Preload("Repo"). + Where("id = ?", taskID). + First(&task). + Where("status = ?", models.StatusPending) if errors.Is(result.Error, gorm.ErrRecordNotFound) { return result.Error @@ -56,17 +46,45 @@ func (s *Service) Upload(input *TaskUploadInput) error { return result.Error } + currentTask := &models.Task{} + result = s.db. + Model(&models.GitRepoAltRepoTask{}). + Select("tasks.*"). + Joins("JOIN tasks ON tasks.id = git_repo_alt_repo_tasks.current_task_id"). + Where(&models.GitRepoAltRepoTask{ + ALTRepoID: task.ALTRepoID, + RepoID: task.RepoID, + }). + Preload("Files"). + Limit(1). + Find(currentTask) + if result.Error != nil { + return result.Error + } + + log.Debug("", map[string]interface{}{ + "val": currentTask, + }) + localPath := path.Join(input.TaskID) taskFolderPath := path.Join(s.config.GetUploadDir(), "tasks", localPath) err = os.MkdirAll(taskFolderPath, os.ModePerm) if err != nil { return err } + shouldCleanTask := true + defer func() { + if shouldCleanTask { + os.RemoveAll(taskFolderPath) + } + }() + + rpmFiles := []models.RPMFile{} for _, fileHeader := range files { file, err := fileHeader.Open() if err != nil { - return err + return fmt.Errorf("can't open fileHeader %w", err) } defer file.Close() @@ -96,34 +114,56 @@ func (s *Service) Upload(input *TaskUploadInput) error { return err } - arch, err := getRPMArchitecture(filePath) + pkg, err := rpm.Open(filePath) if err != nil { return err } f := models.RPMFile{ - Name: fileHeader.Filename, - Arch: arch, - Task: task, + Filename: fileHeader.Filename, + Name: pkg.Name(), + Arch: pkg.Architecture(), + Version: pkg.Version(), + Release: pkg.Release(), + Epoch: pkg.Epoch(), + Task: task, } - if err := s.db.Save(&f).Error; err != nil { - return err + rpmFiles = append(rpmFiles, f) + } + + if currentTask.ID != 0 { + groupedByName := make(map[string][]*models.RPMFile) + for _, file := range rpmFiles { + groupedByName[file.Name] = append(groupedByName[file.Name], &file) + } + for _, file := range currentTask.Files { + groupedByName[file.Name] = append(groupedByName[file.Name], &file) } - // Символическая ссылка - /* - 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 + for name, group := range groupedByName { + if len(group) > 1 { + result := rpm.Compare( + &WrapperRpmVersion{group[0]}, + &WrapperRpmVersion{group[1]}, + ) + + if result < 1 { + return &TooOldError{ + Name: name, + } + } } - */ + } + } + result = s.db.Create(&rpmFiles) + if result.Error != nil { + return result.Error } task.Status = models.StatusCompleted + shouldCleanTask = false return s.onTaskComplete(&task) } diff --git a/internal/services/taskservice/utils.go b/internal/services/taskservice/utils.go new file mode 100644 index 0000000..860b9c4 --- /dev/null +++ b/internal/services/taskservice/utils.go @@ -0,0 +1,11 @@ +package taskservice + +import "code.alt-gnome.ru/aides-infra/aides-repo-api/internal/models" + +type WrapperRpmVersion struct { + file *models.RPMFile +} + +func (c *WrapperRpmVersion) Epoch() int { return c.file.Epoch } +func (c *WrapperRpmVersion) Version() string { return c.file.Version } +func (c *WrapperRpmVersion) Release() string { return c.file.Release } diff --git a/scripts/coverage-badge.sh b/scripts/coverage-badge.sh new file mode 100755 index 0000000..fc2b512 --- /dev/null +++ b/scripts/coverage-badge.sh @@ -0,0 +1,10 @@ +#!/bin/bash +COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') +cat < coverage-badge.svg + + + + coverage + ${COVERAGE}% + +EOF \ No newline at end of file