wms-be/modules/user/service/user_service.go

744 lines
22 KiB
Go

package service
import (
"context"
"errors"
"fmt"
"strings"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
authDto "github.com/Caknoooo/go-gin-clean-starter/modules/auth/dto"
authRepo "github.com/Caknoooo/go-gin-clean-starter/modules/auth/repository"
authService "github.com/Caknoooo/go-gin-clean-starter/modules/auth/service"
clientrepository "github.com/Caknoooo/go-gin-clean-starter/modules/client/repository"
rolerepository "github.com/Caknoooo/go-gin-clean-starter/modules/role/repository"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/user/repository"
warehouserepository "github.com/Caknoooo/go-gin-clean-starter/modules/warehouse/repository"
pkgconstants "github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"github.com/google/uuid"
"gorm.io/gorm"
)
type UserService interface {
Create(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error)
Register(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error)
GetUserById(ctx context.Context, userId string) (dto.UserResponse, error)
Verify(ctx context.Context, req dto.UserLoginRequest) (authDto.TokenResponse, error)
SendVerificationEmail(ctx context.Context, req dto.SendVerificationEmailRequest) error
VerifyEmail(ctx context.Context, req dto.VerifyEmailRequest) (dto.VerifyEmailResponse, error)
Update(ctx context.Context, req dto.UserUpdateRequest, userId string) (dto.UserUpdateResponse, error)
Delete(ctx context.Context, userId string) error
RefreshToken(ctx context.Context, req authDto.RefreshTokenRequest) (authDto.TokenResponse, error)
SwitchRole(ctx context.Context, userId string, req dto.SwitchRoleRequest) (authDto.TokenResponse, error)
GetAll(ctx context.Context) ([]dto.UserResponse, error)
GetUserWarehouses(ctx context.Context, userId string) ([]dto.UserResponse, error)
SwitchWarehouse(ctx context.Context, userId string, req dto.SwitchWarehouseRequest) (authDto.TokenResponse, error)
// AssignWarehouseToUser(ctx context.Context, userId string, warehouseIds []string) error
// RemoveWarehouseFromUser(ctx context.Context, userId string, warehouseIds []string) error
// ClearUserWarehouses(ctx context.Context, userId string) error
AssignWarehousesReplace(ctx context.Context, userId string, warehouseIds []string) error
AssignWarehouse(ctx context.Context, userId string, warehouseId string) error
RemoveWarehouse(ctx context.Context, userId string, warehouseId string) error
BulkCreate(ctx context.Context, req dto.BulkCreateUserRequest) error
}
type userService struct {
clientRepository clientrepository.ClientRepository
userRepository repository.UserRepository
roleRepository rolerepository.RoleRepository
warehouserepository warehouserepository.WarehouseRepository
refreshTokenRepository authRepo.RefreshTokenRepository
jwtService authService.JWTService
db *gorm.DB
}
// BulkCreate implements UserService.
func (s *userService) BulkCreate(ctx context.Context, req dto.BulkCreateUserRequest) error {
users := make([]entities.M_User, len(req.Users))
for i, r := range req.Users {
clientName := strings.ToLower(strings.TrimSpace(r.ClientName))
client, err := s.clientRepository.GetByName(ctx, s.db, clientName)
if err != nil {
return err
}
hashedPassword, err := utils.HashPassword(r.Password)
if err != nil {
return err
}
users[i] = entities.M_User{
Username: r.Username,
Email: r.Email,
Password: hashedPassword,
Name: r.Name,
Gender: r.Gender,
Address: r.Address,
Phone: r.Phone,
PhotoUrl: r.PhotoUrl,
ClientID: client.ID,
// MaintenanceGroupUserID: r.MaintenanceGroupUserID,
// LocationID: r.LocationID,
}
}
tx := s.db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback()
}
}()
err := s.userRepository.BulkCreate(ctx, tx, users)
if err != nil {
tx.Rollback()
return err
}
if err := tx.Commit().Error; err != nil {
return err
}
return nil
}
// GetUserWarehouses implements UserService.
func (s *userService) GetUserWarehouses(ctx context.Context, userId string) ([]dto.UserResponse, error) {
user, err := s.userRepository.GetUserById(ctx, s.db, userId)
if err != nil {
return nil, err
}
var warehouses []pkgdto.IdNameResponse
for _, uw := range user.Warehouses {
warehouses = append(warehouses, pkgdto.IdNameResponse{
ID: uw.ID.String(),
Name: uw.Name,
})
}
response := dto.UserResponse{
ID: user.ID.String(),
Name: user.Name,
Warehouses: warehouses,
}
return []dto.UserResponse{response}, nil
}
// // AssignWarehouseToUser implements UserService.
// func (s *userService) AssignWarehouseToUser(ctx context.Context, userId string, warehouseIds []string) error {
// // Validasi userId
// if _, err := uuid.Parse(userId); err != nil {
// return errors.New("invalid userId format")
// }
// // Validasi warehouseIds
// for _, wid := range warehouseIds {
// if _, err := uuid.Parse(wid); err != nil {
// return errors.New("invalid warehouseId format: " + wid)
// }
// }
// // Hapus semua relasi warehouse lama user, lalu assign yang baru (replace)
// _, err := s.userRepository.AssignWarehousesToUser(ctx, s.db, userId, warehouseIds)
// if err != nil {
// return err
// }
// return nil
// }
// // RemoveWarehouseFromUser implements UserService.
// func (s *userService) RemoveWarehouseFromUser(ctx context.Context, userId string, warehouseIds []string) error {
// // Validasi userId
// if _, err := uuid.Parse(userId); err != nil {
// return errors.New("invalid userId format")
// }
// // Validasi warehouseIds
// for _, wid := range warehouseIds {
// if _, err := uuid.Parse(wid); err != nil {
// return errors.New("invalid warehouseId format: " + wid)
// }
// }
// _, err := s.userRepository.RemoveWarehousesFromUser(ctx, s.db, userId, warehouseIds)
// if err != nil {
// return err
// }
// return nil
// }
// SwitchWarehouse implements UserService.
func (s *userService) SwitchWarehouse(ctx context.Context, userId string, req dto.SwitchWarehouseRequest) (authDto.TokenResponse, error) {
user, err := s.userRepository.GetUserById(ctx, s.db, userId)
if err != nil {
return authDto.TokenResponse{}, err
}
// Delete old refresh tokens for user
err = s.refreshTokenRepository.DeleteByUserID(ctx, s.db, user.ID.String())
if err != nil {
return authDto.TokenResponse{}, err
}
// Get first role and warehouse for token generation
if len(user.Roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned")
}
if len(user.Warehouses) == 0 {
return authDto.TokenResponse{}, errors.New("user has no warehouses assigned")
}
role, err := s.roleRepository.GetRoleByID(ctx, s.db, req.ActiveRoleID)
if err != nil {
return authDto.TokenResponse{}, err
}
warehouse, err := s.warehouserepository.GetById(ctx, s.db, req.WarehouseID)
if err != nil {
return authDto.TokenResponse{}, err
}
accessToken := s.jwtService.GenerateAccessToken(
user.ClientID.String(),
userId,
role.ID.String(), // gunakan role yang sedang aktif
warehouse.ID.String(), // warehouse yang dipilih
)
refreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
refreshToken := entities.RefreshToken{
ID: uuid.New(),
UserID: user.ID,
ClientID: user.ClientID,
Token: refreshTokenString,
ExpiresAt: expiresAt,
}
_, err = s.refreshTokenRepository.Create(ctx, s.db, refreshToken)
if err != nil {
return authDto.TokenResponse{}, err
}
return authDto.TokenResponse{
AccessToken: accessToken,
RefreshToken: refreshTokenString,
}, nil
}
func (s *userService) AssignWarehouse(ctx context.Context, userId string, warehouseId string) error {
return s.userRepository.AddUserWarehouse(ctx, s.db, userId, warehouseId)
}
func (s *userService) RemoveWarehouse(ctx context.Context, userId string, warehouseId string) error {
return s.userRepository.RemoveUserWarehouse(ctx, s.db, userId, warehouseId)
}
func (s *userService) AssignWarehousesReplace(ctx context.Context, userId string, warehouseIds []string) error {
// Validasi input
if _, err := uuid.Parse(userId); err != nil {
return errors.New("invalid userId format")
}
for _, wid := range warehouseIds {
if _, err := uuid.Parse(wid); err != nil {
return fmt.Errorf("invalid warehouseId format: %s", wid)
}
}
// Mulai transaksi
tx := s.db.Begin()
// Hapus relasi lama
if err := s.userRepository.ClearUserWarehouses(ctx, tx, userId); err != nil {
tx.Rollback()
return err
}
// Tambahkan relasi baru
if err := s.userRepository.AddUserWarehouses(ctx, tx, userId, warehouseIds); err != nil {
tx.Rollback()
return err
}
return tx.Commit().Error
}
// GetAll implements UserService.
func (s *userService) GetAll(ctx context.Context) ([]dto.UserResponse, error) {
users, err := s.userRepository.GetAll(ctx, s.db)
if err != nil {
return nil, err
}
var responses []dto.UserResponse
for _, user := range users {
var roles []dto.UserRolesResponse
for _, role := range user.Roles {
roles = append(roles, dto.UserRolesResponse{
ID: role.ID.String(),
Name: role.Name,
})
}
client := pkgdto.IdNameResponse{}
if user.Client.ID != uuid.Nil {
client.ID = user.Client.ID.String()
client.Name = user.Client.Name
}
responses = append(responses, dto.UserResponse{
ID: user.ID.String(),
Name: user.Name,
Username: user.Username,
Email: user.Email,
Gender: user.Gender,
Address: user.Address,
Phone: user.Phone,
PhotoUrl: user.PhotoUrl,
Roles: roles,
Client: client,
})
}
return responses, nil
}
// Create implements UserService.
// Create implements UserService.
func (s *userService) Create(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error) {
// Cek apakah email sudah terdaftar
// _, exists, err := s.userRepository.CheckEmail(ctx, s.db, req.Email)
// if err != nil && err != gorm.ErrRecordNotFound {
// return dto.UserResponse{}, err
// }
// if exists {
// return dto.UserResponse{}, dto.ErrEmailAlreadyExists
// }
// Hash password
hashedPassword, err := utils.HashPassword(req.Password)
if err != nil {
return dto.UserResponse{}, err
}
user := entities.M_User{
Name: req.Name,
Username: req.Username,
Email: req.Email,
Password: hashedPassword,
Gender: req.Gender,
Address: req.Address,
Phone: req.Phone,
PhotoUrl: req.PhotoUrl,
ClientID: req.ClientID,
MaintenanceGroupUserID: req.MaintenanceGroupUserID,
LocationID: req.LocationID,
}
createdUser, err := s.userRepository.Register(ctx, s.db, user)
if err != nil {
return dto.UserResponse{}, err
}
return dto.UserResponse{
ID: createdUser.ID.String(),
Name: createdUser.Name,
Username: createdUser.Username,
Email: createdUser.Email,
Phone: createdUser.Phone,
Gender: createdUser.Gender,
Address: createdUser.Address,
PhotoUrl: createdUser.PhotoUrl,
}, nil
}
func NewUserService(
userRepo repository.UserRepository,
roleRepo rolerepository.RoleRepository,
warehouserepository warehouserepository.WarehouseRepository,
clientRepository clientrepository.ClientRepository,
refreshTokenRepo authRepo.RefreshTokenRepository,
jwtService authService.JWTService,
db *gorm.DB,
) UserService {
return &userService{
userRepository: userRepo,
roleRepository: roleRepo,
warehouserepository: warehouserepository,
clientRepository: clientRepository,
refreshTokenRepository: refreshTokenRepo,
jwtService: jwtService,
db: db,
}
}
// SwitchRole implements UserService.
func (s *userService) SwitchRole(ctx context.Context, userId string, req dto.SwitchRoleRequest) (authDto.TokenResponse, error) {
// Switch user role in repository
user, err := s.userRepository.GetUserById(ctx, s.db, userId)
if err != nil {
return authDto.TokenResponse{}, err
}
// Delete old refresh tokens for user
err = s.refreshTokenRepository.DeleteByUserID(ctx, s.db, user.ID.String())
if err != nil {
return authDto.TokenResponse{}, err
}
// Get first role and warehouse for token generation
if len(user.Roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned")
}
if len(user.Warehouses) == 0 {
return authDto.TokenResponse{}, errors.New("user has no warehouses assigned")
}
role, err := s.roleRepository.GetRoleByID(ctx, s.db, req.RoleID)
if err != nil {
return authDto.TokenResponse{}, err
}
warehouse, err := s.warehouserepository.GetById(ctx, s.db, req.ActiveWarehouseID)
if err != nil {
return authDto.TokenResponse{}, err
}
accessToken := s.jwtService.GenerateAccessToken(
user.ClientID.String(),
userId,
role.ID.String(), // gunakan role yang sedang aktif
warehouse.ID.String(), // warehouse yang dipilih
)
refreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
refreshToken := entities.RefreshToken{
ID: uuid.New(),
UserID: user.ID,
ClientID: user.ClientID,
Token: refreshTokenString,
ExpiresAt: expiresAt,
}
_, err = s.refreshTokenRepository.Create(ctx, s.db, refreshToken)
if err != nil {
return authDto.TokenResponse{}, err
}
return authDto.TokenResponse{
AccessToken: accessToken,
RefreshToken: refreshTokenString,
}, nil
}
func (s *userService) Register(ctx context.Context, req dto.UserCreateRequest) (dto.UserResponse, error) {
_, exists, err := s.userRepository.CheckEmail(ctx, s.db, req.Email)
if err != nil && err != gorm.ErrRecordNotFound {
return dto.UserResponse{}, err
}
if exists {
return dto.UserResponse{}, dto.ErrEmailAlreadyExists
}
enryptPassword, err := utils.HashPassword(req.Password)
if err != nil {
return dto.UserResponse{}, err
}
user := entities.M_User{
Name: req.Name,
Username: req.Username,
Email: req.Email,
Password: enryptPassword,
Gender: req.Gender,
Address: req.Address,
Phone: req.Phone,
PhotoUrl: req.PhotoUrl,
ClientID: req.ClientID,
}
createdUser, err := s.userRepository.Register(ctx, s.db, user)
if err != nil {
return dto.UserResponse{}, err
}
return dto.UserResponse{
ID: createdUser.ID.String(),
Name: createdUser.Name,
Username: createdUser.Username,
Email: createdUser.Email,
Phone: createdUser.Phone,
Gender: createdUser.Gender,
Address: createdUser.Address,
PhotoUrl: createdUser.PhotoUrl,
}, nil
}
func (s *userService) GetUserById(ctx context.Context, userId string) (dto.UserResponse, error) {
user, err := s.userRepository.GetUserById(ctx, s.db, userId)
if err != nil {
return dto.UserResponse{}, err
}
var roles []dto.UserRolesResponse
for _, ur := range user.Roles {
roles = append(roles, dto.UserRolesResponse{
ID: ur.ID.String(),
Name: ur.Name,
})
}
var client pkgdto.IdNameResponse
client = pkgdto.IdNameResponse{
ID: user.Client.ID.String(),
Name: user.Client.Name,
}
var warehouses []pkgdto.IdNameResponse
for _, uw := range user.Warehouses {
warehouses = append(warehouses, pkgdto.IdNameResponse{
ID: uw.ID.String(),
Name: uw.Name,
})
}
return dto.UserResponse{
ID: user.ID.String(),
Name: user.Name,
Username: user.Username,
Email: user.Email,
Gender: user.Gender,
Address: user.Address,
Phone: user.Phone,
PhotoUrl: user.PhotoUrl,
Roles: roles,
Client: client,
Warehouses: warehouses,
}, nil
}
func (s *userService) Verify(ctx context.Context, req dto.UserLoginRequest) (authDto.TokenResponse, error) {
var user entities.M_User
var err error
if strings.Contains(req.Login, "@") {
user, err = s.userRepository.GetUserByEmail(ctx, s.db, req.Login)
} else {
user, err = s.userRepository.GetUserByUsername(ctx, s.db, req.Login)
}
if err != nil {
return authDto.TokenResponse{}, dto.ErrUserNotFound
}
isValid := utils.CheckPasswordHash(req.Password, user.Password)
if !isValid {
return authDto.TokenResponse{}, dto.ErrUserNotFound
}
roles := append([]entities.M_Role{}, user.Roles...)
if len(roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned")
}
warehouses := append([]entities.MWarehouseEntity{}, user.Warehouses...)
warehouseID := ""
if user.Username == pkgconstants.SUPERADMIN {
warehouseID = ""
} else if len(warehouses) > 0 {
warehouseID = warehouses[0].ID.String()
} else {
return authDto.TokenResponse{}, errors.New("user has no warehouses assigned")
}
// DeleteByUserID
err = s.refreshTokenRepository.DeleteByUserID(ctx, s.db, user.ID.String())
if err != nil {
return authDto.TokenResponse{}, err
}
accessToken := s.jwtService.GenerateAccessToken(user.ClientID.String(), user.ID.String(), roles[0].ID.String(), warehouseID)
refreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
refreshToken := entities.RefreshToken{
ID: uuid.New(),
UserID: user.ID,
ClientID: user.ClientID,
Token: refreshTokenString,
ExpiresAt: expiresAt,
}
_, err = s.refreshTokenRepository.Create(ctx, s.db, refreshToken)
if err != nil {
return authDto.TokenResponse{}, err
}
return authDto.TokenResponse{
AccessToken: accessToken,
RefreshToken: refreshTokenString,
}, nil
}
func (s *userService) SendVerificationEmail(ctx context.Context, req dto.SendVerificationEmailRequest) error {
user, err := s.userRepository.GetUserByEmail(ctx, s.db, req.Email)
if err != nil {
return dto.ErrEmailNotFound
}
// if user.IsVerified {
// return dto.ErrAccountAlreadyVerified
// }
roles := append([]entities.M_Role{}, user.Roles...)
if len(roles) == 0 {
return errors.New("user has no roles assigned")
}
warehouses := append([]entities.MWarehouseEntity{}, user.Warehouses...)
if len(warehouses) == 0 {
return errors.New("user has no warehouses assigned")
}
verificationToken := s.jwtService.GenerateAccessToken(user.ClientID.String(), user.ID.String(), roles[0].ID.String(), warehouses[0].ID.String())
subject := "Email Verification"
body := "Please verify your email using this token: " + verificationToken
return utils.SendMail(user.Email, subject, body)
}
func (s *userService) VerifyEmail(ctx context.Context, req dto.VerifyEmailRequest) (dto.VerifyEmailResponse, error) {
token, err := s.jwtService.ValidateToken(req.Token)
if err != nil || !token.Valid {
return dto.VerifyEmailResponse{}, dto.ErrTokenInvalid
}
userTokenInfo, err := s.jwtService.GetUserIDByToken(req.Token)
if err != nil {
return dto.VerifyEmailResponse{}, dto.ErrTokenInvalid
}
user, err := s.userRepository.GetUserById(ctx, s.db, userTokenInfo.UserID)
if err != nil {
return dto.VerifyEmailResponse{}, dto.ErrUserNotFound
}
// user.IsVerified = true
updatedUser, err := s.userRepository.Update(ctx, s.db, user)
if err != nil {
return dto.VerifyEmailResponse{}, err
}
return dto.VerifyEmailResponse{
Email: updatedUser.Email,
// IsVerified: updatedUser.IsVerified,
}, nil
}
func (s *userService) Update(ctx context.Context, req dto.UserUpdateRequest, userId string) (dto.UserUpdateResponse, error) {
user, err := s.userRepository.GetUserById(ctx, s.db, userId)
if err != nil {
return dto.UserUpdateResponse{}, dto.ErrUserNotFound
}
if req.Name != "" {
user.Name = req.Name
}
if req.Email != "" {
user.Email = req.Email
}
if req.Username != "" {
user.Username = req.Username
}
if req.Password != "" {
enryptPassword, err := utils.HashPassword(req.Password)
if err != nil {
return dto.UserUpdateResponse{}, err
}
user.Password = enryptPassword
}
if req.Gender != "" {
user.Gender = req.Gender
}
if req.Address != "" {
user.Address = req.Address
}
if req.Phone != "" {
user.Phone = req.Phone
}
if req.PhotoUrl != "" {
user.PhotoUrl = req.PhotoUrl
}
if req.ClientID != uuid.Nil {
user.ClientID = req.ClientID
}
if req.MaintenanceGroupUserID != uuid.Nil {
user.MaintenanceGroupUserID = req.MaintenanceGroupUserID
}
if req.LocationID != uuid.Nil {
user.LocationID = req.LocationID
}
updatedUser, err := s.userRepository.Update(ctx, s.db, user)
if err != nil {
return dto.UserUpdateResponse{}, err
}
return dto.UserUpdateResponse{
ID: updatedUser.ID.String(),
Name: updatedUser.Name,
Username: updatedUser.Username,
Phone: updatedUser.Phone,
Email: updatedUser.Email,
Gender: updatedUser.Gender,
Address: updatedUser.Address,
PhotoUrl: updatedUser.PhotoUrl,
}, nil
}
func (s *userService) Delete(ctx context.Context, userId string) error {
return s.userRepository.Delete(ctx, s.db, userId)
}
func (s *userService) RefreshToken(ctx context.Context, req authDto.RefreshTokenRequest) (authDto.TokenResponse, error) {
refreshToken, err := s.refreshTokenRepository.FindByToken(ctx, s.db, req.RefreshToken)
if err != nil {
return authDto.TokenResponse{}, err
}
roles := append([]entities.M_Role{}, refreshToken.User.Roles...)
if len(roles) == 0 {
return authDto.TokenResponse{}, errors.New("user has no roles assigned")
}
warehouses := append([]entities.MWarehouseEntity{}, refreshToken.User.Warehouses...)
if len(warehouses) == 0 {
return authDto.TokenResponse{}, errors.New("user has no warehouses assigned")
}
accessToken := s.jwtService.GenerateAccessToken(refreshToken.ClientID.String(), refreshToken.UserID.String(), roles[0].ID.String(), warehouses[0].ID.String())
// newRefreshTokenString, expiresAt := s.jwtService.GenerateRefreshToken()
// err = s.refreshTokenRepository.DeleteByToken(ctx, s.db, req.RefreshToken)
// if err != nil {
// return authDto.TokenResponse{}, err
// }
// newRefreshToken := entities.RefreshToken{
// ID: uuid.New(),
// UserID: refreshToken.UserID,
// ClientID: refreshToken.ClientID,
// Token: newRefreshTokenString,
// ExpiresAt: expiresAt,
// }
// _, err = s.refreshTokenRepository.Create(ctx, s.db, newRefreshToken)
// if err != nil {
// return authDto.TokenResponse{}, err
// }
return authDto.TokenResponse{
AccessToken: accessToken,
// RefreshToken: newRefreshTokenString,
RefreshToken: req.RefreshToken, // atau kosongkan jika tidak ingin return refresh token sama sekali
}, nil
}