package user import ( "errors" "cardverse/internal/pkg/utils" ) var ErrEmailTaken = errors.New("email sudah terdaftar") var ErrUserNotFound = errors.New("user tidak ditemukan") var ErrUserSuspended = errors.New("akun anda sedang disuspensi") var ErrForbiddenRoleCreation = errors.New("hanya superadmin yang dapat membuat atau mengubah akun dengan role admin atau superadmin") type Service interface { Create(creatorID *uint, req CreateUserRequest) (*User, error) GetAll() ([]User, error) GetAllPaginated(query ListUserQuery) ([]User, int64, error) GetByID(id uint) (*User, error) Update(modifierID uint, id uint, req UpdateUserRequest) (*User, error) UpdateProfile(userID uint, req UpdateProfileRequest) (*User, error) UpdateAvatar(userID uint, avatarURL string) (*User, error) SuspendUser(modifierID uint, id uint) (*User, error) UnsuspendUser(modifierID uint, id uint) (*User, error) Delete(id uint) error } type service struct { repo Repository } func NewService(repo Repository) Service { return &service{repo: repo} } func (s *service) Create(creatorID *uint, req CreateUserRequest) (*User, error) { existing, _ := s.repo.FindByEmail(req.Email) if existing != nil { return nil, ErrEmailTaken } role := req.Role if role == "" { role = RoleUser } // Batasan role: Hanya Superadmin yang boleh membuat akun dengan role admin atau superadmin if role == RoleAdmin || role == RoleSuperAdmin { if creatorID == nil { return nil, ErrForbiddenRoleCreation } creator, err := s.repo.FindByID(*creatorID) if err != nil || creator.Role != RoleSuperAdmin { return nil, ErrForbiddenRoleCreation } } hashedPassword, err := utils.HashPassword(req.Password) if err != nil { return nil, err } newUser := &User{ Name: req.Name, Email: req.Email, Password: hashedPassword, Role: role, Status: StatusActive, IsEmailVerified: true, CreatedBy: creatorID, } if err := s.repo.Create(newUser); err != nil { return nil, err } return newUser, nil } func (s *service) GetAll() ([]User, error) { return s.repo.FindAll() } func (s *service) GetAllPaginated(query ListUserQuery) ([]User, int64, error) { page := query.Page if page < 1 { page = 1 } limit := query.Limit if limit < 1 { limit = 20 } return s.repo.FindAllPaginated(page, limit, query) } func (s *service) GetByID(id uint) (*User, error) { u, err := s.repo.FindByID(id) if err != nil { return nil, ErrUserNotFound } return u, nil } func (s *service) Update(modifierID uint, id uint, req UpdateUserRequest) (*User, error) { u, err := s.repo.FindByID(id) if err != nil { return nil, ErrUserNotFound } if req.Name != "" { u.Name = req.Name } if req.Email != "" && req.Email != u.Email { existing, _ := s.repo.FindByEmail(req.Email) if existing != nil { return nil, ErrEmailTaken } u.Email = req.Email } if req.AvatarURL != "" { if u.AvatarURL != nil && *u.AvatarURL != "" && *u.AvatarURL != req.AvatarURL { utils.DeleteLocalFile(*u.AvatarURL) } u.AvatarURL = &req.AvatarURL } if req.Role != "" && req.Role != u.Role { // Batasan role: Hanya Superadmin yang boleh mengubah role ke admin atau superadmin if req.Role == RoleAdmin || req.Role == RoleSuperAdmin { creator, err := s.repo.FindByID(modifierID) if err != nil || creator.Role != RoleSuperAdmin { return nil, ErrForbiddenRoleCreation } } u.Role = req.Role } if req.Status != "" { u.Status = req.Status } u.UpdatedBy = &modifierID if err := s.repo.Update(u); err != nil { return nil, err } return u, nil } func (s *service) UpdateProfile(userID uint, req UpdateProfileRequest) (*User, error) { u, err := s.repo.FindByID(userID) if err != nil { return nil, ErrUserNotFound } if req.Name != "" { u.Name = req.Name } if req.AvatarURL != "" { if u.AvatarURL != nil && *u.AvatarURL != "" && *u.AvatarURL != req.AvatarURL { utils.DeleteLocalFile(*u.AvatarURL) } u.AvatarURL = &req.AvatarURL } u.UpdatedBy = &userID if err := s.repo.Update(u); err != nil { return nil, err } return u, nil } func (s *service) UpdateAvatar(userID uint, avatarURL string) (*User, error) { u, err := s.repo.FindByID(userID) if err != nil { return nil, ErrUserNotFound } // Hapus file avatar lama jika ada if u.AvatarURL != nil && *u.AvatarURL != "" && *u.AvatarURL != avatarURL { utils.DeleteLocalFile(*u.AvatarURL) } u.AvatarURL = &avatarURL u.UpdatedBy = &userID if err := s.repo.Update(u); err != nil { return nil, err } return u, nil } func (s *service) SuspendUser(modifierID uint, id uint) (*User, error) { u, err := s.repo.FindByID(id) if err != nil { return nil, ErrUserNotFound } u.Status = StatusSuspended u.UpdatedBy = &modifierID if err := s.repo.Update(u); err != nil { return nil, err } return u, nil } func (s *service) UnsuspendUser(modifierID uint, id uint) (*User, error) { u, err := s.repo.FindByID(id) if err != nil { return nil, ErrUserNotFound } u.Status = StatusActive u.UpdatedBy = &modifierID if err := s.repo.Update(u); err != nil { return nil, err } return u, nil } func (s *service) Delete(id uint) error { _, err := s.repo.FindByID(id) if err != nil { return ErrUserNotFound } return s.repo.Delete(id) }