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

514 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"
"github.com/Caknoooo/go-gin-clean-starter/pkg/constants"
"github.com/Caknoooo/go-gin-clean-starter/pkg/utils"
"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
log *logrus.Logger
}
// 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, dto.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
}
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) {
userID := ""
if ctx != nil {
userID = utils.GetUserID(ctx)
}
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,
FullAuditTrail: utils.FillAuditTrail(ctx, constants.CREATE),
}
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
}
r.log.WithFields(logrus.Fields{
"user_id": userID,
"action": "create",
"entity": "role",
"entity_id": createdRole.ID.String(),
}).Info("Role created")
return dto.ToRoleResponse(result), nil
}
// DeleteRole implements RoleService.
func (r *roleService) DeleteRole(ctx context.Context, id string) error {
role, err := r.roleRepo.GetRoleByID(ctx, r.db, id)
if err != nil {
return dto.ErrRoleNotFound
}
role.FullAuditTrail = utils.FillAuditTrail(ctx, constants.DELETE)
if _, err := r.roleRepo.UpdateRole(ctx, r.db, role); err != nil {
return err
}
if err := r.roleRepo.DeleteRole(ctx, r.db, id); err != nil {
return err
}
userID := ""
if ctx != nil {
userID = utils.GetUserID(ctx)
}
r.log.WithFields(logrus.Fields{
"user_id": userID,
"action": "delete",
"entity": "role",
"entity_id": id,
}).Info("Role deleted")
return nil
}
// 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 dto.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 {
return nil, err
}
var responses []dto.RoleResponse
for _, role := range roles {
responses = append(responses, dto.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
}
before := existingRole
if req.Name != "" {
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
}
existingRole.FullAuditTrail = utils.FillAuditTrail(ctx, constants.UPDATE)
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
}
changes := utils.GetChangedFields(before, result)
userID := ""
if ctx != nil {
userID = utils.GetUserID(ctx)
}
r.log.WithFields(logrus.Fields{
"user_id": userID,
"action": "update",
"entity": "role",
"entity_id": id,
"changes": changes,
}).Info("Role updated")
return dto.ToRoleResponse(result), nil
}
func NewRoleService(
roleRepo repository.RoleRepository,
refreshTokenRepo authRepo.RefreshTokenRepository,
jwtService service.JWTService,
userService userService.UserService,
db *gorm.DB,
log *logrus.Logger,
) RoleService {
return &roleService{
roleRepo: roleRepo,
refreshTokenRepository: refreshTokenRepo,
jwtService: jwtService,
userService: userService,
db: db,
log: log,
}
}