aboutsummaryrefslogtreecommitdiff
path: root/pkg/service
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/service')
-rw-r--r--pkg/service/crud/crud.go87
-rw-r--r--pkg/service/user/login.go31
-rw-r--r--pkg/service/user/register.go85
-rw-r--r--pkg/service/user/user.go12
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}
+}