diff options
Diffstat (limited to 'pkg/service')
-rw-r--r-- | pkg/service/crud/crud.go | 87 | ||||
-rw-r--r-- | pkg/service/user/login.go | 31 | ||||
-rw-r--r-- | pkg/service/user/register.go | 85 | ||||
-rw-r--r-- | pkg/service/user/user.go | 12 |
4 files changed, 215 insertions, 0 deletions
diff --git a/pkg/service/crud/crud.go b/pkg/service/crud/crud.go new file mode 100644 index 0000000..799eacb --- /dev/null +++ b/pkg/service/crud/crud.go @@ -0,0 +1,87 @@ +package crud + +import ( + "context" + "errors" + + "github.com/uptrace/bun" + "github.com/uptrace/bun/driver/pgdriver" +) + +var ErrRecordAlreadyExists = errors.New("record already exists") + +type Service[T any] struct { + db *bun.DB +} + +func NewService[T any](db *bun.DB) *Service[T] { + return &Service[T]{ + db: db, + } +} + +func (s *Service[T]) Insert(ctx context.Context, model *T) error { + if _, err := s.db.NewInsert().Model(model).Returning("*").Exec(ctx); err != nil { + pqErr := pgdriver.Error{} + if errors.As(err, &pqErr) { + if pqErr.Field('C') == "23505" { + return ErrRecordAlreadyExists + } + } + + return err + } + + return nil +} + +func (s *Service[T]) UpdatePk(ctx context.Context, model *T, columns []string) error { + if _, err := s.db.NewUpdate().Model(model).WherePK(columns...).Column(columns...).Exec(ctx); err != nil { + pqErr := pgdriver.Error{} + if errors.As(err, &pqErr) { + if pqErr.Field('C') == "23505" { + return ErrRecordAlreadyExists + } + } + + return err + } + + return nil +} + +//nolint:revive +func (s *Service[T]) Update(ctx context.Context, model *T, columns []string, query string, args ...any) error { + if _, err := s.db.NewUpdate().Model(model).Where(query, args...).Column(columns...).Exec(ctx); err != nil { + pqErr := pgdriver.Error{} + if errors.As(err, &pqErr) { + if pqErr.Field('C') == "23505" { + return ErrRecordAlreadyExists + } + } + + return err + } + + return nil +} + +func (s *Service[T]) FindOne(ctx context.Context, query string, args ...any) (*T, error) { + m := new(T) + + return m, s.db.NewSelect().Model(m).Where(query, args...).Scan(ctx, m) +} + +//nolint:revive +func (s *Service[T]) Find(ctx context.Context, query string, args ...any) ([]T, int, error) { + m := make([]T, 0) + c, err := s.db.NewSelect().Model(m).Where(query, args...).ScanAndCount(ctx, &m) + + return m, c, err +} + +func (s *Service[T]) Delete(ctx context.Context, query string, args ...any) error { + _, err := s.db.NewDelete().Model((*T)(nil)).Where(query, args...).Exec(ctx) + + return err +} diff --git a/pkg/service/user/login.go b/pkg/service/user/login.go new file mode 100644 index 0000000..d1f5c65 --- /dev/null +++ b/pkg/service/user/login.go @@ -0,0 +1,31 @@ +package user + +import ( + "context" + "errors" + + normalizer "github.com/dimuska139/go-email-normalizer/v3" + "golang.org/x/crypto/bcrypt" + + "go.neonxp.ru/framework/pkg/model" + "go.neonxp.ru/framework/pkg/tpl" +) + +var ErrInvalidUserOrPassword = errors.New("invalid_user_or_password") + +func (s *Service) Login(ctx context.Context, form *tpl.LoginForm) (*model.User, error) { + n := normalizer.NewNormalizer() + u := &model.User{ + Email: n.Normalize(form.Email), + } + + if err := s.db.NewSelect().Model(u).Where("email = ?", u.Email).Scan(ctx, u); err != nil { + return nil, errors.Join(err, ErrInvalidUserOrPassword) + } + + if err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(form.Password)); err != nil { + return nil, errors.Join(err, ErrInvalidUserOrPassword) + } + + return u, nil +} diff --git a/pkg/service/user/register.go b/pkg/service/user/register.go new file mode 100644 index 0000000..69f19a2 --- /dev/null +++ b/pkg/service/user/register.go @@ -0,0 +1,85 @@ +package user + +import ( + "context" + "errors" + "regexp" + + normalizer "github.com/dimuska139/go-email-normalizer/v3" + "github.com/uptrace/bun/driver/pgdriver" + "golang.org/x/crypto/bcrypt" + + "go.neonxp.ru/framework/pkg/model" + "go.neonxp.ru/framework/pkg/tpl" +) + +var ( + ErrUsernameToShort = errors.New("username_to_short") + ErrUserAlreadyExist = errors.New("user_already_exists") + ErrPasswordTooWeak = errors.New("password_too_weak") + ErrPasswordTooShort = errors.New("password_too_short") +) + +const ( + minUsernameLen = 3 + minPasswordLen = 8 +) + +func (s *Service) Register(ctx context.Context, form *tpl.RegisterForm) (*model.User, error) { + if len(form.Username) < minUsernameLen { + return nil, ErrUsernameToShort + } + + if err := checkPasswordLever(form.Password); err != nil { + return nil, err + } + + password, err := bcrypt.GenerateFromPassword([]byte(form.Password), bcrypt.DefaultCost) + if err != nil { + return nil, err + } + + n := normalizer.NewNormalizer() + u := &model.User{ + Username: form.Username, + Email: n.Normalize(form.Email), + Password: string(password), + } + + if _, err := s.db.NewInsert().Model(u).Returning("*").Exec(ctx); err != nil { + pqErr := pgdriver.Error{} + if errors.As(err, &pqErr) { + if pqErr.Field('C') == "23505" { + return nil, ErrUserAlreadyExist + } + } + + return nil, err + } + + return u, nil +} + +func checkPasswordLever(ps string) error { + if len(ps) < minPasswordLen { + return ErrPasswordTooShort + } + + lowerCase := `[a-z]{1}` + upperCase := `[A-Z]{1}` + symbol := `[0-9!@#~$%^&*()+|_]{1}` + + if b, err := regexp.MatchString(lowerCase, ps); !b || err != nil { + return ErrPasswordTooWeak + } + + if b, err := regexp.MatchString(upperCase, ps); !b || err != nil { + return ErrPasswordTooWeak + } + + if b, err := regexp.MatchString(symbol, ps); !b || err != nil { + return ErrPasswordTooWeak + } + + return nil +} diff --git a/pkg/service/user/user.go b/pkg/service/user/user.go new file mode 100644 index 0000000..aa27eee --- /dev/null +++ b/pkg/service/user/user.go @@ -0,0 +1,12 @@ +package user + +import "github.com/uptrace/bun" + +type Service struct { + db *bun.DB +} + +// NewService returns new Service. +func NewService(db *bun.DB) *Service { + return &Service{db: db} +} |