diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b95c93c..3432c1e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "build": { "dockerfile": "Dockerfile", "args": { - "GO_VERSION": "1.24.0", + "GO_VERSION": "1.24.1", "GOLANGCI_LINT_VERSION": "1.64.5", "MIGRATE_VERSION": "4.18.2" } diff --git a/.woodpecker/.build.yml b/build/ci/.build.yml similarity index 100% rename from .woodpecker/.build.yml rename to build/ci/.build.yml diff --git a/.woodpecker/.lint.yml b/build/ci/.lint.yml similarity index 100% rename from .woodpecker/.lint.yml rename to build/ci/.lint.yml diff --git a/.woodpecker/.publish.yml b/build/ci/.publish.yml similarity index 100% rename from .woodpecker/.publish.yml rename to build/ci/.publish.yml diff --git a/.woodpecker/.tests.yml b/build/ci/.tests.yml similarity index 100% rename from .woodpecker/.tests.yml rename to build/ci/.tests.yml diff --git a/Dockerfile b/build/docker/Dockerfile similarity index 100% rename from Dockerfile rename to build/docker/Dockerfile diff --git a/cmd/api/main.go b/cmd/api/main.go index c3545bc..0a89ccb 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -11,7 +11,7 @@ import ( "syscall" "time" - "gitea.qpismont.fr/qpismont/trepa/internal/accounts" + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/api" "gitea.qpismont.fr/qpismont/trepa/internal/core" "github.com/jmoiron/sqlx" ) @@ -105,7 +105,7 @@ func setupDB() *sqlx.DB { func setupRouter(db *sqlx.DB) *core.ServerMux { router := core.NewServerMux() - accounts.BindRoutes(router, db) + api.BindRoutes(router, db) return router } diff --git a/go.mod b/go.mod index 4da147d..15d4a83 100644 --- a/go.mod +++ b/go.mod @@ -14,12 +14,19 @@ require ( require ( github.com/cockroachdb/apd v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.25.0 // indirect github.com/gofrs/uuid v4.4.0+incompatible // indirect github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect golang.org/x/crypto v0.33.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index dee274e..c4f4b80 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,14 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 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/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= +github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA= @@ -18,6 +26,8 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= @@ -34,6 +44,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/internal/accounts/api/controller.go b/internal/accounts/api/controller.go new file mode 100644 index 0000000..11b7522 --- /dev/null +++ b/internal/accounts/api/controller.go @@ -0,0 +1,33 @@ +package api + +import ( + "encoding/json" + "net/http" + + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "github.com/go-playground/validator/v10" +) + +var validate *validator.Validate + +type Controller struct { + service domain.AccountService +} + +func NewController(service domain.AccountService) Controller { + return Controller{service: service} +} + +func (c *Controller) Login(w *core.Response, r *http.Request) { + var request LoginAccountRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + w.WriteError(core.ErrInvalidStruct) + return + } + + if err := validate.Struct(request); err != nil { + w.WriteError(core.ErrInvalidStruct) + return + } +} diff --git a/internal/accounts/api/dto.go b/internal/accounts/api/dto.go new file mode 100644 index 0000000..aadd037 --- /dev/null +++ b/internal/accounts/api/dto.go @@ -0,0 +1,6 @@ +package api + +type LoginAccountRequest struct { + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` +} diff --git a/internal/accounts/api/routes.go b/internal/accounts/api/routes.go new file mode 100644 index 0000000..3f088db --- /dev/null +++ b/internal/accounts/api/routes.go @@ -0,0 +1,16 @@ +package api + +import ( + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/repository" + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/service" + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "github.com/jmoiron/sqlx" +) + +func BindRoutes(srv *core.ServerMux, db *sqlx.DB) { + repository := repository.NewRepository(db) + service := service.NewService(repository) + controller := NewController(service) + + srv.HandleFunc("POST /accounts/login", controller.Login) +} diff --git a/internal/accounts/controller.go b/internal/accounts/controller.go deleted file mode 100644 index a807d79..0000000 --- a/internal/accounts/controller.go +++ /dev/null @@ -1,20 +0,0 @@ -package accounts - -import ( - "net/http" - - "gitea.qpismont.fr/qpismont/trepa/internal/core" -) - -type Controller struct { - service Service -} - -func NewController(service Service) Controller { - return Controller{service: service} -} - -func (c *Controller) CreateAccount(w *core.Response, r *http.Request) { - w.WriteHeader(http.StatusCreated) - w.Json([]byte("test")) -} diff --git a/internal/accounts/model.go b/internal/accounts/domain/account.go similarity index 94% rename from internal/accounts/model.go rename to internal/accounts/domain/account.go index fad9d86..6f9d139 100644 --- a/internal/accounts/model.go +++ b/internal/accounts/domain/account.go @@ -1,4 +1,4 @@ -package accounts +package domain type Account struct { Id int `db:"id" json:"id"` diff --git a/internal/accounts/errors.go b/internal/accounts/domain/errors.go similarity index 95% rename from internal/accounts/errors.go rename to internal/accounts/domain/errors.go index 2f1428c..2df998b 100644 --- a/internal/accounts/errors.go +++ b/internal/accounts/domain/errors.go @@ -1,4 +1,4 @@ -package accounts +package domain import ( "net/http" diff --git a/internal/accounts/domain/repository.go b/internal/accounts/domain/repository.go new file mode 100644 index 0000000..1bf6b12 --- /dev/null +++ b/internal/accounts/domain/repository.go @@ -0,0 +1,6 @@ +package domain + +type AccountRepository interface { + Insert(account *Account) (int, error) + FetchOneByUsername(username string) (*Account, error) +} diff --git a/internal/accounts/domain/service.go b/internal/accounts/domain/service.go new file mode 100644 index 0000000..8baaca8 --- /dev/null +++ b/internal/accounts/domain/service.go @@ -0,0 +1,4 @@ +package domain + +type AccountService interface { +} diff --git a/internal/accounts/repository.go b/internal/accounts/repository/account.go similarity index 60% rename from internal/accounts/repository.go rename to internal/accounts/repository/account.go index 59da0f5..5c6f42c 100644 --- a/internal/accounts/repository.go +++ b/internal/accounts/repository/account.go @@ -1,8 +1,9 @@ -package accounts +package repository import ( "database/sql" + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" "github.com/jmoiron/sqlx" ) @@ -10,11 +11,11 @@ type Repository struct { db *sqlx.DB } -func NewRepository(db *sqlx.DB) Repository { - return Repository{db: db} +func NewRepository(db *sqlx.DB) domain.AccountRepository { + return &Repository{db: db} } -func (r *Repository) Insert(account *Account) (int, error) { +func (r *Repository) Insert(account *domain.Account) (int, error) { var id int stmt, err := r.db.Prepare(SqlInsert) @@ -32,8 +33,8 @@ func (r *Repository) Insert(account *Account) (int, error) { return id, nil } -func (r *Repository) FetchOneByUsername(username string) (*Account, error) { - var account Account +func (r *Repository) FetchOneByUsername(username string) (*domain.Account, error) { + var account domain.Account err := r.db.Get(&account, SqlFetchOneByUsername, username) if err != nil { diff --git a/internal/accounts/repository_test.go b/internal/accounts/repository/account_test.go similarity index 88% rename from internal/accounts/repository_test.go rename to internal/accounts/repository/account_test.go index 552f6b1..0e56437 100644 --- a/internal/accounts/repository_test.go +++ b/internal/accounts/repository/account_test.go @@ -1,8 +1,9 @@ -package accounts +package repository import ( "testing" + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" "gitea.qpismont.fr/qpismont/trepa/test" "github.com/magiconair/properties/assert" ) @@ -13,7 +14,7 @@ func TestRepository_Insert(t *testing.T) { repo := NewRepository(db) - account := &Account{ + account := &domain.Account{ Username: "test", Password: "test", RoleId: 1, diff --git a/internal/accounts/const.go b/internal/accounts/repository/const.go similarity index 90% rename from internal/accounts/const.go rename to internal/accounts/repository/const.go index 6e417dd..03b80c2 100644 --- a/internal/accounts/const.go +++ b/internal/accounts/repository/const.go @@ -1,4 +1,4 @@ -package accounts +package repository const ( SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id" diff --git a/internal/accounts/routes.go b/internal/accounts/routes.go deleted file mode 100644 index 01b8553..0000000 --- a/internal/accounts/routes.go +++ /dev/null @@ -1,14 +0,0 @@ -package accounts - -import ( - "gitea.qpismont.fr/qpismont/trepa/internal/core" - "github.com/jmoiron/sqlx" -) - -func BindRoutes(srv *core.ServerMux, db *sqlx.DB) { - repository := NewRepository(db) - service := NewService(repository) - controller := NewController(service) - - srv.HandleFunc("GET /accounts", controller.CreateAccount) -} diff --git a/internal/accounts/service.go b/internal/accounts/service.go deleted file mode 100644 index 87f47c5..0000000 --- a/internal/accounts/service.go +++ /dev/null @@ -1,9 +0,0 @@ -package accounts - -type Service struct { - repository Repository -} - -func NewService(repository Repository) Service { - return Service{repository: repository} -} diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go new file mode 100644 index 0000000..8cca60c --- /dev/null +++ b/internal/accounts/service/account.go @@ -0,0 +1,11 @@ +package service + +import "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" + +type Service struct { + repository domain.AccountRepository +} + +func NewService(repository domain.AccountRepository) domain.AccountService { + return &Service{repository: repository} +} diff --git a/internal/core/errors.go b/internal/core/errors.go index c0ca822..545cccd 100644 --- a/internal/core/errors.go +++ b/internal/core/errors.go @@ -1,11 +1,13 @@ package core import ( + "encoding/json" "net/http" ) var ( - ErrInvalidToken = NewHTTPError(http.StatusUnauthorized, "Invalid token", nil) + ErrInvalidToken = NewHTTPError(http.StatusUnauthorized, "Invalid token", nil) + ErrInvalidStruct = NewHTTPError(http.StatusBadRequest, "Invalid struct", nil) ) type HTTPError struct { @@ -23,6 +25,14 @@ func (e *HTTPError) Unwrap() error { return e.Cause } +func (e *HTTPError) IntoJsonBytes() []byte { + json, err := json.Marshal(e) + if err != nil { + return nil + } + return json +} + func NewHTTPError(code int, message string, cause error) *HTTPError { details := "" if cause != nil { diff --git a/internal/core/http.go b/internal/core/http.go index 23fb068..a4e4aad 100644 --- a/internal/core/http.go +++ b/internal/core/http.go @@ -26,6 +26,12 @@ func (r *Response) WriteHeader(status int) { r.Status = status } +func (r *Response) WriteError(err *HTTPError) { + r.Header().Set("Content-Type", "application/json") + r.WriteHeader(err.Code) + r.Write(err.IntoJsonBytes()) +} + func (r *Response) Json(json []byte) { r.Header().Set("Content-Type", "application/json") r.Write(json)