starting accounts #2
22 changed files with 296 additions and 2 deletions
|
@ -24,3 +24,9 @@ type AccountCreate struct {
|
||||||
Password string
|
Password string
|
||||||
RoleId int
|
RoleId int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AccountRegister struct {
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
RoleId int
|
||||||
|
}
|
||||||
|
|
|
@ -4,5 +4,6 @@ import "gitea.qpismont.fr/qpismont/trepa/internal/core"
|
||||||
|
|
||||||
type AccountService interface {
|
type AccountService interface {
|
||||||
Login(login AccountLogin) (*AccountWithToken, *core.HTTPError)
|
Login(login AccountLogin) (*AccountWithToken, *core.HTTPError)
|
||||||
|
Register(register AccountRegister) (int, *core.HTTPError)
|
||||||
GetAccount(id int) (*Account, *core.HTTPError)
|
GetAccount(id int) (*Account, *core.HTTPError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,3 +62,18 @@ func (r *Repository) FetchOneByUsername(username string) (*domain.Account, error
|
||||||
|
|
||||||
return &account, nil
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -4,4 +4,5 @@ const (
|
||||||
SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id"
|
SqlInsert = "INSERT INTO accounts (username, password, role_id) VALUES ($1, $2, $3) RETURNING id"
|
||||||
SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1"
|
SqlFetchOneByUsername = "SELECT * FROM accounts WHERE username = $1"
|
||||||
SqlFetchOneById = "SELECT * FROM accounts WHERE id = $1"
|
SqlFetchOneById = "SELECT * FROM accounts WHERE id = $1"
|
||||||
|
SqlFetchOneByRoleId = "SELECT * FROM accounts WHERE role_id = $1"
|
||||||
)
|
)
|
||||||
|
|
|
@ -60,3 +60,32 @@ func (s *Service) Login(login domain.AccountLogin) (*domain.AccountWithToken, *c
|
||||||
Token: token,
|
Token: token,
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ func (m *MockRepository) FetchOneById(id int) (*domain.Account, error) {
|
||||||
if args.Get(0) == nil {
|
if args.Get(0) == nil {
|
||||||
return nil, args.Error(1)
|
return nil, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return args.Get(0).(*domain.Account), 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 {
|
if args.Get(0) == nil {
|
||||||
return nil, args.Error(1)
|
return nil, args.Error(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
return args.Get(0).(*domain.Account), 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)
|
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) {
|
func TestService_GetAccount(t *testing.T) {
|
||||||
db := test.SetupTestDB(t, "../../..")
|
db := test.SetupTestDB(t, "../../..")
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
40
internal/variables/api/controller.go
Normal file
40
internal/variables/api/controller.go
Normal file
|
@ -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)
|
||||||
|
}
|
6
internal/variables/api/dto.go
Normal file
6
internal/variables/api/dto.go
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
type GetVariableResponse struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
16
internal/variables/api/routes.go
Normal file
16
internal/variables/api/routes.go
Normal file
|
@ -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)
|
||||||
|
}
|
9
internal/variables/domain/errors.go
Normal file
9
internal/variables/domain/errors.go
Normal file
|
@ -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)
|
5
internal/variables/domain/repository.go
Normal file
5
internal/variables/domain/repository.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
type VariableRepository interface {
|
||||||
|
FetchOneByName(name string) (*Variable, error)
|
||||||
|
}
|
7
internal/variables/domain/service.go
Normal file
7
internal/variables/domain/service.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
package domain
|
||||||
|
|
||||||
|
import "gitea.qpismont.fr/qpismont/trepa/internal/core"
|
||||||
|
|
||||||
|
type VariableService interface {
|
||||||
|
GetVariable(name string) (*Variable, *core.HTTPError)
|
||||||
|
}
|
11
internal/variables/domain/variable.go
Normal file
11
internal/variables/domain/variable.go
Normal file
|
@ -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"`
|
||||||
|
}
|
5
internal/variables/repository/const.go
Normal file
5
internal/variables/repository/const.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package repository
|
||||||
|
|
||||||
|
const (
|
||||||
|
SqlFetchOneByName = "SELECT * FROM variables WHERE name = $1"
|
||||||
|
)
|
24
internal/variables/repository/variable.go
Normal file
24
internal/variables/repository/variable.go
Normal file
|
@ -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
|
||||||
|
}
|
20
internal/variables/repository/variable_test.go
Normal file
20
internal/variables/repository/variable_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
27
internal/variables/service/variable.go
Normal file
27
internal/variables/service/variable.go
Normal file
|
@ -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
|
||||||
|
}
|
24
internal/variables/service/variable_test.go
Normal file
24
internal/variables/service/variable_test.go
Normal file
|
@ -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)
|
||||||
|
}
|
14
migrations/20250417194859_variables.up.sql
Normal file
14
migrations/20250417194859_variables.up.sql
Normal file
|
@ -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');
|
5
scripts/create_migration.sh
Normal file
5
scripts/create_migration.sh
Normal file
|
@ -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
|
|
@ -1,11 +1,9 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
#Check if the migrate command is available
|
|
||||||
if ! command -v migrate &> /dev/null; then
|
if ! command -v migrate &> /dev/null; then
|
||||||
echo "migrate could not be found"
|
echo "migrate could not be found"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
#Execute migrate up
|
|
||||||
migrate -path ./migrations -database "$DATABASE_URL" -verbose up
|
migrate -path ./migrations -database "$DATABASE_URL" -verbose up
|
||||||
|
|
1
test/fixtures/01-variables.sql
vendored
Normal file
1
test/fixtures/01-variables.sql
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
INSERT INTO variables (name, value) VALUES ('first_account_created', 'false');
|
Loading…
Reference in a new issue