package processor import ( "context" "fmt" "golang.org/x/crypto/bcrypt" "apskel-pos-be/internal/entities" "apskel-pos-be/internal/mappers" "apskel-pos-be/internal/models" "github.com/google/uuid" ) type UserProcessorImpl struct { userRepo UserRepository organizationRepo OrganizationRepository outletRepo OutletRepository } func NewUserProcessor( userRepo UserRepository, organizationRepo OrganizationRepository, outletRepo OutletRepository, ) *UserProcessorImpl { return &UserProcessorImpl{ userRepo: userRepo, organizationRepo: organizationRepo, outletRepo: outletRepo, } } func (p *UserProcessorImpl) CreateUser(ctx context.Context, req *models.CreateUserRequest) (*models.UserResponse, error) { _, err := p.organizationRepo.GetByID(ctx, req.OrganizationID) if err != nil { return nil, fmt.Errorf("organization not found: %w", err) } if req.OutletID != nil { _, err := p.outletRepo.GetByID(ctx, *req.OutletID) if err != nil { return nil, fmt.Errorf("outlet not found: %w", err) } } existingUser, err := p.userRepo.GetByEmail(ctx, req.Email) if err == nil && existingUser != nil { return nil, fmt.Errorf("user with email %s already exists", req.Email) } passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) if err != nil { return nil, fmt.Errorf("failed to hash password: %w", err) } userEntity := mappers.UserCreateRequestToEntity(req, string(passwordHash)) err = p.userRepo.Create(ctx, userEntity) if err != nil { return nil, fmt.Errorf("failed to create user: %w", err) } return mappers.UserEntityToResponse(userEntity), nil } func (p *UserProcessorImpl) UpdateUser(ctx context.Context, id uuid.UUID, req *models.UpdateUserRequest) (*models.UserResponse, error) { existingUser, err := p.userRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } if req.Email != nil && *req.Email != existingUser.Email { existingUserByEmail, err := p.userRepo.GetByEmail(ctx, *req.Email) if err == nil && existingUserByEmail != nil && existingUserByEmail.ID != id { return nil, fmt.Errorf("user with email %s already exists", *req.Email) } } if req.Name != nil { existingUser.Name = *req.Name } if req.Email != nil { existingUser.Email = *req.Email } if req.Role != nil { existingUser.Role = entities.UserRole(*req.Role) } if req.OutletID != nil { existingUser.OutletID = req.OutletID } if req.IsActive != nil { existingUser.IsActive = *req.IsActive } if req.Permissions != nil { existingUser.Permissions = entities.Permissions(*req.Permissions) } err = p.userRepo.Update(ctx, existingUser) if err != nil { return nil, fmt.Errorf("failed to update user: %w", err) } return mappers.UserEntityToResponse(existingUser), nil } func (p *UserProcessorImpl) DeleteUser(ctx context.Context, id uuid.UUID) error { _, err := p.userRepo.GetByID(ctx, id) if err != nil { return fmt.Errorf("user not found: %w", err) } err = p.userRepo.Delete(ctx, id) if err != nil { return fmt.Errorf("failed to delete user: %w", err) } return nil } func (p *UserProcessorImpl) GetUserByID(ctx context.Context, id uuid.UUID) (*models.UserResponse, error) { user, err := p.userRepo.GetByID(ctx, id) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } return mappers.UserEntityToResponse(user), nil } func (p *UserProcessorImpl) GetUserByEmail(ctx context.Context, email string) (*models.UserResponse, error) { user, err := p.userRepo.GetByEmail(ctx, email) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } return mappers.UserEntityToResponse(user), nil } func (p *UserProcessorImpl) ListUsers(ctx context.Context, organizationID uuid.UUID, page, limit int) ([]models.UserResponse, int, error) { _, err := p.organizationRepo.GetByID(ctx, organizationID) if err != nil { return nil, 0, fmt.Errorf("organization not found: %w", err) } offset := (page - 1) * limit filters := map[string]interface{}{ "organization_id": organizationID, } users, totalCount, err := p.userRepo.List(ctx, filters, limit, offset) if err != nil { return nil, 0, fmt.Errorf("failed to get users: %w", err) } responses := make([]models.UserResponse, len(users)) for i, user := range users { response := mappers.UserEntityToResponse(user) if response != nil { responses[i] = *response } } return responses, int(totalCount), nil } func (p *UserProcessorImpl) GetUserEntityByEmail(ctx context.Context, email string) (*entities.User, error) { user, err := p.userRepo.GetByEmail(ctx, email) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } return user, nil } func (p *UserProcessorImpl) ChangePassword(ctx context.Context, userID uuid.UUID, req *models.ChangePasswordRequest) error { user, err := p.userRepo.GetByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.CurrentPassword)) if err != nil { return fmt.Errorf("current password is incorrect") } newPasswordHash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost) if err != nil { return fmt.Errorf("failed to hash new password: %w", err) } err = p.userRepo.UpdatePassword(ctx, userID, string(newPasswordHash)) if err != nil { return fmt.Errorf("failed to update password: %w", err) } return nil } func (p *UserProcessorImpl) ActivateUser(ctx context.Context, userID uuid.UUID) error { _, err := p.userRepo.GetByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } err = p.userRepo.UpdateActiveStatus(ctx, userID, true) if err != nil { return fmt.Errorf("failed to activate user: %w", err) } return nil } func (p *UserProcessorImpl) DeactivateUser(ctx context.Context, userID uuid.UUID) error { _, err := p.userRepo.GetByID(ctx, userID) if err != nil { return fmt.Errorf("user not found: %w", err) } err = p.userRepo.UpdateActiveStatus(ctx, userID, false) if err != nil { return fmt.Errorf("failed to deactivate user: %w", err) } return nil } func (p *UserProcessorImpl) UpdateUserOutlet(ctx context.Context, userID uuid.UUID, req *models.UpdateUserOutletRequest) (*models.UserResponse, error) { // Get user first to validate existence and get organization_id existingUser, err := p.userRepo.GetByID(ctx, userID) if err != nil { return nil, fmt.Errorf("user not found: %w", err) } // Validate outlet exists outlet, err := p.outletRepo.GetByID(ctx, req.OutletID) if err != nil { return nil, fmt.Errorf("outlet not found: %w", err) } // Validate outlet belongs to user's organization if outlet.OrganizationID != existingUser.OrganizationID { return nil, fmt.Errorf("outlet does not belong to user's organization") } // Update user's outlet_id existingUser.OutletID = &req.OutletID err = p.userRepo.Update(ctx, existingUser) if err != nil { return nil, fmt.Errorf("failed to update user outlet: %w", err) } return mappers.UserEntityToResponse(existingUser), nil }