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