Files
uPortal2/backend/internal/user/service.go
T
2026-10-21 19:41:26 +03:00

192 lines
4.5 KiB
Go

package user
import (
"context"
"errors"
"log/slog"
"golang.org/x/crypto/bcrypt"
)
const (
MaxImageWidth = 1024
MaxImageHeight = 1024
PasswordCost = 14
)
type Service struct {
r *Repository
}
func NewService(r *Repository) (*Service, error) {
return &Service{r: r}, nil
}
func (s *Service) hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), PasswordCost)
if err != nil {
slog.Error("hash password", slog.String("op", "service.hashPassword"), slog.Any("error", err))
return "", ErrInternal
}
return string(bytes), nil
}
func (s *Service) CreateUser(ctx context.Context, login, password string, isActive bool) (*User, error) {
hashed, err := s.hashPassword(password)
if err != nil {
return nil, err
}
return s.r.CreateUser(ctx, login, hashed, isActive)
}
func (s *Service) SetPassword(ctx context.Context, user *User, password string) (*User, error) {
hashed, err := s.hashPassword(password)
if err != nil {
return nil, err
}
values := *user
cloned := &values
cloned.Password = hashed
err = s.r.UpdateUser(ctx, cloned)
if err != nil {
return nil, err
}
return cloned, nil
}
func (s *Service) ValidatePassword(ctx context.Context, user *User, password string) error {
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
if err != nil {
if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) {
return ErrInvalidPassword
}
slog.Error("compare password", slog.String("op", "service.ValidatePassword"), slog.Int64("user_id", user.Id), slog.Any("error", err))
return ErrInternal
}
return nil
}
func (s *Service) Users(ctx context.Context, includeInactive bool, limit, offset int64) ([]*User, error) {
return s.r.Users(ctx, includeInactive, limit, offset)
}
func (s *Service) CountUsers(ctx context.Context, includeInactive bool) (int64, error) {
return s.r.CountUsers(ctx, includeInactive)
}
func (s *Service) UserById(ctx context.Context, id int64) (*User, error) {
return s.r.UserById(ctx, id)
}
func (s *Service) UserByLogin(ctx context.Context, login string) (*User, error) {
return s.r.UserByLogin(ctx, login)
}
func (s *Service) UpdatePassword(ctx context.Context, user *User, oldPassword, newPassword string) (*User, error) {
err := s.ValidatePassword(ctx, user, oldPassword)
if err != nil {
return nil, err
}
return s.SetPassword(ctx, user, newPassword)
}
func (s *Service) UpdateProfile(ctx context.Context, user *User, fullname, email string) (*User, error) {
values := *user
cloned := &values
cloned.FullName = &fullname
cloned.Email = &email
err := s.r.UpdateUser(ctx, cloned)
if err != nil {
return nil, err
}
return cloned, nil
}
func (s *Service) CreateAdmin(ctx context.Context, login, password string) (*User, error) {
hashed, err := s.hashPassword(password)
if err != nil {
return nil, err
}
tx, err := s.r.Db.BeginTx(ctx, nil)
if err != nil {
slog.Error("begin transaction", slog.String("op", "service.CreateAdmin"), slog.Any("error", err))
return nil, ErrInternal
}
defer tx.Rollback()
txRepo := s.r.WithTx(tx)
user, err := txRepo.CreateUser(ctx, login, hashed, true)
if err != nil {
return nil, err
}
user.IsAdmin = true
err = txRepo.UpdateUser(ctx, user)
if err != nil {
return nil, err
}
tx.Commit()
return user, nil
}
func (s *Service) UpdateAvatar(ctx context.Context, user *User, image []byte) error {
tx, err := s.r.Db.BeginTx(ctx, nil)
if err != nil {
slog.Error("begin transaction", slog.String("op", "service.UpdateAvatar"), slog.Int64("user_id", user.Id), slog.Any("error", err))
return ErrInternal
}
defer tx.Rollback()
txRepo := s.r.WithTx(tx)
err = txRepo.SetAvatar(ctx, user.Id, image)
if err != nil {
return err
}
err = txRepo.UpdateUser(ctx, user)
if err != nil {
return err
}
tx.Commit()
return nil
}
func (s *Service) Avatar(ctx context.Context, user *User) ([]byte, error) {
return s.r.Avatar(ctx, user.Id)
}
func (s *Service) ActivateUser(ctx context.Context, user *User) (*User, error) {
tx, err := s.r.Db.BeginTx(ctx, nil)
if err != nil {
slog.Error("begin transaction", slog.String("op", "service.ActivateUser"), slog.Int64("user_id", user.Id), slog.Any("error", err))
return nil, ErrInternal
}
defer tx.Rollback()
txRepo := s.r.WithTx(tx)
err = txRepo.ActivateUser(ctx, user.Id)
if err != nil {
return nil, err
}
cloned, err := txRepo.UserById(ctx, user.Id)
if err != nil {
return nil, err
}
tx.Commit()
return cloned, nil
}
func (s *Service) DeleteUser(ctx context.Context, user *User) error {
return s.r.DeleteUser(ctx, user.Id)
}