583 lines
15 KiB
Go
583 lines
15 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.
|
|
// 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
|
|
}
|
|
|
|
rows := make([]entities.M_Role_Menu, 0, len(menuIds))
|
|
roleUUID, err := uuid.Parse(roleId)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, mid := range menuIds {
|
|
menuUUID, err := uuid.Parse(mid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
rows = append(rows, entities.M_Role_Menu{
|
|
RoleID: roleUUID,
|
|
MenuID: menuUUID,
|
|
})
|
|
}
|
|
if len(rows) == 0 {
|
|
return dto.ErrMenuNotFound
|
|
}
|
|
|
|
res := tx.Clauses(clause.OnConflict{
|
|
Columns: []clause.Column{{Name: "role_id"}, {Name: "menu_id"}},
|
|
DoNothing: true,
|
|
}).Create(&rows)
|
|
if res.Error != nil {
|
|
return res.Error
|
|
}
|
|
|
|
if res.RowsAffected == 0 {
|
|
return dto.ErrMenuAlreadyExists
|
|
}
|
|
|
|
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,
|
|
Level: req.Level,
|
|
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
|
|
}
|
|
if req.Level != 0 {
|
|
existingRole.Level = req.Level
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|