add user usecase/repository | add admin acc creation when none exists
This commit is contained in:
parent
532cf70b84
commit
a9d7250623
1
go.mod
1
go.mod
|
@ -10,6 +10,7 @@ require (
|
|||
github.com/gosimple/slug v1.9.0
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sethvargo/go-password v0.2.0
|
||||
github.com/sirupsen/logrus v1.8.0
|
||||
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -125,6 +125,8 @@ github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI=
|
||||
github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE=
|
||||
github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/go-pg/pg/v10"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sethvargo/go-password/password"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
)
|
||||
|
||||
|
@ -19,28 +20,31 @@ const (
|
|||
`
|
||||
)
|
||||
|
||||
func init() {
|
||||
orm.RegisterTable((*models.QualificationToProfession)(nil))
|
||||
}
|
||||
var log = logrus.WithField("package", "internal/db")
|
||||
|
||||
type Config struct {
|
||||
DebugHook bool
|
||||
}
|
||||
|
||||
func init() {
|
||||
orm.RegisterTable((*models.QualificationToProfession)(nil))
|
||||
}
|
||||
|
||||
func New(cfg *Config) (*pg.DB, error) {
|
||||
db := pg.Connect(prepareOptions())
|
||||
if err := createSchema(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if cfg != nil {
|
||||
if cfg.DebugHook {
|
||||
db.AddQueryHook(DebugHook{
|
||||
Entry: logrus.WithField("package", "internal/db"),
|
||||
Entry: log,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if err := createSchema(db); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
|
@ -60,7 +64,7 @@ func createSchema(db *pg.DB) error {
|
|||
return errors.Wrap(err, "createSchema")
|
||||
}
|
||||
|
||||
models := []interface{}{
|
||||
modelsToCreate := []interface{}{
|
||||
(*models.User)(nil),
|
||||
(*models.Profession)(nil),
|
||||
(*models.Qualification)(nil),
|
||||
|
@ -68,7 +72,7 @@ func createSchema(db *pg.DB) error {
|
|||
(*models.Question)(nil),
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
for _, model := range modelsToCreate {
|
||||
err := tx.Model(model).CreateTable(&orm.CreateTableOptions{
|
||||
IfNotExists: true,
|
||||
FKConstraints: true,
|
||||
|
@ -78,6 +82,35 @@ func createSchema(db *pg.DB) error {
|
|||
}
|
||||
}
|
||||
|
||||
total, err := db.Model(modelsToCreate[0]).Where("role = ?", models.RoleAdmin).Count()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "createSchema")
|
||||
}
|
||||
if total == 0 {
|
||||
activated := true
|
||||
pswd, err := password.Generate(16, 4, 4, true, false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "createSchema")
|
||||
}
|
||||
email := "admin@admin.com"
|
||||
_, err = tx.
|
||||
Model(&models.User{
|
||||
DisplayName: "admin",
|
||||
Email: email,
|
||||
Role: models.RoleAdmin,
|
||||
Activated: &activated,
|
||||
Password: pswd,
|
||||
}).
|
||||
Insert()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "createSchema")
|
||||
}
|
||||
log.
|
||||
WithField("email", email).
|
||||
WithField("password", pswd).
|
||||
Info("Admin account has been created")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package db
|
|||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -22,7 +23,7 @@ func (logger DebugHook) BeforeQuery(ctx context.Context, evt *pg.QueryEvent) (co
|
|||
if evt.Err != nil {
|
||||
logger.Entry.Errorf("%s executing a query:\n%s\n", evt.Err, q)
|
||||
} else {
|
||||
logger.Entry.Info(string(q))
|
||||
logger.Entry.Info(strings.TrimSpace(string(q)))
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
|
|
|
@ -8,7 +8,6 @@ import (
|
|||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/go-pg/pg/v10/orm"
|
||||
"github.com/gosimple/slug"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
@ -17,8 +16,7 @@ type User struct {
|
|||
tableName struct{} `pg:"alias:user"`
|
||||
|
||||
ID int `json:"id" pg:",pk" xml:"id" gqlgen:"id"`
|
||||
Slug string `json:"slug" pg:",unique" xml:"slug" gqlgen:"slug"`
|
||||
DisplayName string `json:"displayName" pg:",use_zero" xml:"displayName" gqlgen:"displayName"`
|
||||
DisplayName string `json:"displayName" pg:",use_zero,notnull" xml:"displayName" gqlgen:"displayName"`
|
||||
Password string `json:"-" gqlgen:"-" xml:"password"`
|
||||
Email string `json:"email" pg:",unique" xml:"email" gqlgen:"email"`
|
||||
CreatedAt time.Time `json:"createdAt" pg:"default:now()" xml:"createdAt" gqlgen:"createdAt"`
|
||||
|
@ -38,19 +36,6 @@ func (u *User) BeforeInsert(ctx context.Context) (context.Context, error) {
|
|||
return ctx, err
|
||||
}
|
||||
u.Password = string(hashedPassword)
|
||||
u.Slug = slug.Make(u.DisplayName)
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (u *User) BeforeUpdate(ctx context.Context) (context.Context, error) {
|
||||
if cost, _ := bcrypt.Cost([]byte(u.Password)); u.Password != "" && cost == 0 {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(u.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
u.Password = string(hashedPassword)
|
||||
}
|
||||
|
||||
return ctx, nil
|
||||
}
|
||||
|
@ -73,6 +58,15 @@ type UserInput struct {
|
|||
Activated *bool `json:"activated" xml:"activated" gqlgen:"activated"`
|
||||
}
|
||||
|
||||
func (input *UserInput) IsEmpty() bool {
|
||||
return input == nil &&
|
||||
input.DisplayName == nil &&
|
||||
input.Password == nil &&
|
||||
input.Email == nil &&
|
||||
input.Role == nil &&
|
||||
input.Activated == nil
|
||||
}
|
||||
|
||||
func (input *UserInput) ToUser() *User {
|
||||
u := &User{
|
||||
Activated: input.Activated,
|
||||
|
@ -92,13 +86,40 @@ func (input *UserInput) ToUser() *User {
|
|||
return u
|
||||
}
|
||||
|
||||
func (input *UserInput) ApplyUpdate(q *orm.Query) (*orm.Query, error) {
|
||||
if !input.IsEmpty() {
|
||||
if input.DisplayName != nil {
|
||||
q.Set("display_name = ?", *input.DisplayName)
|
||||
}
|
||||
|
||||
if input.Password != nil {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(*input.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return q, err
|
||||
}
|
||||
q.Set("password = ?", string(hashedPassword))
|
||||
}
|
||||
|
||||
if input.Email != nil {
|
||||
q.Set("name = ?", *input.Email)
|
||||
}
|
||||
|
||||
if input.Role != nil {
|
||||
q.Set("role = ?", *input.Role)
|
||||
}
|
||||
|
||||
if input.Activated != nil {
|
||||
q.Set("activated = ?", *input.Activated)
|
||||
}
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
type UserFilter struct {
|
||||
ID []int `json:"id" xml:"id" gqlgen:"id"`
|
||||
IDNEQ []int `json:"idNEQ" xml:"idNEQ" gqlgen:"idNEQ"`
|
||||
|
||||
Slug []string `json:"slug" xml:"slug" gqlgen:"slug"`
|
||||
SlugNEQ []string `json:"slugNEQ" xml:"slugNEQ" gqlgen:"slugNEQ"`
|
||||
|
||||
Activated *bool `json:"activated" xml:"activated" gqlgen:"activated"`
|
||||
|
||||
DisplayName []string `json:"displayName" xml:"displayName" gqlgen:"displayName"`
|
||||
|
@ -133,13 +154,6 @@ func (f *UserFilter) WhereWithAlias(q *orm.Query, alias string) (*orm.Query, err
|
|||
q = q.Where(sqlutils.BuildConditionNotInArray(sqlutils.AddAliasToColumnName("id", alias)), pg.Array(f.IDNEQ))
|
||||
}
|
||||
|
||||
if !isZero(f.Slug) {
|
||||
q = q.Where(sqlutils.BuildConditionArray(sqlutils.AddAliasToColumnName("slug", alias)), pg.Array(f.Slug))
|
||||
}
|
||||
if !isZero(f.SlugNEQ) {
|
||||
q = q.Where(sqlutils.BuildConditionNotInArray(sqlutils.AddAliasToColumnName("slug", alias)), pg.Array(f.SlugNEQ))
|
||||
}
|
||||
|
||||
if !isZero(f.Activated) {
|
||||
q = q.Where(sqlutils.BuildConditionEquals(sqlutils.AddAliasToColumnName("activated", alias)), f.Activated)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.Profe
|
|||
return nil, fmt.Errorf(messageInvalidID)
|
||||
}
|
||||
if err := ucase.validateInput(input, validateOptions{true}); err != nil {
|
||||
|
||||
return nil, err
|
||||
}
|
||||
items, err := ucase.professionRepository.UpdateMany(ctx,
|
||||
&models.ProfessionFilter{
|
||||
|
|
9
internal/user/constants.go
Normal file
9
internal/user/constants.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package user
|
||||
|
||||
const (
|
||||
DefaultLimit = 100
|
||||
MinDisplayNameLength = 2
|
||||
MaxDisplayNameLength = 32
|
||||
MinPasswordLength = 6
|
||||
MaxPasswordLength = 64
|
||||
)
|
22
internal/user/repository.go
Normal file
22
internal/user/repository.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
)
|
||||
|
||||
type FetchConfig struct {
|
||||
Filter *models.UserFilter
|
||||
Offset int
|
||||
Limit int
|
||||
Sort []string
|
||||
Count bool
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
Store(ctx context.Context, input *models.UserInput) (*models.User, error)
|
||||
UpdateMany(ctx context.Context, f *models.UserFilter, input *models.UserInput) ([]*models.User, error)
|
||||
Delete(ctx context.Context, f *models.UserFilter) ([]*models.User, error)
|
||||
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.User, int, error)
|
||||
}
|
8
internal/user/repository/message.go
Normal file
8
internal/user/repository/message.go
Normal file
|
@ -0,0 +1,8 @@
|
|||
package repository
|
||||
|
||||
const (
|
||||
messageEmailIsAlreadyTaken = "Istnieje już użytkownik o podanym adresie e-mail."
|
||||
messageFailedToSaveModel = "Wystąpił błąd podczas zapisywania użytkownika, prosimy spróbować później."
|
||||
messageFailedToDeleteModel = "Wystąpił błąd podczas usuwania użytkownika, prosimy spróbować później."
|
||||
messageFailedToFetchModel = "Wystąpił błąd podczas pobierania użytkowników, prosimy spróbować później."
|
||||
)
|
97
internal/user/repository/pg_repository.go
Normal file
97
internal/user/repository/pg_repository.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
errorutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/error"
|
||||
|
||||
"github.com/go-pg/pg/v10"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/user"
|
||||
)
|
||||
|
||||
type pgRepository struct {
|
||||
*pg.DB
|
||||
}
|
||||
|
||||
type PGRepositoryConfig struct {
|
||||
DB *pg.DB
|
||||
}
|
||||
|
||||
func NewPGRepository(cfg *PGRepositoryConfig) (user.Repository, error) {
|
||||
if cfg == nil || cfg.DB == nil {
|
||||
return nil, fmt.Errorf("user/pg_repository: *pg.DB is required")
|
||||
}
|
||||
return &pgRepository{
|
||||
cfg.DB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Store(ctx context.Context, input *models.UserInput) (*models.User, error) {
|
||||
item := input.ToUser()
|
||||
if _, err := repo.
|
||||
Model(item).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Insert(); err != nil {
|
||||
if strings.Contains(err.Error(), "email") {
|
||||
return nil, errorutils.Wrap(err, messageEmailIsAlreadyTaken)
|
||||
}
|
||||
return nil, errorutils.Wrap(err, messageFailedToSaveModel)
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) UpdateMany(ctx context.Context, f *models.UserFilter, input *models.UserInput) ([]*models.User, error) {
|
||||
items := []*models.User{}
|
||||
if _, err := repo.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Apply(input.ApplyUpdate).
|
||||
Apply(f.Where).
|
||||
Update(); err != nil && err != pg.ErrNoRows {
|
||||
if strings.Contains(err.Error(), "email") {
|
||||
return nil, errorutils.Wrap(err, messageEmailIsAlreadyTaken)
|
||||
}
|
||||
return nil, errorutils.Wrap(err, messageFailedToSaveModel)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Delete(ctx context.Context, f *models.UserFilter) ([]*models.User, error) {
|
||||
items := []*models.User{}
|
||||
if _, err := repo.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Returning("*").
|
||||
Apply(f.Where).
|
||||
Delete(); err != nil && err != pg.ErrNoRows {
|
||||
return nil, errorutils.Wrap(err, messageFailedToDeleteModel)
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (repo *pgRepository) Fetch(ctx context.Context, cfg *user.FetchConfig) ([]*models.User, int, error) {
|
||||
var err error
|
||||
items := []*models.User{}
|
||||
total := 0
|
||||
query := repo.
|
||||
Model(&items).
|
||||
Context(ctx).
|
||||
Limit(cfg.Limit).
|
||||
Offset(cfg.Offset).
|
||||
Apply(cfg.Filter.Where)
|
||||
|
||||
if cfg.Count {
|
||||
total, err = query.SelectAndCount()
|
||||
} else {
|
||||
err = query.Select()
|
||||
}
|
||||
if err != nil && err != pg.ErrNoRows {
|
||||
return nil, 0, errorutils.Wrap(err, messageFailedToFetchModel)
|
||||
}
|
||||
return items, total, nil
|
||||
}
|
17
internal/user/usecase.go
Normal file
17
internal/user/usecase.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
)
|
||||
|
||||
type Usecase interface {
|
||||
Store(ctx context.Context, input *models.UserInput) (*models.User, error)
|
||||
UpdateOne(ctx context.Context, id int, input *models.UserInput) (*models.User, error)
|
||||
UpdateMany(ctx context.Context, f *models.UserFilter, input *models.UserInput) ([]*models.User, error)
|
||||
Delete(ctx context.Context, f *models.UserFilter) ([]*models.User, error)
|
||||
Fetch(ctx context.Context, cfg *FetchConfig) ([]*models.User, int, error)
|
||||
GetByID(ctx context.Context, id int) (*models.User, error)
|
||||
GetByCredentials(ctx context.Context, email, password string) (*models.User, error)
|
||||
}
|
15
internal/user/usecase/message.go
Normal file
15
internal/user/usecase/message.go
Normal file
|
@ -0,0 +1,15 @@
|
|||
package usecase
|
||||
|
||||
const (
|
||||
messageInvalidID = "Niepoprawne ID."
|
||||
messageItemNotFound = "Nie znaleziono użytkownika."
|
||||
messageEmptyPayload = "Nie wprowadzono jakichkolwiek danych."
|
||||
messageInvalidCredentials = "Niepoprawny email/hasło."
|
||||
messageDisplayNameIsRequired = "Wymagane jest wprowadzenie nazwy użytkownika."
|
||||
messageDisplayNameIsTooLong = "Nazwa użytkownika może się składać z maksymalnie %d znaków."
|
||||
messageEmailIsRequired = "Wymagane jest wprowadzenie adresu e-mail"
|
||||
messageEmailIsInvalid = "Wprowadzony adres e-mail jest nieprawidłowy."
|
||||
messagePasswordIsRequired = "Wymagane jest wprowadzenie hasła."
|
||||
messagePasswordInvalidLength = "Długość hasła powinna wynosić %d-%d znaków."
|
||||
messageInvalidRole = "Nieznana rola użytkownika."
|
||||
)
|
174
internal/user/usecase/usecase.go
Normal file
174
internal/user/usecase/usecase.go
Normal file
|
@ -0,0 +1,174 @@
|
|||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/models"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/internal/user"
|
||||
"github.com/zdam-egzamin-zawodowy/backend/pkg/utils"
|
||||
sqlutils "github.com/zdam-egzamin-zawodowy/backend/pkg/utils/sql"
|
||||
)
|
||||
|
||||
type usecase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
UserRepository user.Repository
|
||||
}
|
||||
|
||||
func New(cfg *Config) (user.Usecase, error) {
|
||||
if cfg == nil || cfg.UserRepository == nil {
|
||||
return nil, fmt.Errorf("user/usecase: UserRepository is required")
|
||||
}
|
||||
return &usecase{
|
||||
cfg.UserRepository,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) Store(ctx context.Context, input *models.UserInput) (*models.User, error) {
|
||||
if err := ucase.validateInput(input, validateOptions{false}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ucase.userRepository.Store(ctx, input)
|
||||
}
|
||||
|
||||
func (ucase *usecase) UpdateOne(ctx context.Context, id int, input *models.UserInput) (*models.User, error) {
|
||||
if id <= 0 {
|
||||
return nil, fmt.Errorf(messageInvalidID)
|
||||
}
|
||||
items, err := ucase.UpdateMany(
|
||||
ctx,
|
||||
&models.UserFilter{
|
||||
ID: []int{id},
|
||||
},
|
||||
input,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageItemNotFound)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) UpdateMany(ctx context.Context, f *models.UserFilter, input *models.UserInput) ([]*models.User, error) {
|
||||
if f == nil {
|
||||
return []*models.User{}, nil
|
||||
}
|
||||
if err := ucase.validateInput(input, validateOptions{true}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items, err := ucase.userRepository.UpdateMany(ctx, f, input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) Delete(ctx context.Context, f *models.UserFilter) ([]*models.User, error) {
|
||||
return ucase.userRepository.Delete(ctx, f)
|
||||
}
|
||||
|
||||
func (ucase *usecase) Fetch(ctx context.Context, cfg *user.FetchConfig) ([]*models.User, int, error) {
|
||||
if cfg == nil {
|
||||
cfg = &user.FetchConfig{
|
||||
Limit: user.DefaultLimit,
|
||||
Count: true,
|
||||
}
|
||||
}
|
||||
cfg.Sort = sqlutils.SanitizeSortExpressions(cfg.Sort)
|
||||
return ucase.userRepository.Fetch(ctx, cfg)
|
||||
}
|
||||
|
||||
func (ucase *usecase) GetByID(ctx context.Context, id int) (*models.User, error) {
|
||||
items, _, err := ucase.Fetch(ctx, &user.FetchConfig{
|
||||
Limit: 1,
|
||||
Count: false,
|
||||
Filter: &models.UserFilter{
|
||||
ID: []int{id},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageItemNotFound)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
func (ucase *usecase) GetByCredentials(ctx context.Context, email, password string) (*models.User, error) {
|
||||
items, _, err := ucase.Fetch(ctx, &user.FetchConfig{
|
||||
Limit: 1,
|
||||
Count: false,
|
||||
Filter: &models.UserFilter{
|
||||
Email: []string{email},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(items) == 0 {
|
||||
return nil, fmt.Errorf(messageInvalidCredentials)
|
||||
}
|
||||
if err := items[0].CompareHashAndPassword(password); err != nil {
|
||||
return nil, fmt.Errorf(messageInvalidCredentials)
|
||||
}
|
||||
return items[0], nil
|
||||
}
|
||||
|
||||
type validateOptions struct {
|
||||
acceptNilValues bool
|
||||
}
|
||||
|
||||
func (ucase *usecase) validateInput(input *models.UserInput, opts validateOptions) error {
|
||||
if input.IsEmpty() {
|
||||
return fmt.Errorf(messageEmptyPayload)
|
||||
}
|
||||
|
||||
if input.DisplayName != nil {
|
||||
trimmedDisplayName := strings.TrimSpace(*input.DisplayName)
|
||||
input.DisplayName = &trimmedDisplayName
|
||||
if len(trimmedDisplayName) < user.MinDisplayNameLength {
|
||||
return fmt.Errorf(messageDisplayNameIsRequired)
|
||||
} else if len(trimmedDisplayName) > user.MaxDisplayNameLength {
|
||||
return fmt.Errorf(messageDisplayNameIsTooLong, user.MaxDisplayNameLength)
|
||||
}
|
||||
} else if !opts.acceptNilValues {
|
||||
return fmt.Errorf(messageDisplayNameIsRequired)
|
||||
}
|
||||
|
||||
if input.Email != nil {
|
||||
trimmedEmail := strings.TrimSpace(*input.Email)
|
||||
input.Email = &trimmedEmail
|
||||
if !utils.IsEmailValid(trimmedEmail) {
|
||||
return fmt.Errorf(messageEmailIsInvalid)
|
||||
}
|
||||
} else if !opts.acceptNilValues {
|
||||
return fmt.Errorf(messageEmailIsRequired)
|
||||
}
|
||||
|
||||
if input.Password != nil {
|
||||
trimmedPassword := strings.ToLower(strings.TrimSpace(*input.Password))
|
||||
input.Password = &trimmedPassword
|
||||
if len(trimmedPassword) > user.MaxPasswordLength || len(trimmedPassword) < user.MinPasswordLength {
|
||||
return fmt.Errorf(messagePasswordInvalidLength, user.MinPasswordLength, user.MaxPasswordLength)
|
||||
}
|
||||
} else if !opts.acceptNilValues {
|
||||
return fmt.Errorf(messagePasswordIsRequired)
|
||||
}
|
||||
|
||||
if input.Role != nil {
|
||||
if !input.Role.IsValid() {
|
||||
return fmt.Errorf(messageInvalidRole)
|
||||
}
|
||||
} else if !opts.acceptNilValues {
|
||||
return fmt.Errorf(messagePasswordIsRequired)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
24
pkg/utils/is_email_valid.go
Normal file
24
pkg/utils/is_email_valid.go
Normal file
|
@ -0,0 +1,24 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func IsEmailValid(e string) bool {
|
||||
if len(e) < 3 && len(e) > 254 {
|
||||
return false
|
||||
}
|
||||
if !emailRegex.MatchString(e) {
|
||||
return false
|
||||
}
|
||||
parts := strings.Split(e, "@")
|
||||
mx, err := net.LookupMX(parts[1])
|
||||
if err != nil || len(mx) == 0 {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
Reference in New Issue
Block a user