wms-be/modules/role/service/role_service.go

550 lines
14 KiB
Go

package service
import (
"context"
"github.com/Caknoooo/go-gin-clean-starter/database/entities"
authRepo "github.com/Caknoooo/go-gin-clean-starter/modules/auth/repository"
"github.com/Caknoooo/go-gin-clean-starter/modules/auth/service"
"github.com/Caknoooo/go-gin-clean-starter/modules/role/dto"
"github.com/Caknoooo/go-gin-clean-starter/modules/role/query"
"github.com/Caknoooo/go-gin-clean-starter/modules/role/repository"
userDto "github.com/Caknoooo/go-gin-clean-starter/modules/user/dto"
userService "github.com/Caknoooo/go-gin-clean-starter/modules/user/service"
pkgdto "github.com/Caknoooo/go-gin-clean-starter/pkg/dto"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type RoleService interface {
CreateRole(ctx context.Context, role dto.RoleCreateRequest) (dto.RoleResponse, error)
GetRoles(ctx context.Context, filter query.RoleFilter) ([]dto.RoleResponse, error)
GetRoleByID(ctx context.Context, id string) (dto.RoleResponse, error)
UpdateRole(ctx context.Context, id string, role dto.RoleUpdateRequest) (dto.RoleResponse, error)
DeleteRole(ctx context.Context, id string) error
AssignPermissionsToRole(ctx context.Context, roleId string, permissions []string) error
RemovePermissionsFromRole(ctx context.Context, roleId string, permissions []string) error
AssignRolesToUser(ctx context.Context, userId string, roleIds []string) error
RemoveRolesFromUser(ctx context.Context, userId string, roleIds []string) error
GetRolesByUserID(ctx context.Context, userId string) ([]dto.RoleResponse, error)
AssignMenusToRole(ctx context.Context, roleId string, menuIds []string) error
RemoveMenusFromRole(ctx context.Context, roleId string, menuIds []string) error
GetAll(ctx context.Context) ([]dto.RoleResponse, error)
}
type roleService struct {
roleRepo repository.RoleRepository
refreshTokenRepository authRepo.RefreshTokenRepository
jwtService service.JWTService
userService userService.UserService
db *gorm.DB
}
// GetAll implements RoleService.
func (r *roleService) GetAll(ctx context.Context) ([]dto.RoleResponse, error) {
roles, err := r.roleRepo.GetAll(ctx, r.db)
if err != nil {
return nil, err
}
var responses []dto.RoleResponse
for _, role := range roles {
responses = append(responses, ToRoleResponse(role))
}
return responses, nil
}
// AssignMenusToRole implements RoleService.
func (r *roleService) AssignMenusToRole(ctx context.Context, roleId string, menuIds []string) error {
if len(menuIds) == 0 {
return nil
}
// Pastikan role ada
if _, err := r.GetRoleByID(ctx, roleId); err != nil {
return dto.ErrRoleNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Validasi role di DB
_, err := r.roleRepo.GetRoleByID(ctx, tx, roleId)
if err != nil {
return dto.ErrRoleNotFound
}
// Assign menus ke role
if err := r.roleRepo.AssignMenusToRole(ctx, tx, roleId, menuIds); err != nil {
return err
}
return nil
})
}
// RemoveMenusFromRole implements RoleService.
func (r *roleService) RemoveMenusFromRole(ctx context.Context, roleId string, menuIds []string) error {
if len(menuIds) == 0 {
return nil
}
// Pastikan role ada
if _, err := r.GetRoleByID(ctx, roleId); err != nil {
return dto.ErrRoleNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Validasi role di DB
_, err := r.roleRepo.GetRoleByID(ctx, tx, roleId)
if err != nil {
return dto.ErrRoleNotFound
}
// Remove menus dari role
if err := r.roleRepo.RemoveMenusFromRole(ctx, tx, roleId, menuIds); err != nil {
return err
}
return nil
})
}
// AssignPermissionsToRole implements RoleService.
func (r *roleService) AssignPermissionsToRole(ctx context.Context, roleId string, permission_ids []string) error {
if len(permission_ids) == 0 {
return nil
}
if _, err := r.GetRoleByID(ctx, roleId); err != nil {
return dto.ErrRoleNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
_, err := r.roleRepo.GetRoleByID(ctx, tx, roleId)
if err != nil {
return dto.ErrRoleNotFound
}
rows := make([]entities.M_Role_Permission, 0, len(permission_ids))
roleUUID, err := uuid.Parse(roleId)
if err != nil {
return err
}
for _, pid := range permission_ids {
permissionUUID, err := uuid.Parse(pid)
if err != nil {
return err
}
// _, err = r.roleRepo.GetPermissionByID(ctx, tx, pid)
// if err != nil {
// return dto.ErrPermissionNotFound
// }
rows = append(rows, entities.M_Role_Permission{
RoleID: roleUUID,
PermissionID: permissionUUID,
})
logrus.Info("Prepared to assign permission ", permissionUUID, " to role ", roleUUID)
}
if len(rows) == 0 {
return dto.ErrPermissionNotFound
}
res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "role_id"}, {Name: "permission_id"}},
DoNothing: true,
}).Create(&rows)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return dto.ErrPermissionAlreadyExists
}
return nil
})
}
// AssignRolesToUser implements RoleService.
func (r *roleService) AssignRolesToUser(ctx context.Context, userId string, roleIds []string) error {
if len(roleIds) == 0 {
return nil
}
// verify user exists via UserService (reuse business logic)
if _, err := r.userService.GetUserById(ctx, userId); err != nil {
return userDto.ErrUserNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
rows := make([]entities.M_User_Role, 0, len(roleIds))
userUUID, err := uuid.Parse(userId)
if err != nil {
return err
}
for _, rid := range roleIds {
roleUUID, err := uuid.Parse(rid)
if err != nil {
return err
}
_, err = r.roleRepo.GetRoleByID(ctx, tx, rid)
if err != nil {
continue
}
rows = append(rows, entities.M_User_Role{
UserID: userUUID,
RoleID: roleUUID,
})
}
if len(rows) == 0 {
return dto.ErrRoleNotFound
}
res := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "role_id"}},
DoNothing: true,
}).Create(&rows)
if res.Error != nil {
return res.Error
}
if res.RowsAffected == 0 {
return dto.ErrRoleAlreadyExists
}
return nil
})
}
// CreateRole implements RoleService.
func (r *roleService) CreateRole(ctx context.Context, req dto.RoleCreateRequest) (dto.RoleResponse, error) {
// _, exists, err := r.roleRepo.CheckRoleName(ctx, r.db, req.Name)
// if err != nil && err != gorm.ErrRecordNotFound {
// return dto.RoleResponse{}, err
// }
// if exists {
// return dto.RoleResponse{}, dto.ErrRoleAlreadyExists
// }
clientUUID, err := uuid.Parse(req.ClientID)
if err != nil {
return dto.RoleResponse{}, err
}
role := entities.M_Role{
Name: req.Name,
Description: req.Description,
IconUrl: req.IconUrl,
Type: req.Type,
HomeUrl: req.HomeUrl,
ClientID: clientUUID,
}
createdRole, err := r.roleRepo.CreateRole(ctx, r.db, role)
if err != nil {
return dto.RoleResponse{}, err
}
result, err := r.roleRepo.GetRoleByID(ctx, r.db, createdRole.ID.String())
if err != nil {
return dto.RoleResponse{}, err
}
return ToRoleResponse(result), nil
}
// DeleteRole implements RoleService.
func (r *roleService) DeleteRole(ctx context.Context, id string) error {
if _, err := r.roleRepo.GetRoleByID(ctx, r.db, id); err != nil {
return dto.ErrRoleNotFound
}
return r.roleRepo.DeleteRole(ctx, r.db, id)
}
// GetRoleByID implements RoleService.
func (r *roleService) GetRoleByID(ctx context.Context, id string) (dto.RoleResponse, error) {
role, err := r.roleRepo.GetRoleByID(ctx, r.db, id)
if err != nil {
return dto.RoleResponse{}, err
}
return ToRoleResponse(role), nil
}
// GetRoles implements RoleService.
func (r *roleService) GetRoles(ctx context.Context, filter query.RoleFilter) ([]dto.RoleResponse, error) {
panic("unimplemented")
}
// GetRolesByUserID implements RoleService.
// func (r *roleService) GetRolesByUserID(ctx context.Context, userId string) ([]dto.RoleResponse, error) {
// if _, err := r.userService.GetUserById(ctx, userId); err != nil {
// return nil, userDto.ErrUserNotFound
// }
// roles, err := r.roleRepo.GetRolesByUserID(ctx, r.db, userId)
// if err != nil {
// logrus.Error("Error fetching roles for user ", userId, ": ", err)
// return nil, err
// }
// logrus.Info("Fetched ", len(roles), " roles for user ", userId)
// var responses []dto.RoleResponse
// for _, role := range roles {
// var permissions []dto.RolePermissionsResponse
// for _, p := range role.Permissions {
// permissions = append(permissions, dto.RolePermissionsResponse{
// ID: p.ID.String(),
// Name: p.Name,
// })
// }
// responses = append(responses, dto.RoleResponse{
// ID: role.ID.String(),
// Name: role.Name,
// Description: role.Description,
// IconUrl: role.IconUrl,
// Type: role.Type,
// HomeUrl: role.HomeUrl,
// ClientID: role.ClientID.String(),
// Permissions: permissions,
// })
// }
// return responses, nil
// }
func (r *roleService) GetRolesByUserID(ctx context.Context, userId string) ([]dto.RoleResponse, error) {
if _, err := r.userService.GetUserById(ctx, userId); err != nil {
return nil, userDto.ErrUserNotFound
}
roles, err := r.roleRepo.GetRolesByUserID(ctx, r.db, userId)
if err != nil {
return nil, err
}
var responses []dto.RoleResponse
for _, role := range roles {
responses = append(responses, ToRoleResponse(role))
}
return responses, nil
}
// RemovePermissionsFromRole implements RoleService.
func (r *roleService) RemovePermissionsFromRole(ctx context.Context, roleId string, permission_ids []string) error {
if len(permission_ids) == 0 {
return nil
}
if _, err := r.GetRoleByID(ctx, roleId); err != nil {
return dto.ErrRoleNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
roleUUID, err := uuid.Parse(roleId)
if err != nil {
return err
}
permissionUUIDs := make([]uuid.UUID, 0, len(permission_ids))
for _, pid := range permission_ids {
permissionUUID, err := uuid.Parse(pid)
if err != nil {
return err
}
permissionUUIDs = append(permissionUUIDs, permissionUUID)
}
if len(permissionUUIDs) == 0 {
return dto.ErrPermissionNotFound
}
// delete matching user-role pairs
res := tx.Where("role_id = ? AND permission_id IN ?", roleUUID, permissionUUIDs).
Delete(&entities.M_Role_Permission{})
if res.Error != nil {
return res.Error
}
// jika tidak ada row yang dihapus, kembalikan error agar klien tahu tidak ada role yang cocok
if res.RowsAffected == 0 {
return dto.ErrRoleNotFound
}
return nil
})
}
// RemoveRolesFromUser implements RoleService.
func (r *roleService) RemoveRolesFromUser(ctx context.Context, userId string, roleIds []string) error {
if len(roleIds) == 0 {
return nil
}
// verify user exists via UserService (reuse business logic)
if _, err := r.userService.GetUserById(ctx, userId); err != nil {
return userDto.ErrUserNotFound
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
userUUID, err := uuid.Parse(userId)
if err != nil {
return err
}
roleUUIDs := make([]uuid.UUID, 0, len(roleIds))
for _, rid := range roleIds {
roleUUID, err := uuid.Parse(rid)
if err != nil {
return err
}
roleUUIDs = append(roleUUIDs, roleUUID)
}
if len(roleUUIDs) == 0 {
return dto.ErrRoleNotFound
}
// delete matching user-role pairs
res := tx.Where("user_id = ? AND role_id IN ?", userUUID, roleUUIDs).
Delete(&entities.M_User_Role{})
if res.Error != nil {
return res.Error
}
// jika tidak ada row yang dihapus, kembalikan error agar klien tahu tidak ada role yang cocok
if res.RowsAffected == 0 {
return dto.ErrRoleNotFound
}
return nil
})
}
// UpdateRole implements RoleService.
func (r *roleService) UpdateRole(ctx context.Context, id string, req dto.RoleUpdateRequest) (dto.RoleResponse, error) {
existingRole, err := r.roleRepo.GetRoleByID(ctx, r.db, id)
if err != nil {
return dto.RoleResponse{}, dto.ErrRoleNotFound
}
if req.Name != "" {
// _, exists, err := r.roleRepo.CheckRoleName(ctx, r.db, req.Name)
// if err != nil {
// return dto.RoleResponse{}, err
// }
// if exists {
// return dto.RoleResponse{}, dto.ErrRoleAlreadyExists
// }
existingRole.Name = req.Name
}
if req.Description != "" {
existingRole.Description = req.Description
}
if req.IconUrl != "" {
existingRole.IconUrl = req.IconUrl
}
if req.Type != "" {
existingRole.Type = req.Type
}
if req.HomeUrl != "" {
existingRole.HomeUrl = req.HomeUrl
}
updatedRole, err := r.roleRepo.UpdateRole(ctx, r.db, existingRole)
if err != nil {
return dto.RoleResponse{}, err
}
result, err := r.roleRepo.GetRoleByID(ctx, r.db, updatedRole.ID.String())
if err != nil {
return dto.RoleResponse{}, err
}
return ToRoleResponse(result), nil
}
func ToRoleResponse(role entities.M_Role) dto.RoleResponse {
var permissions []dto.RolePermissionsResponse
for _, p := range role.Permissions {
permissions = append(permissions, dto.RolePermissionsResponse{
ID: p.ID.String(),
Name: p.Name,
})
}
var client pkgdto.IdNameResponse
if role.Client.ID != uuid.Nil {
client = pkgdto.IdNameResponse{
ID: role.Client.ID.String(),
Name: role.Client.Name,
}
}
var menus []dto.RoleMenuResponse
for _, m := range role.Menus {
var parent *dto.RoleMenuResponse
if m.Parent != nil {
parent = &dto.RoleMenuResponse{
ID: m.Parent.ID.String(),
Name: m.Parent.Name,
IconUrl: m.Parent.IconUrl,
Url: m.Parent.Url,
Sequence: m.Parent.Sequence,
Parent: nil, // atau rekursif jika ingin nested lebih dalam
}
}
menus = append(menus, dto.RoleMenuResponse{
ID: m.ID.String(),
Name: m.Name,
IconUrl: m.IconUrl,
Url: m.Url,
Sequence: m.Sequence,
Parent: parent,
})
}
return dto.RoleResponse{
ID: role.ID.String(),
Name: role.Name,
Description: role.Description,
IconUrl: role.IconUrl,
Type: role.Type,
HomeUrl: role.HomeUrl,
Level: role.Level,
Client: client,
Permissions: permissions,
Menus: menus,
}
}
func NewRoleService(
roleRepo repository.RoleRepository,
refreshTokenRepo authRepo.RefreshTokenRepository,
jwtService service.JWTService,
userService userService.UserService,
db *gorm.DB,
) RoleService {
return &roleService{
roleRepo: roleRepo,
refreshTokenRepository: refreshTokenRepo,
jwtService: jwtService,
userService: userService,
db: db,
}
}