From dcc5df8300b85edf27206f49314de3efdf990d16 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 11 Mar 2025 21:42:36 +0000 Subject: [PATCH 01/15] starting accounts --- .devcontainer/devcontainer.json | 2 +- {.woodpecker => build/ci}/.build.yml | 0 {.woodpecker => build/ci}/.lint.yml | 0 {.woodpecker => build/ci}/.publish.yml | 0 {.woodpecker => build/ci}/.tests.yml | 0 Dockerfile => build/docker/Dockerfile | 0 cmd/api/main.go | 4 +-- go.mod | 7 ++++ go.sum | 14 ++++++++ internal/accounts/api/controller.go | 33 +++++++++++++++++++ internal/accounts/api/dto.go | 6 ++++ internal/accounts/api/routes.go | 16 +++++++++ internal/accounts/controller.go | 20 ----------- .../accounts/{model.go => domain/account.go} | 2 +- internal/accounts/{ => domain}/errors.go | 2 +- internal/accounts/domain/repository.go | 6 ++++ internal/accounts/domain/service.go | 4 +++ .../{repository.go => repository/account.go} | 13 ++++---- .../account_test.go} | 5 +-- internal/accounts/{ => repository}/const.go | 2 +- internal/accounts/routes.go | 14 -------- internal/accounts/service.go | 9 ----- internal/accounts/service/account.go | 11 +++++++ internal/core/errors.go | 12 ++++++- internal/core/http.go | 6 ++++ 25 files changed, 130 insertions(+), 58 deletions(-) rename {.woodpecker => build/ci}/.build.yml (100%) rename {.woodpecker => build/ci}/.lint.yml (100%) rename {.woodpecker => build/ci}/.publish.yml (100%) rename {.woodpecker => build/ci}/.tests.yml (100%) rename Dockerfile => build/docker/Dockerfile (100%) create mode 100644 internal/accounts/api/controller.go create mode 100644 internal/accounts/api/dto.go create mode 100644 internal/accounts/api/routes.go delete mode 100644 internal/accounts/controller.go rename internal/accounts/{model.go => domain/account.go} (94%) rename internal/accounts/{ => domain}/errors.go (95%) create mode 100644 internal/accounts/domain/repository.go create mode 100644 internal/accounts/domain/service.go rename internal/accounts/{repository.go => repository/account.go} (60%) rename internal/accounts/{repository_test.go => repository/account_test.go} (88%) rename internal/accounts/{ => repository}/const.go (90%) delete mode 100644 internal/accounts/routes.go delete mode 100644 internal/accounts/service.go create mode 100644 internal/accounts/service/account.go 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) -- 2.45.2 From 26ce8522acfb518dad2e3f3c3a99dfcf9b9860c3 Mon Sep 17 00:00:00 2001 From: qpismont Date: Wed, 12 Mar 2025 21:36:57 +0000 Subject: [PATCH 02/15] Refactor account handling: update Login method signature, introduce AccountLogin and AccountCreate types, modify repository interface, and implement login logic in service. Add environment variable loading and hashing functions with tests. --- internal/accounts/api/controller.go | 3 ++- internal/accounts/domain/account.go | 11 ++++++++++ internal/accounts/domain/repository.go | 2 +- internal/accounts/domain/service.go | 3 +++ internal/accounts/repository/account.go | 2 +- internal/accounts/repository/account_test.go | 2 +- internal/accounts/service/account.go | 18 +++++++++++++++- internal/core/{helpers.go => env.go} | 0 .../core/{helpers_test.go => env_test.go} | 0 internal/core/hash.go | 12 +++++++++++ internal/core/hash_test.go | 21 +++++++++++++++++++ 11 files changed, 69 insertions(+), 5 deletions(-) rename internal/core/{helpers.go => env.go} (100%) rename internal/core/{helpers_test.go => env_test.go} (100%) create mode 100644 internal/core/hash.go create mode 100644 internal/core/hash_test.go diff --git a/internal/accounts/api/controller.go b/internal/accounts/api/controller.go index 11b7522..1a246f1 100644 --- a/internal/accounts/api/controller.go +++ b/internal/accounts/api/controller.go @@ -19,7 +19,7 @@ func NewController(service domain.AccountService) Controller { return Controller{service: service} } -func (c *Controller) Login(w *core.Response, r *http.Request) { +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) @@ -30,4 +30,5 @@ func (c *Controller) Login(w *core.Response, r *http.Request) { w.WriteError(core.ErrInvalidStruct) return } + } diff --git a/internal/accounts/domain/account.go b/internal/accounts/domain/account.go index 6f9d139..d4209c7 100644 --- a/internal/accounts/domain/account.go +++ b/internal/accounts/domain/account.go @@ -8,3 +8,14 @@ type Account struct { CreatedAt string `db:"created_at" json:"created_at"` UpdatedAt string `db:"updated_at" json:"updated_at"` } + +type AccountLogin struct { + Username string + Password string +} + +type AccountCreate struct { + Username string + Password string + RoleId int +} diff --git a/internal/accounts/domain/repository.go b/internal/accounts/domain/repository.go index 1bf6b12..72210eb 100644 --- a/internal/accounts/domain/repository.go +++ b/internal/accounts/domain/repository.go @@ -1,6 +1,6 @@ package domain type AccountRepository interface { - Insert(account *Account) (int, error) + Insert(account Account) (int, error) FetchOneByUsername(username string) (*Account, error) } diff --git a/internal/accounts/domain/service.go b/internal/accounts/domain/service.go index 8baaca8..7cc23a9 100644 --- a/internal/accounts/domain/service.go +++ b/internal/accounts/domain/service.go @@ -1,4 +1,7 @@ package domain +import "gitea.qpismont.fr/qpismont/trepa/internal/core" + type AccountService interface { + Login(login AccountLogin) (*Account, *core.HTTPError) } diff --git a/internal/accounts/repository/account.go b/internal/accounts/repository/account.go index 5c6f42c..17caafb 100644 --- a/internal/accounts/repository/account.go +++ b/internal/accounts/repository/account.go @@ -15,7 +15,7 @@ func NewRepository(db *sqlx.DB) domain.AccountRepository { return &Repository{db: db} } -func (r *Repository) Insert(account *domain.Account) (int, error) { +func (r *Repository) Insert(account domain.Account) (int, error) { var id int stmt, err := r.db.Prepare(SqlInsert) diff --git a/internal/accounts/repository/account_test.go b/internal/accounts/repository/account_test.go index 0e56437..e1aaf7b 100644 --- a/internal/accounts/repository/account_test.go +++ b/internal/accounts/repository/account_test.go @@ -14,7 +14,7 @@ func TestRepository_Insert(t *testing.T) { repo := NewRepository(db) - account := &domain.Account{ + account := domain.Account{ Username: "test", Password: "test", RoleId: 1, diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index 8cca60c..9925549 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -1,6 +1,9 @@ package service -import "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" +import ( + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" + "gitea.qpismont.fr/qpismont/trepa/internal/core" +) type Service struct { repository domain.AccountRepository @@ -9,3 +12,16 @@ type Service struct { func NewService(repository domain.AccountRepository) domain.AccountService { return &Service{repository: repository} } + +func (s *Service) Login(login domain.AccountLogin) (*domain.Account, *core.HTTPError) { + account, err := s.repository.FetchOneByUsername(login.Username) + if err != nil { + return nil, domain.ErrAccountNotFound + } + + if !core.ComparePassword(login.Password, account.Password) { + return nil, domain.ErrBadPassword + } + + return account, nil +} diff --git a/internal/core/helpers.go b/internal/core/env.go similarity index 100% rename from internal/core/helpers.go rename to internal/core/env.go diff --git a/internal/core/helpers_test.go b/internal/core/env_test.go similarity index 100% rename from internal/core/helpers_test.go rename to internal/core/env_test.go diff --git a/internal/core/hash.go b/internal/core/hash.go new file mode 100644 index 0000000..f5cb3e6 --- /dev/null +++ b/internal/core/hash.go @@ -0,0 +1,12 @@ +package core + +import "golang.org/x/crypto/argon2" + +func HashPassword(password string) string { + return string(argon2.IDKey([]byte(password), nil, 1, 64*1024, 4, 32)) +} + +func ComparePassword(password string, hash string) bool { + hashedPassword := HashPassword(password) + return hashedPassword == hash +} diff --git a/internal/core/hash_test.go b/internal/core/hash_test.go new file mode 100644 index 0000000..ff194d1 --- /dev/null +++ b/internal/core/hash_test.go @@ -0,0 +1,21 @@ +package core + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestHashPassword(t *testing.T) { + password := "password" + hashedPassword := HashPassword(password) + assert.NotEmpty(t, hashedPassword) + t.Log(hashedPassword) + assert.Equal(t, hashedPassword, "LOLPASSWORD") +} + +func TestComparePassword(t *testing.T) { + password := "password" + hashedPassword := HashPassword(password) + assert.True(t, ComparePassword(password, hashedPassword)) +} -- 2.45.2 From 3e8171162bc2bf1f7ddd61962288b6ba0102d412 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 18 Mar 2025 21:19:49 +0000 Subject: [PATCH 03/15] Update dependencies, improve password handling, and enhance devcontainer configuration. Bump Go version to 1.24.1, add Air version to Dockerfile, and refactor password hashing and comparison functions to return errors. Update tests accordingly. --- .devcontainer/Dockerfile | 5 ++++- .devcontainer/devcontainer.json | 3 ++- go.mod | 9 +++++---- go.sum | 16 +++++++++------ internal/accounts/service/account.go | 7 ++++++- internal/core/hash.go | 29 ++++++++++++++++++++++------ internal/core/hash_test.go | 15 ++++++-------- 7 files changed, 56 insertions(+), 28 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index a9a9e45..5a2fbbd 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -5,14 +5,17 @@ WORKDIR /app ARG GO_VERSION ARG GOLANGCI_LINT_VERSION ARG MIGRATE_VERSION +ARG AIR_VERSION RUN apt update &&\ apt install git wget curl -y &&\ wget https://go.dev/dl/go$GO_VERSION.linux-amd64.tar.gz &&\ rm -rf /usr/local/go && tar -C /usr/local -xzf go$GO_VERSION.linux-amd64.tar.gz &&\ - curl -sSfL https://raw.githubusercontent.com/air-verse/air/master/install.sh | sh -s -- -b /usr/local/go/bin &&\ wget https://github.com/golangci/golangci-lint/releases/download/v$GOLANGCI_LINT_VERSION/golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.deb &&\ dpkg -i golangci-lint-$GOLANGCI_LINT_VERSION-linux-amd64.deb &&\ wget https://github.com/golang-migrate/migrate/releases/download/v$MIGRATE_VERSION/migrate.linux-amd64.deb &&\ dpkg -i migrate.linux-amd64.deb &&\ + wget https://github.com/air-verse/air/releases/download/v$AIR_VERSION/air_${AIR_VERSION}_linux_amd64 &&\ + chmod +x air_${AIR_VERSION}_linux_amd64 &&\ + mv air_${AIR_VERSION}_linux_amd64 /usr/local/go/bin/air &&\ echo "export PATH=$PATH:/usr/local/go/bin" > /root/.bashrc \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3432c1e..2f0d0d9 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,8 @@ "args": { "GO_VERSION": "1.24.1", "GOLANGCI_LINT_VERSION": "1.64.5", - "MIGRATE_VERSION": "4.18.2" + "MIGRATE_VERSION": "4.18.2", + "AIR_VERSION": "1.61.7" } }, "customizations": { diff --git a/go.mod b/go.mod index 15d4a83..cab4140 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,13 @@ module gitea.qpismont.fr/qpismont/trepa go 1.24.0 require ( + github.com/go-playground/validator/v10 v10.25.0 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 github.com/magiconair/properties v1.8.9 + github.com/matthewhartstonge/argon2 v1.2.0 github.com/stretchr/testify v1.10.0 ) @@ -17,16 +19,15 @@ require ( 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/crypto v0.36.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 + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c4f4b80..7ba8b2c 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ 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/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 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= @@ -32,6 +34,8 @@ 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= github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matthewhartstonge/argon2 v1.2.0 h1:oHo0H92JcmG4q5Ax6MuwDHa6iuJPz97RLwSfqcrjsSY= +github.com/matthewhartstonge/argon2 v1.2.0/go.mod h1:2zMl2u3Ooe9zkpeU61cmcAJ4vgMC3YfvRbKWnPg0wAU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -42,14 +46,14 @@ github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 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/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index 9925549..b6a13fa 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -19,7 +19,12 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.Account, *core.HTTPE return nil, domain.ErrAccountNotFound } - if !core.ComparePassword(login.Password, account.Password) { + ok, err := core.ComparePassword(login.Password, account.Password) + if err != nil { + return nil, domain.ErrBadPassword + } + + if !ok { return nil, domain.ErrBadPassword } diff --git a/internal/core/hash.go b/internal/core/hash.go index f5cb3e6..16a60ae 100644 --- a/internal/core/hash.go +++ b/internal/core/hash.go @@ -1,12 +1,29 @@ package core -import "golang.org/x/crypto/argon2" +import ( + "github.com/matthewhartstonge/argon2" +) -func HashPassword(password string) string { - return string(argon2.IDKey([]byte(password), nil, 1, 64*1024, 4, 32)) +func HashPassword(password string) (string, error) { + argon := instanceArgon2() + + hash, err := argon.HashEncoded([]byte(password)) + if err != nil { + return "", err + } + + return string(hash), nil } -func ComparePassword(password string, hash string) bool { - hashedPassword := HashPassword(password) - return hashedPassword == hash +func ComparePassword(password string, hash string) (bool, error) { + ok, err := argon2.VerifyEncoded([]byte(password), []byte(hash)) + if err != nil { + return false, err + } + + return ok, nil +} + +func instanceArgon2() argon2.Config { + return argon2.DefaultConfig() } diff --git a/internal/core/hash_test.go b/internal/core/hash_test.go index ff194d1..4e61e39 100644 --- a/internal/core/hash_test.go +++ b/internal/core/hash_test.go @@ -8,14 +8,11 @@ import ( func TestHashPassword(t *testing.T) { password := "password" - hashedPassword := HashPassword(password) - assert.NotEmpty(t, hashedPassword) - t.Log(hashedPassword) - assert.Equal(t, hashedPassword, "LOLPASSWORD") -} + hashedPassword, err := HashPassword(password) -func TestComparePassword(t *testing.T) { - password := "password" - hashedPassword := HashPassword(password) - assert.True(t, ComparePassword(password, hashedPassword)) + assert.NoError(t, err) + + hashedOk, err := ComparePassword(password, hashedPassword) + assert.NoError(t, err) + assert.True(t, hashedOk) } -- 2.45.2 From c2a78d820e42571d99783bd68d3511d8be3cc1d5 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 18 Mar 2025 21:20:03 +0000 Subject: [PATCH 04/15] Update Go version to 1.24.1 in go.mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index cab4140..022545b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module gitea.qpismont.fr/qpismont/trepa -go 1.24.0 +go 1.24.1 require ( github.com/go-playground/validator/v10 v10.25.0 -- 2.45.2 From a5e059a636e7071937b94a548f912a8aa4621666 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 24 Mar 2025 20:43:00 +0000 Subject: [PATCH 05/15] Add account management features: implement GetAccount endpoint, update Login method to return token, enhance repository and service layers, and add JWT middleware for authentication. Update .gitignore to include tmp directory. --- .air.toml | 51 ++++++++++++++++++ .gitignore | 4 +- bruno/accounts/folder.bru | 3 ++ bruno/accounts/login.bru | 18 +++++++ bruno/bruno.json | 9 ++++ bruno/environments/dev.bru | 3 ++ bruno/environments/test.bru | 3 ++ internal/accounts/api/controller.go | 56 ++++++++++++++++++++ internal/accounts/api/dto.go | 21 ++++++++ internal/accounts/api/routes.go | 1 + internal/accounts/domain/account.go | 5 ++ internal/accounts/domain/repository.go | 1 + internal/accounts/domain/service.go | 3 +- internal/accounts/repository/account.go | 15 ++++++ internal/accounts/repository/account_test.go | 20 +++++++ internal/accounts/repository/const.go | 1 + internal/accounts/service/account.go | 32 +++++++++-- internal/core/jwt.go | 2 +- internal/core/middleware.go | 18 +++++++ 19 files changed, 260 insertions(+), 6 deletions(-) create mode 100644 .air.toml create mode 100644 bruno/accounts/folder.bru create mode 100644 bruno/accounts/login.bru create mode 100644 bruno/bruno.json create mode 100644 bruno/environments/dev.bru create mode 100644 bruno/environments/test.bru diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..18751e5 --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./main" + cmd = "go build -o ./main ./cmd/api/main.go" + delay = 1000 + exclude_dir = ["tmp", "vendor", "cmd/scripts"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = true + keep_scroll = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index c759515..e99ee53 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,6 @@ go.work.sum .env # dist directory -dist/ \ No newline at end of file +dist/ + +tmp/ \ No newline at end of file diff --git a/bruno/accounts/folder.bru b/bruno/accounts/folder.bru new file mode 100644 index 0000000..c054647 --- /dev/null +++ b/bruno/accounts/folder.bru @@ -0,0 +1,3 @@ +meta { + name: accounts +} diff --git a/bruno/accounts/login.bru b/bruno/accounts/login.bru new file mode 100644 index 0000000..e123bf1 --- /dev/null +++ b/bruno/accounts/login.bru @@ -0,0 +1,18 @@ +meta { + name: login + type: http + seq: 1 +} + +post { + url: {{base_url}}/accounts/login + body: json + auth: inherit +} + +body:json { + { + "username": "lol", + "password": "lol" + } +} diff --git a/bruno/bruno.json b/bruno/bruno.json new file mode 100644 index 0000000..233a852 --- /dev/null +++ b/bruno/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "trepa", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/bruno/environments/dev.bru b/bruno/environments/dev.bru new file mode 100644 index 0000000..82175bb --- /dev/null +++ b/bruno/environments/dev.bru @@ -0,0 +1,3 @@ +vars { + base_url: 127.0.0.1:3000 +} diff --git a/bruno/environments/test.bru b/bruno/environments/test.bru new file mode 100644 index 0000000..e8e40ba --- /dev/null +++ b/bruno/environments/test.bru @@ -0,0 +1,3 @@ +vars { + base_url: {{process.env.BASE_URL}} +} diff --git a/internal/accounts/api/controller.go b/internal/accounts/api/controller.go index 1a246f1..b2682d0 100644 --- a/internal/accounts/api/controller.go +++ b/internal/accounts/api/controller.go @@ -16,9 +16,37 @@ type Controller struct { } func NewController(service domain.AccountService) Controller { + validate = validator.New() + return Controller{service: service} } +func (c Controller) GetAccount(w *core.Response, r *http.Request) { + claims := r.Context().Value("claims").(core.JWTClaims) + + account, httpErr := c.service.GetAccount(claims.AccountId) + if httpErr != nil { + w.WriteError(httpErr) + return + } + + response := GetAccountResponse{ + Id: account.Id, + Username: account.Username, + RoleId: account.RoleId, + CreatedAt: account.CreatedAt, + UpdatedAt: account.UpdatedAt, + } + + responseJson, err := json.Marshal(response) + if err != nil { + w.WriteError(core.NewInternalServerError(err)) + return + } + + w.Json(responseJson) +} + func (c Controller) Login(w *core.Response, r *http.Request) { var request LoginAccountRequest if err := json.NewDecoder(r.Body).Decode(&request); err != nil { @@ -31,4 +59,32 @@ func (c Controller) Login(w *core.Response, r *http.Request) { return } + result, httpErr := c.service.Login(domain.AccountLogin{ + Username: request.Username, + Password: request.Password, + }) + + if httpErr != nil { + w.WriteError(httpErr) + return + } + + response := LoginAccountResponse{ + Account: Account{ + Id: result.Account.Id, + Username: result.Account.Username, + RoleId: result.Account.RoleId, + CreatedAt: result.Account.CreatedAt, + UpdatedAt: result.Account.UpdatedAt, + }, + Token: result.Token, + } + + responseJson, err := json.Marshal(response) + if err != nil { + w.WriteError(core.NewInternalServerError(err)) + return + } + + w.Json(responseJson) } diff --git a/internal/accounts/api/dto.go b/internal/accounts/api/dto.go index aadd037..71d5ae4 100644 --- a/internal/accounts/api/dto.go +++ b/internal/accounts/api/dto.go @@ -4,3 +4,24 @@ type LoginAccountRequest struct { Username string `json:"username" validate:"required"` Password string `json:"password" validate:"required"` } + +type LoginAccountResponse struct { + Account Account `json:"account"` + Token string `json:"token"` +} + +type Account struct { + Id int `json:"id"` + Username string `json:"username"` + RoleId int `json:"role_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +type GetAccountResponse struct { + Id int `json:"id"` + Username string `json:"username"` + RoleId int `json:"role_id"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} diff --git a/internal/accounts/api/routes.go b/internal/accounts/api/routes.go index 3f088db..6f2e76f 100644 --- a/internal/accounts/api/routes.go +++ b/internal/accounts/api/routes.go @@ -13,4 +13,5 @@ func BindRoutes(srv *core.ServerMux, db *sqlx.DB) { controller := NewController(service) srv.HandleFunc("POST /accounts/login", controller.Login) + srv.HandleFunc("GET /accounts/me", core.JwtMiddleware(controller.GetAccount)) } diff --git a/internal/accounts/domain/account.go b/internal/accounts/domain/account.go index d4209c7..e1219dc 100644 --- a/internal/accounts/domain/account.go +++ b/internal/accounts/domain/account.go @@ -14,6 +14,11 @@ type AccountLogin struct { Password string } +type AccountWithToken struct { + Account *Account + Token string +} + type AccountCreate struct { Username string Password string diff --git a/internal/accounts/domain/repository.go b/internal/accounts/domain/repository.go index 72210eb..fc3e6cc 100644 --- a/internal/accounts/domain/repository.go +++ b/internal/accounts/domain/repository.go @@ -3,4 +3,5 @@ package domain type AccountRepository interface { Insert(account Account) (int, error) FetchOneByUsername(username string) (*Account, error) + FetchOneById(id int) (*Account, error) } diff --git a/internal/accounts/domain/service.go b/internal/accounts/domain/service.go index 7cc23a9..b2171d4 100644 --- a/internal/accounts/domain/service.go +++ b/internal/accounts/domain/service.go @@ -3,5 +3,6 @@ package domain import "gitea.qpismont.fr/qpismont/trepa/internal/core" type AccountService interface { - Login(login AccountLogin) (*Account, *core.HTTPError) + Login(login AccountLogin) (*AccountWithToken, *core.HTTPError) + GetAccount(id int) (*Account, *core.HTTPError) } diff --git a/internal/accounts/repository/account.go b/internal/accounts/repository/account.go index 17caafb..b959d72 100644 --- a/internal/accounts/repository/account.go +++ b/internal/accounts/repository/account.go @@ -33,6 +33,21 @@ func (r *Repository) Insert(account domain.Account) (int, error) { return id, nil } +func (r *Repository) FetchOneById(id int) (*domain.Account, error) { + var account domain.Account + + err := r.db.Get(&account, SqlFetchOneById, id) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + return &account, nil +} + func (r *Repository) FetchOneByUsername(username string) (*domain.Account, error) { var account domain.Account diff --git a/internal/accounts/repository/account_test.go b/internal/accounts/repository/account_test.go index e1aaf7b..85cd846 100644 --- a/internal/accounts/repository/account_test.go +++ b/internal/accounts/repository/account_test.go @@ -47,3 +47,23 @@ func TestRepository_FetchOneByUsername(t *testing.T) { assert.Equal(t, account.Password, "LOLPASSWORD") assert.Equal(t, account.RoleId, 1) } + +func TestRepository_FetchOneById(t *testing.T) { + db := test.SetupTestDB(t, "../../..") + defer db.Close() + + repo := NewRepository(db) + + account, err := repo.FetchOneById(1) + if err != nil { + t.Fatalf("Failed to fetch account: %v", err) + } + + if account == nil { + t.Fatalf("Account not found") + } + + assert.Equal(t, account.Username, "admin") + assert.Equal(t, account.Password, "LOLPASSWORD") + assert.Equal(t, account.RoleId, 1) +} diff --git a/internal/accounts/repository/const.go b/internal/accounts/repository/const.go index 03b80c2..a09c820 100644 --- a/internal/accounts/repository/const.go +++ b/internal/accounts/repository/const.go @@ -3,4 +3,5 @@ package repository const ( SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id" SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1" + SqlFetchOneById = "SELECT * FROM accounts WHERE id = $1" ) diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index b6a13fa..910a3f1 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -13,10 +13,23 @@ func NewService(repository domain.AccountRepository) domain.AccountService { return &Service{repository: repository} } -func (s *Service) Login(login domain.AccountLogin) (*domain.Account, *core.HTTPError) { +func (s *Service) GetAccount(id int) (*domain.Account, *core.HTTPError) { + account, err := s.repository.FetchOneById(id) + if err != nil { + return nil, core.NewInternalServerError(err) + } + + if account == nil { + return nil, domain.ErrAccountNotFound + } + + return account, nil +} + +func (s *Service) Login(login domain.AccountLogin) (*domain.AccountWithToken, *core.HTTPError) { account, err := s.repository.FetchOneByUsername(login.Username) if err != nil { - return nil, domain.ErrAccountNotFound + return nil, core.NewInternalServerError(err) } ok, err := core.ComparePassword(login.Password, account.Password) @@ -28,5 +41,18 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.Account, *core.HTTPE return nil, domain.ErrBadPassword } - return account, nil + claims := core.JWTClaims{ + AccountId: account.Id, + RoleId: account.RoleId, + } + + token, err := core.SignJWT(claims) + if err != nil { + return nil, core.NewInternalServerError(err) + } + + return &domain.AccountWithToken{ + Account: account, + Token: token, + }, nil } diff --git a/internal/core/jwt.go b/internal/core/jwt.go index 4515334..b1d7b5d 100644 --- a/internal/core/jwt.go +++ b/internal/core/jwt.go @@ -22,7 +22,7 @@ func SignJWT(claims JWTClaims) (string, error) { return token.SignedString([]byte(jwtSecret)) } -func VerifyJWT(token string) (JWTClaims, error) { +func VerifyJWT(token string) (JWTClaims, *HTTPError) { parsedClaims := JWTClaims{} claims, err := jwt.ParseWithClaims(token, &parsedClaims, func(token *jwt.Token) (any, error) { return []byte(jwtSecret), nil diff --git a/internal/core/middleware.go b/internal/core/middleware.go index c3a3db6..5092907 100644 --- a/internal/core/middleware.go +++ b/internal/core/middleware.go @@ -44,3 +44,21 @@ func ErrorMiddleware(next func(w *Response, r *http.Request)) func(w *Response, next(w, r) } } + +func JwtMiddleware(next func(w *Response, r *http.Request)) func(w *Response, r *http.Request) { + return func(w *Response, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + w.WriteError(ErrInvalidToken) + return + } + + _, err := VerifyJWT(token) + if err != nil { + w.WriteError(err) + return + } + + next(w, r) + } +} -- 2.45.2 From 9d544c7e8a4b6bbef3e68f02e96b36f853608eb6 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 24 Mar 2025 20:55:54 +0000 Subject: [PATCH 06/15] Update CI configuration to use Go 1.24.1-alpine for build, lint, and test steps. Fix test database setup path in account tests and improve error handling in JWT verification. --- build/ci/.build.yml | 2 +- build/ci/.lint.yml | 2 +- build/ci/.tests.yml | 2 +- internal/accounts/repository/account_test.go | 4 ++-- internal/accounts/service/account.go | 4 ++++ internal/core/jwt_test.go | 6 +++--- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build/ci/.build.yml b/build/ci/.build.yml index 860c71f..6edd38e 100644 --- a/build/ci/.build.yml +++ b/build/ci/.build.yml @@ -3,7 +3,7 @@ when: steps: - name: build - image: golang:1.24-alpine + image: golang:1.24.1-alpine commands: - apk update - apk add bash diff --git a/build/ci/.lint.yml b/build/ci/.lint.yml index 5ff810f..11c97f4 100644 --- a/build/ci/.lint.yml +++ b/build/ci/.lint.yml @@ -3,7 +3,7 @@ when: steps: - name: lint - image: golang:1.24-alpine + image: golang:1.24.1-alpine commands: - apk update - apk add bash curl jq diff --git a/build/ci/.tests.yml b/build/ci/.tests.yml index 61d8312..66596f3 100644 --- a/build/ci/.tests.yml +++ b/build/ci/.tests.yml @@ -3,7 +3,7 @@ when: steps: - name: tests - image: golang:1.24-alpine + image: golang:1.24.1-alpine environment: TEST_DB_HOST: db TEST_DB_PORT: 5432 diff --git a/internal/accounts/repository/account_test.go b/internal/accounts/repository/account_test.go index 85cd846..a191004 100644 --- a/internal/accounts/repository/account_test.go +++ b/internal/accounts/repository/account_test.go @@ -9,7 +9,7 @@ import ( ) func TestRepository_Insert(t *testing.T) { - db := test.SetupTestDB(t, "../..") + db := test.SetupTestDB(t, "../../..") defer db.Close() repo := NewRepository(db) @@ -29,7 +29,7 @@ func TestRepository_Insert(t *testing.T) { } func TestRepository_FetchOneByUsername(t *testing.T) { - db := test.SetupTestDB(t, "../..") + db := test.SetupTestDB(t, "../../..") defer db.Close() repo := NewRepository(db) diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index 910a3f1..580c7a6 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -32,6 +32,10 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.AccountWithToken, *c return nil, core.NewInternalServerError(err) } + if account == nil { + return nil, domain.ErrAccountNotFound + } + ok, err := core.ComparePassword(login.Password, account.Password) if err != nil { return nil, domain.ErrBadPassword diff --git a/internal/core/jwt_test.go b/internal/core/jwt_test.go index d9dce0f..bf48f8a 100644 --- a/internal/core/jwt_test.go +++ b/internal/core/jwt_test.go @@ -19,9 +19,9 @@ func TestJWT_GenerateToken(t *testing.T) { assert.NotEmpty(t, token) - claims, err := VerifyJWT(token) - if err != nil { - t.Fatalf("Failed to verify token: %v", err) + claims, jwtErr := VerifyJWT(token) + if jwtErr != nil { + t.Fatalf("Failed to verify token: %v", jwtErr) } assert.Equal(t, claims.AccountId, 1) -- 2.45.2 From 7c810ded85119ad198b0bfb0314fc45e256b5034 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 24 Mar 2025 21:18:01 +0000 Subject: [PATCH 07/15] Update dependencies and improve account tests: add objx library, replace assert library with testify, enhance password assertions, and modify SQL fixtures for password hashing. --- go.mod | 1 + go.sum | 2 + internal/accounts/repository/account_test.go | 14 +-- internal/accounts/service/account.go | 2 +- internal/accounts/service/account_test.go | 92 ++++++++++++++++++++ test/fixtures/00-accounts.sql | 4 +- 6 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 internal/accounts/service/account_test.go diff --git a/go.mod b/go.mod index 022545b..997146f 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( 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 + github.com/stretchr/objx v0.5.2 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 7ba8b2c..d0bde66 100644 --- a/go.sum +++ b/go.sum @@ -44,6 +44,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= diff --git a/internal/accounts/repository/account_test.go b/internal/accounts/repository/account_test.go index a191004..33db105 100644 --- a/internal/accounts/repository/account_test.go +++ b/internal/accounts/repository/account_test.go @@ -5,7 +5,7 @@ import ( "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" "gitea.qpismont.fr/qpismont/trepa/test" - "github.com/magiconair/properties/assert" + "github.com/stretchr/testify/assert" ) func TestRepository_Insert(t *testing.T) { @@ -43,9 +43,9 @@ func TestRepository_FetchOneByUsername(t *testing.T) { t.Fatalf("Account not found") } - assert.Equal(t, account.Username, "admin") - assert.Equal(t, account.Password, "LOLPASSWORD") - assert.Equal(t, account.RoleId, 1) + assert.Equal(t, "admin", account.Username) + assert.NotEmpty(t, account.Password) + assert.Equal(t, 1, account.RoleId) } func TestRepository_FetchOneById(t *testing.T) { @@ -63,7 +63,7 @@ func TestRepository_FetchOneById(t *testing.T) { t.Fatalf("Account not found") } - assert.Equal(t, account.Username, "admin") - assert.Equal(t, account.Password, "LOLPASSWORD") - assert.Equal(t, account.RoleId, 1) + assert.Equal(t, "admin", account.Username) + assert.NotEmpty(t, account.Password) + assert.Equal(t, 1, account.RoleId) } diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index 580c7a6..37df9ee 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -38,7 +38,7 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.AccountWithToken, *c ok, err := core.ComparePassword(login.Password, account.Password) if err != nil { - return nil, domain.ErrBadPassword + return nil, core.NewInternalServerError(err) } if !ok { diff --git a/internal/accounts/service/account_test.go b/internal/accounts/service/account_test.go new file mode 100644 index 0000000..e5a8f77 --- /dev/null +++ b/internal/accounts/service/account_test.go @@ -0,0 +1,92 @@ +package service + +import ( + "testing" + + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/domain" + "gitea.qpismont.fr/qpismont/trepa/internal/accounts/repository" + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "gitea.qpismont.fr/qpismont/trepa/test" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type MockRepository struct { + mock.Mock +} + +func (m *MockRepository) FetchOneById(id int) (*domain.Account, error) { + args := m.Called(id) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.Account), args.Error(1) +} + +func (m *MockRepository) FetchOneByUsername(username string) (*domain.Account, error) { + args := m.Called(username) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*domain.Account), args.Error(1) +} + +func (m *MockRepository) Insert(account domain.Account) (int, error) { + args := m.Called(account) + return args.Int(0), args.Error(1) +} + +func TestService_GetAccount(t *testing.T) { + db := test.SetupTestDB(t, "../../..") + defer db.Close() + + repo := repository.NewRepository(db) + + service := NewService(repo) + + account, err := service.GetAccount(1) + if err != nil { + t.Fatalf("Failed to get account: %v", err) + } + + if account == nil { + t.Fatalf("Account not found") + } + + assert.Equal(t, "admin", account.Username) + assert.Equal(t, 1, account.RoleId) + assert.NotEmpty(t, account.Password) +} + +func TestService_Login(t *testing.T) { + mockRepo := new(MockRepository) + + testPassword := "testpassword" + hashedPassword, _ := core.HashPassword(testPassword) + + mockRepo.On("FetchOneByUsername", "admin").Return(&domain.Account{ + Id: 1, + Username: "admin", + Password: hashedPassword, + RoleId: 1, + }, nil) + + service := NewService(mockRepo) + + result, err := service.Login(domain.AccountLogin{ + Username: "admin", + Password: testPassword, + }) + + if err != nil { + t.Fatalf("Failed to login: %v", err) + } + + assert.NotNil(t, result) + assert.NotNil(t, result.Token) + assert.NotNil(t, result.Account) + assert.Equal(t, "admin", result.Account.Username) + assert.Equal(t, 1, result.Account.RoleId) + + mockRepo.AssertExpectations(t) +} diff --git a/test/fixtures/00-accounts.sql b/test/fixtures/00-accounts.sql index bc38a90..a0ce17f 100644 --- a/test/fixtures/00-accounts.sql +++ b/test/fixtures/00-accounts.sql @@ -1,2 +1,2 @@ -INSERT INTO accounts (username, password, role_id) VALUES ('admin', 'LOLPASSWORD', 1); -INSERT INTO accounts (username, password, role_id) VALUES ('user', 'LOLPASSWORD', 2); +INSERT INTO accounts (username, password, role_id) VALUES ('admin', '##TEST_PASSWORD_HASH##', 1); +INSERT INTO accounts (username, password, role_id) VALUES ('user', '##TEST_PASSWORD_HASH##', 2); -- 2.45.2 From 923083cc5f90364711219c7f4a973a025540a7b9 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 24 Mar 2025 21:19:25 +0000 Subject: [PATCH 08/15] Remove magiconair/properties dependency from go.mod and go.sum --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 997146f..55ccfb4 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,6 @@ require ( github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 - github.com/magiconair/properties v1.8.9 github.com/matthewhartstonge/argon2 v1.2.0 github.com/stretchr/testify v1.10.0 ) diff --git a/go.sum b/go.sum index d0bde66..090b867 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,6 @@ 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= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/matthewhartstonge/argon2 v1.2.0 h1:oHo0H92JcmG4q5Ax6MuwDHa6iuJPz97RLwSfqcrjsSY= github.com/matthewhartstonge/argon2 v1.2.0/go.mod h1:2zMl2u3Ooe9zkpeU61cmcAJ4vgMC3YfvRbKWnPg0wAU= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -- 2.45.2 From dabfc43678c3f56d443e923d6d594c732316b1f4 Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 27 Mar 2025 22:08:45 +0000 Subject: [PATCH 09/15] Add run arguments to devcontainer configuration: set network to 'dev-network' and name to 'trepa-dev'. --- .devcontainer/devcontainer.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 2f0d0d9..727b5f8 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,6 +17,10 @@ ] } }, + "runArgs": [ + "--network=dev-network", + "--name=trepa-dev" + ], "forwardPorts": [ 3000 ] -- 2.45.2 From 3ea22fd39d08c8bfc8552c0f2c954cd73d90bf09 Mon Sep 17 00:00:00 2001 From: qpismont Date: Mon, 31 Mar 2025 19:46:54 +0000 Subject: [PATCH 10/15] Refactor devcontainer configuration: update network settings and container name for improved development environment consistency. --- scripts/migrate.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 scripts/migrate.sh diff --git a/scripts/migrate.sh b/scripts/migrate.sh new file mode 100644 index 0000000..4a90a08 --- /dev/null +++ b/scripts/migrate.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +#Check if the migrate command is available +if ! command -v migrate &> /dev/null; then + echo "migrate could not be found" + exit 1 +fi + + +#Execute migrate up +migrate -path ./migrations -database "$DATABASE_URL" -verbose up -- 2.45.2 From 3f7fe223924f0e70021e37985fcec8097ba2b592 Mon Sep 17 00:00:00 2001 From: qpismont Date: Tue, 8 Apr 2025 18:50:30 +0200 Subject: [PATCH 11/15] upgrade go 1.24.2 --- .devcontainer/devcontainer.json | 4 ++-- build/ci/.build.yml | 2 +- build/ci/.lint.yml | 4 ++-- build/ci/.tests.yml | 2 +- go.mod | 12 ++++++------ go.sum | 10 ++++++++++ 6 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 727b5f8..3729d9a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,8 +4,8 @@ "build": { "dockerfile": "Dockerfile", "args": { - "GO_VERSION": "1.24.1", - "GOLANGCI_LINT_VERSION": "1.64.5", + "GO_VERSION": "1.24.2", + "GOLANGCI_LINT_VERSION": "2.0.2", "MIGRATE_VERSION": "4.18.2", "AIR_VERSION": "1.61.7" } diff --git a/build/ci/.build.yml b/build/ci/.build.yml index 6edd38e..e4809fe 100644 --- a/build/ci/.build.yml +++ b/build/ci/.build.yml @@ -3,7 +3,7 @@ when: steps: - name: build - image: golang:1.24.1-alpine + image: golang:1.24.2-alpine commands: - apk update - apk add bash diff --git a/build/ci/.lint.yml b/build/ci/.lint.yml index 11c97f4..1c137af 100644 --- a/build/ci/.lint.yml +++ b/build/ci/.lint.yml @@ -3,9 +3,9 @@ when: steps: - name: lint - image: golang:1.24.1-alpine + image: golang:1.24.2-alpine commands: - apk update - apk add bash curl jq - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.64.5 + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.0.2 - $(go env GOPATH)/bin/golangci-lint run --fast diff --git a/build/ci/.tests.yml b/build/ci/.tests.yml index 66596f3..eea6ba2 100644 --- a/build/ci/.tests.yml +++ b/build/ci/.tests.yml @@ -3,7 +3,7 @@ when: steps: - name: tests - image: golang:1.24.1-alpine + image: golang:1.24.2-alpine environment: TEST_DB_HOST: db TEST_DB_PORT: 5432 diff --git a/go.mod b/go.mod index 55ccfb4..4411c7c 100644 --- a/go.mod +++ b/go.mod @@ -1,10 +1,10 @@ module gitea.qpismont.fr/qpismont/trepa -go 1.24.1 +go 1.24.2 require ( - github.com/go-playground/validator/v10 v10.25.0 - github.com/golang-jwt/jwt/v5 v5.2.1 + github.com/go-playground/validator/v10 v10.26.0 + github.com/golang-jwt/jwt/v5 v5.2.2 github.com/jackc/pgx v3.6.2+incompatible github.com/jmoiron/sqlx v1.4.0 github.com/joho/godotenv v1.5.1 @@ -26,8 +26,8 @@ require ( github.com/shopspring/decimal v1.4.0 // indirect github.com/stretchr/objx v0.5.2 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/net v0.38.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 090b867..e16b052 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,16 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn 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-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 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= github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o= @@ -50,10 +54,16 @@ golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -- 2.45.2 From 1f31d9c78e794e1bf7c7f1b032e2b785ebf282c4 Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 17 Apr 2025 20:24:05 +0000 Subject: [PATCH 12/15] Add account registration feature: introduce AccountRegister type, implement Register method in AccountService, and add corresponding tests. Also, create variable management API with CRUD operations and database migration for variables table. --- internal/accounts/domain/account.go | 6 +++ internal/accounts/domain/service.go | 1 + internal/accounts/repository/account.go | 15 +++++++ internal/accounts/repository/const.go | 1 + internal/accounts/service/account.go | 29 ++++++++++++++ internal/accounts/service/account_test.go | 30 ++++++++++++++ internal/variables/api/controller.go | 40 +++++++++++++++++++ internal/variables/api/dto.go | 6 +++ internal/variables/api/routes.go | 16 ++++++++ internal/variables/domain/errors.go | 9 +++++ internal/variables/domain/repository.go | 5 +++ internal/variables/domain/service.go | 7 ++++ internal/variables/domain/variable.go | 11 +++++ internal/variables/repository/const.go | 5 +++ internal/variables/repository/variable.go | 24 +++++++++++ .../variables/repository/variable_test.go | 20 ++++++++++ internal/variables/service/variable.go | 27 +++++++++++++ internal/variables/service/variable_test.go | 24 +++++++++++ migrations/20250417194859_variables.up.sql | 14 +++++++ scripts/create_migration.sh | 5 +++ scripts/migrate.sh | 2 - test/fixtures/01-variables.sql | 1 + 22 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 internal/variables/api/controller.go create mode 100644 internal/variables/api/dto.go create mode 100644 internal/variables/api/routes.go create mode 100644 internal/variables/domain/errors.go create mode 100644 internal/variables/domain/repository.go create mode 100644 internal/variables/domain/service.go create mode 100644 internal/variables/domain/variable.go create mode 100644 internal/variables/repository/const.go create mode 100644 internal/variables/repository/variable.go create mode 100644 internal/variables/repository/variable_test.go create mode 100644 internal/variables/service/variable.go create mode 100644 internal/variables/service/variable_test.go create mode 100644 migrations/20250417194859_variables.up.sql create mode 100644 scripts/create_migration.sh create mode 100644 test/fixtures/01-variables.sql diff --git a/internal/accounts/domain/account.go b/internal/accounts/domain/account.go index e1219dc..38ab74e 100644 --- a/internal/accounts/domain/account.go +++ b/internal/accounts/domain/account.go @@ -24,3 +24,9 @@ type AccountCreate struct { Password string RoleId int } + +type AccountRegister struct { + Username string + Password string + RoleId int +} diff --git a/internal/accounts/domain/service.go b/internal/accounts/domain/service.go index b2171d4..f73b455 100644 --- a/internal/accounts/domain/service.go +++ b/internal/accounts/domain/service.go @@ -4,5 +4,6 @@ import "gitea.qpismont.fr/qpismont/trepa/internal/core" type AccountService interface { Login(login AccountLogin) (*AccountWithToken, *core.HTTPError) + Register(register AccountRegister) (int, *core.HTTPError) GetAccount(id int) (*Account, *core.HTTPError) } diff --git a/internal/accounts/repository/account.go b/internal/accounts/repository/account.go index b959d72..9f97091 100644 --- a/internal/accounts/repository/account.go +++ b/internal/accounts/repository/account.go @@ -62,3 +62,18 @@ func (r *Repository) FetchOneByUsername(username string) (*domain.Account, error return &account, nil } + +func (r *Repository) FetchOneByRoleId(roleId int) (*domain.Account, error) { + var account domain.Account + + err := r.db.Get(&account, SqlFetchOneByRoleId, roleId) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } else { + return nil, err + } + } + + return &account, nil +} diff --git a/internal/accounts/repository/const.go b/internal/accounts/repository/const.go index a09c820..929707d 100644 --- a/internal/accounts/repository/const.go +++ b/internal/accounts/repository/const.go @@ -4,4 +4,5 @@ const ( SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id" SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1" SqlFetchOneById = "SELECT * FROM accounts WHERE id = $1" + SqlFetchOneByRoleId = "SELECT * FROM accounts WHERE role_id = $1" ) diff --git a/internal/accounts/service/account.go b/internal/accounts/service/account.go index 37df9ee..b708432 100644 --- a/internal/accounts/service/account.go +++ b/internal/accounts/service/account.go @@ -60,3 +60,32 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.AccountWithToken, *c Token: token, }, nil } + +func (s *Service) Register(register domain.AccountRegister) (int, *core.HTTPError) { + accountExist, err := s.repository.FetchOneByUsername(register.Username) + if err != nil { + return 0, core.NewInternalServerError(err) + } + + if accountExist != nil { + return 0, domain.ErrAccountAlreadyExists + } + + hashedPassword, err := core.HashPassword(register.Password) + if err != nil { + return 0, core.NewInternalServerError(err) + } + + account := domain.Account{ + Username: register.Username, + Password: hashedPassword, + RoleId: register.RoleId, + } + + id, err := s.repository.Insert(account) + if err != nil { + return 0, core.NewInternalServerError(err) + } + + return id, nil +} diff --git a/internal/accounts/service/account_test.go b/internal/accounts/service/account_test.go index e5a8f77..0903059 100644 --- a/internal/accounts/service/account_test.go +++ b/internal/accounts/service/account_test.go @@ -20,6 +20,7 @@ func (m *MockRepository) FetchOneById(id int) (*domain.Account, error) { if args.Get(0) == nil { return nil, args.Error(1) } + return args.Get(0).(*domain.Account), args.Error(1) } @@ -28,6 +29,7 @@ func (m *MockRepository) FetchOneByUsername(username string) (*domain.Account, e if args.Get(0) == nil { return nil, args.Error(1) } + return args.Get(0).(*domain.Account), args.Error(1) } @@ -36,6 +38,34 @@ func (m *MockRepository) Insert(account domain.Account) (int, error) { return args.Int(0), args.Error(1) } +func (m *MockRepository) FetchOneByRoleId(roleId int) (*domain.Account, error) { + args := m.Called(roleId) + if args.Get(0) == nil { + return nil, args.Error(1) + } + + return args.Get(0).(*domain.Account), args.Error(1) +} + +func TestService_Register(t *testing.T) { + mockRepo := new(MockRepository) + + mockRepo.On("FetchOneByUsername", "admin").Return(nil, nil) + mockRepo.On("Insert", mock.Anything).Return(1, nil) + + service := NewService(mockRepo) + + id, _ := service.Register(domain.AccountRegister{ + Username: "admin", + Password: "admin", + RoleId: 1, + }) + + assert.Equal(t, 1, id) + + mockRepo.AssertExpectations(t) +} + func TestService_GetAccount(t *testing.T) { db := test.SetupTestDB(t, "../../..") defer db.Close() diff --git a/internal/variables/api/controller.go b/internal/variables/api/controller.go new file mode 100644 index 0000000..bd36d92 --- /dev/null +++ b/internal/variables/api/controller.go @@ -0,0 +1,40 @@ +package api + +import ( + "encoding/json" + "net/http" + + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "gitea.qpismont.fr/qpismont/trepa/internal/variables/domain" +) + +type Controller struct { + service domain.VariableService +} + +func NewController(service domain.VariableService) Controller { + return Controller{service: service} +} + +func (c Controller) GetVariableByName(w *core.Response, r *http.Request) { + name := r.PathValue("name") + + variable, httpErr := c.service.GetVariable(name) + if httpErr != nil { + w.WriteError(httpErr) + return + } + + response := GetVariableResponse{ + Name: variable.Name, + Value: variable.Value, + } + + responseJson, err := json.Marshal(response) + if err != nil { + w.WriteError(core.NewInternalServerError(err)) + return + } + + w.Json(responseJson) +} diff --git a/internal/variables/api/dto.go b/internal/variables/api/dto.go new file mode 100644 index 0000000..241dd7b --- /dev/null +++ b/internal/variables/api/dto.go @@ -0,0 +1,6 @@ +package api + +type GetVariableResponse struct { + Name string `json:"name"` + Value string `json:"value"` +} diff --git a/internal/variables/api/routes.go b/internal/variables/api/routes.go new file mode 100644 index 0000000..b295640 --- /dev/null +++ b/internal/variables/api/routes.go @@ -0,0 +1,16 @@ +package api + +import ( + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "gitea.qpismont.fr/qpismont/trepa/internal/variables/repository" + "gitea.qpismont.fr/qpismont/trepa/internal/variables/service" + "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("GET /variables/{name}", controller.GetVariableByName) +} diff --git a/internal/variables/domain/errors.go b/internal/variables/domain/errors.go new file mode 100644 index 0000000..8833700 --- /dev/null +++ b/internal/variables/domain/errors.go @@ -0,0 +1,9 @@ +package domain + +import ( + "net/http" + + "gitea.qpismont.fr/qpismont/trepa/internal/core" +) + +var ErrVariableNotFound = core.NewHTTPError(http.StatusNotFound, "variable not found", nil) diff --git a/internal/variables/domain/repository.go b/internal/variables/domain/repository.go new file mode 100644 index 0000000..58964ae --- /dev/null +++ b/internal/variables/domain/repository.go @@ -0,0 +1,5 @@ +package domain + +type VariableRepository interface { + FetchOneByName(name string) (*Variable, error) +} diff --git a/internal/variables/domain/service.go b/internal/variables/domain/service.go new file mode 100644 index 0000000..c2fd663 --- /dev/null +++ b/internal/variables/domain/service.go @@ -0,0 +1,7 @@ +package domain + +import "gitea.qpismont.fr/qpismont/trepa/internal/core" + +type VariableService interface { + GetVariable(name string) (*Variable, *core.HTTPError) +} diff --git a/internal/variables/domain/variable.go b/internal/variables/domain/variable.go new file mode 100644 index 0000000..661a9f6 --- /dev/null +++ b/internal/variables/domain/variable.go @@ -0,0 +1,11 @@ +package domain + +import "time" + +type Variable struct { + Id int `db:"id" json:"id"` + Name string `db:"name" json:"name"` + Value string `db:"value" json:"value"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` +} diff --git a/internal/variables/repository/const.go b/internal/variables/repository/const.go new file mode 100644 index 0000000..9fcbd52 --- /dev/null +++ b/internal/variables/repository/const.go @@ -0,0 +1,5 @@ +package repository + +const ( + SqlFetchOneByName = "SELECT * FROM variables WHERE name = $1" +) diff --git a/internal/variables/repository/variable.go b/internal/variables/repository/variable.go new file mode 100644 index 0000000..68869a8 --- /dev/null +++ b/internal/variables/repository/variable.go @@ -0,0 +1,24 @@ +package repository + +import ( + "gitea.qpismont.fr/qpismont/trepa/internal/variables/domain" + "github.com/jmoiron/sqlx" +) + +type VariableRepository struct { + db *sqlx.DB +} + +func NewRepository(db *sqlx.DB) *VariableRepository { + return &VariableRepository{db: db} +} + +func (r *VariableRepository) FetchOneByName(name string) (*domain.Variable, error) { + var variable domain.Variable + err := r.db.Get(&variable, "SELECT * FROM variables WHERE name = $1", name) + if err != nil { + return nil, err + } + + return &variable, nil +} diff --git a/internal/variables/repository/variable_test.go b/internal/variables/repository/variable_test.go new file mode 100644 index 0000000..d6c4a6c --- /dev/null +++ b/internal/variables/repository/variable_test.go @@ -0,0 +1,20 @@ +package repository + +import ( + "testing" + + "gitea.qpismont.fr/qpismont/trepa/test" + "github.com/stretchr/testify/assert" +) + +func TestVariableRepository_FetchOneByName(t *testing.T) { + db := test.SetupTestDB(t, "../../..") + defer db.Close() + + repository := NewRepository(db) + + variable, err := repository.FetchOneByName("first_account_created") + assert.NoError(t, err) + assert.Equal(t, "first_account_created", variable.Name) + assert.Equal(t, "false", variable.Value) +} diff --git a/internal/variables/service/variable.go b/internal/variables/service/variable.go new file mode 100644 index 0000000..ae4edb0 --- /dev/null +++ b/internal/variables/service/variable.go @@ -0,0 +1,27 @@ +package service + +import ( + "gitea.qpismont.fr/qpismont/trepa/internal/core" + "gitea.qpismont.fr/qpismont/trepa/internal/variables/domain" +) + +type VariableService struct { + repository domain.VariableRepository +} + +func NewService(repository domain.VariableRepository) *VariableService { + return &VariableService{repository: repository} +} + +func (s *VariableService) GetVariable(name string) (*domain.Variable, *core.HTTPError) { + variable, err := s.repository.FetchOneByName(name) + if err != nil { + return nil, core.NewInternalServerError(err) + } + + if variable == nil { + return nil, domain.ErrVariableNotFound + } + + return variable, nil +} diff --git a/internal/variables/service/variable_test.go b/internal/variables/service/variable_test.go new file mode 100644 index 0000000..1ae5bbd --- /dev/null +++ b/internal/variables/service/variable_test.go @@ -0,0 +1,24 @@ +package service + +import ( + "testing" + + "gitea.qpismont.fr/qpismont/trepa/internal/variables/repository" + "gitea.qpismont.fr/qpismont/trepa/test" + "github.com/stretchr/testify/assert" +) + +func TestVariableService_GetVariable(t *testing.T) { + db := test.SetupTestDB(t, "../../..") + defer db.Close() + + repository := repository.NewRepository(db) + service := NewService(repository) + + variable, _ := service.GetVariable("first_account_created") + + assert.NotNil(t, variable) + assert.Equal(t, 1, variable.Id) + assert.Equal(t, "first_account_created", variable.Name) + assert.Equal(t, "false", variable.Value) +} diff --git a/migrations/20250417194859_variables.up.sql b/migrations/20250417194859_variables.up.sql new file mode 100644 index 0000000..43e362d --- /dev/null +++ b/migrations/20250417194859_variables.up.sql @@ -0,0 +1,14 @@ +CREATE TABLE public.variables +( + id integer NOT NULL GENERATED ALWAYS AS IDENTITY, + name text, + value text, + created_at timestamp without time zone NOT NULL DEFAULT now(), + updated_at timestamp without time zone NOT NULL DEFAULT now(), + PRIMARY KEY (id) +); + +ALTER TABLE IF EXISTS public.variables + OWNER to dev; + +INSERT INTO public.variables (name, value) VALUES ('first_account_created', 'false'); \ No newline at end of file diff --git a/scripts/create_migration.sh b/scripts/create_migration.sh new file mode 100644 index 0000000..27fb60d --- /dev/null +++ b/scripts/create_migration.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +timestamp=$(date +%Y%m%d%H%M%S) +migration_file="migrations/$(date +%Y%m%d%H%M%S)_$1.up.sql" +touch $migration_file diff --git a/scripts/migrate.sh b/scripts/migrate.sh index 4a90a08..c4c611c 100644 --- a/scripts/migrate.sh +++ b/scripts/migrate.sh @@ -1,11 +1,9 @@ #!/bin/bash -#Check if the migrate command is available if ! command -v migrate &> /dev/null; then echo "migrate could not be found" exit 1 fi -#Execute migrate up migrate -path ./migrations -database "$DATABASE_URL" -verbose up diff --git a/test/fixtures/01-variables.sql b/test/fixtures/01-variables.sql new file mode 100644 index 0000000..ec143f8 --- /dev/null +++ b/test/fixtures/01-variables.sql @@ -0,0 +1 @@ +INSERT INTO variables (name, value) VALUES ('first_account_created', 'false'); \ No newline at end of file -- 2.45.2 From edc9aeb4710a97fc4590f6bee525d5dce8e1f838 Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 17 Apr 2025 20:24:20 +0000 Subject: [PATCH 13/15] Remove FetchOneByRoleId method and its corresponding SQL constant from the account repository to streamline account management functionality. --- internal/accounts/repository/account.go | 15 --------------- internal/accounts/repository/const.go | 1 - 2 files changed, 16 deletions(-) diff --git a/internal/accounts/repository/account.go b/internal/accounts/repository/account.go index 9f97091..b959d72 100644 --- a/internal/accounts/repository/account.go +++ b/internal/accounts/repository/account.go @@ -62,18 +62,3 @@ func (r *Repository) FetchOneByUsername(username string) (*domain.Account, error return &account, nil } - -func (r *Repository) FetchOneByRoleId(roleId int) (*domain.Account, error) { - var account domain.Account - - err := r.db.Get(&account, SqlFetchOneByRoleId, roleId) - if err != nil { - if err == sql.ErrNoRows { - return nil, nil - } else { - return nil, err - } - } - - return &account, nil -} diff --git a/internal/accounts/repository/const.go b/internal/accounts/repository/const.go index 929707d..a09c820 100644 --- a/internal/accounts/repository/const.go +++ b/internal/accounts/repository/const.go @@ -4,5 +4,4 @@ const ( SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id" SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1" SqlFetchOneById = "SELECT * FROM accounts WHERE id = $1" - SqlFetchOneByRoleId = "SELECT * FROM accounts WHERE role_id = $1" ) -- 2.45.2 From f8f0f0eca27064581ebdb0658b052e8c603446b2 Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 17 Apr 2025 20:34:14 +0000 Subject: [PATCH 14/15] Update VSCode settings and CI lint configuration: modify Go lint flags for improved performance and update golangci-lint version to 2.1.2. --- .vscode/settings.json | 11 ++++++++++- build/ci/.lint.yml | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 017bdb7..d1dcd6f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,15 @@ "editor.formatOnSave": true, "go.lintTool": "golangci-lint", "go.lintFlags": [ - "--fast" + "--path-mode=abs", + "--fast-only" + ], + "go.formatTool": "custom", + "go.alternateTools": { + "customFormatter": "golangci-lint-v2" + }, + "go.formatFlags": [ + "fmt", + "--stdin" ] } \ No newline at end of file diff --git a/build/ci/.lint.yml b/build/ci/.lint.yml index 1c137af..ca446f1 100644 --- a/build/ci/.lint.yml +++ b/build/ci/.lint.yml @@ -7,5 +7,5 @@ steps: commands: - apk update - apk add bash curl jq - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.0.2 - - $(go env GOPATH)/bin/golangci-lint run --fast + - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.2 + - $(go env GOPATH)/bin/golangci-lint run -- 2.45.2 From 2445b82395243cf8b3105626777d42f8039f0d5d Mon Sep 17 00:00:00 2001 From: qpismont Date: Thu, 17 Apr 2025 21:21:04 +0000 Subject: [PATCH 15/15] Update golangci-lint version to 2.1.2 and adjust CI lint command for improved performance. --- .devcontainer/devcontainer.json | 2 +- .vscode/settings.json | 2 +- build/ci/.lint.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 3729d9a..150ba77 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "dockerfile": "Dockerfile", "args": { "GO_VERSION": "1.24.2", - "GOLANGCI_LINT_VERSION": "2.0.2", + "GOLANGCI_LINT_VERSION": "2.1.2", "MIGRATE_VERSION": "4.18.2", "AIR_VERSION": "1.61.7" } diff --git a/.vscode/settings.json b/.vscode/settings.json index d1dcd6f..a8f525a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,7 +7,7 @@ ], "go.formatTool": "custom", "go.alternateTools": { - "customFormatter": "golangci-lint-v2" + "customFormatter": "golangci-lint" }, "go.formatFlags": [ "fmt", diff --git a/build/ci/.lint.yml b/build/ci/.lint.yml index ca446f1..eb88550 100644 --- a/build/ci/.lint.yml +++ b/build/ci/.lint.yml @@ -8,4 +8,4 @@ steps: - apk update - apk add bash curl jq - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.2 - - $(go env GOPATH)/bin/golangci-lint run + - $(go env GOPATH)/bin/golangci-lint run --fast-only --path-mode=abs -- 2.45.2