184 lines
5.0 KiB
Go
184 lines
5.0 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gateway/internal/model/permission/domain"
|
|
"gateway/internal/model/permission/domain/entity"
|
|
"gateway/internal/model/permission/domain/enum"
|
|
domrepo "gateway/internal/model/permission/domain/repository"
|
|
dom "gateway/internal/model/permission/domain/usecase"
|
|
)
|
|
|
|
// roleKeyPattern enforces lower-case alphanumeric / underscore / dot keys.
|
|
var roleKeyPattern = regexp.MustCompile(`^[a-z][a-z0-9._-]{1,63}$`)
|
|
|
|
// RoleUseCaseParam injects the role + role-permission + user-role repos.
|
|
// User roles are needed so Delete can refuse when assignments still exist.
|
|
type RoleUseCaseParam struct {
|
|
Roles domrepo.RoleRepository
|
|
RolePermissions domrepo.RolePermissionRepository
|
|
UserRoles domrepo.UserRoleRepository
|
|
}
|
|
|
|
type roleUseCase struct {
|
|
roles domrepo.RoleRepository
|
|
rolePerms domrepo.RolePermissionRepository
|
|
userRoles domrepo.UserRoleRepository
|
|
}
|
|
|
|
// NewRoleUseCase returns the tenant-scoped role manager.
|
|
func NewRoleUseCase(param RoleUseCaseParam) dom.RoleUseCase {
|
|
return &roleUseCase{
|
|
roles: param.Roles,
|
|
rolePerms: param.RolePermissions,
|
|
userRoles: param.UserRoles,
|
|
}
|
|
}
|
|
|
|
func (uc *roleUseCase) Create(ctx context.Context, param *dom.CreateRoleParam) (*entity.Role, error) {
|
|
if param == nil || param.TenantID == "" {
|
|
return nil, errb.InputMissingRequired("tenant_id")
|
|
}
|
|
if err := validateRoleKey(param.Key); err != nil {
|
|
return nil, err
|
|
}
|
|
displayName := strings.TrimSpace(param.DisplayName)
|
|
if displayName == "" {
|
|
displayName = param.Key
|
|
}
|
|
if len(displayName) > domain.RoleDisplayNameMax {
|
|
return nil, errb.InputInvalidRange("display_name too long")
|
|
}
|
|
status := param.Status
|
|
if status == "" {
|
|
status = enum.StatusOpen
|
|
}
|
|
if !status.IsValid() {
|
|
return nil, errb.InputInvalidFormat("invalid status")
|
|
}
|
|
role := &entity.Role{
|
|
TenantID: param.TenantID,
|
|
Key: param.Key,
|
|
DisplayName: displayName,
|
|
CreatorUID: param.CreatorUID,
|
|
Status: status,
|
|
IsSystem: false,
|
|
}
|
|
if err := uc.roles.Insert(ctx, role); err != nil {
|
|
return nil, wrapRepoErr(err, "create role")
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
func (uc *roleUseCase) Get(ctx context.Context, tenantID, id string) (*entity.Role, error) {
|
|
role, err := uc.roles.GetByID(ctx, tenantID, id)
|
|
if err != nil {
|
|
return nil, wrapRepoErr(err)
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
func (uc *roleUseCase) GetByKey(ctx context.Context, tenantID, key string) (*entity.Role, error) {
|
|
role, err := uc.roles.GetByKey(ctx, tenantID, key)
|
|
if err != nil {
|
|
return nil, wrapRepoErr(err)
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
func (uc *roleUseCase) List(ctx context.Context, tenantID string) ([]*entity.Role, error) {
|
|
roles, err := uc.roles.ListByTenant(ctx, tenantID)
|
|
if err != nil {
|
|
return nil, wrapRepoErr(err)
|
|
}
|
|
return roles, nil
|
|
}
|
|
|
|
func (uc *roleUseCase) Update(
|
|
ctx context.Context,
|
|
tenantID, id string,
|
|
param *dom.UpdateRoleParam,
|
|
) (*entity.Role, error) {
|
|
if param == nil {
|
|
return uc.Get(ctx, tenantID, id)
|
|
}
|
|
existing, err := uc.roles.GetByID(ctx, tenantID, id)
|
|
if err != nil {
|
|
return nil, wrapRepoErr(err)
|
|
}
|
|
if existing.IsSystem && param.Status != nil {
|
|
return nil, wrapRepoErr(domain.ErrRoleSystemImmutable)
|
|
}
|
|
update := &domrepo.RoleUpdate{}
|
|
if param.DisplayName != nil {
|
|
display := strings.TrimSpace(*param.DisplayName)
|
|
if display == "" {
|
|
return nil, errb.InputMissingRequired("display_name")
|
|
}
|
|
if len(display) > domain.RoleDisplayNameMax {
|
|
return nil, errb.InputInvalidRange("display_name too long")
|
|
}
|
|
update.DisplayName = &display
|
|
}
|
|
if param.Status != nil {
|
|
if !param.Status.IsValid() {
|
|
return nil, errb.InputInvalidFormat("invalid status")
|
|
}
|
|
s := param.Status.String()
|
|
update.Status = &s
|
|
}
|
|
role, err := uc.roles.Update(ctx, tenantID, id, update)
|
|
if err != nil {
|
|
return nil, wrapRepoErr(err, "update role")
|
|
}
|
|
return role, nil
|
|
}
|
|
|
|
func (uc *roleUseCase) Delete(ctx context.Context, tenantID, id string) error {
|
|
existing, err := uc.roles.GetByID(ctx, tenantID, id)
|
|
if err != nil {
|
|
return wrapRepoErr(err)
|
|
}
|
|
if existing.IsSystem {
|
|
return wrapRepoErr(domain.ErrRoleSystemImmutable)
|
|
}
|
|
assignments, err := uc.userRoles.ListByRole(ctx, tenantID, id)
|
|
if err != nil {
|
|
return wrapRepoErr(err, "check user roles")
|
|
}
|
|
if len(assignments) > 0 {
|
|
return errb.ResPreconditionFailed("role still has assignments")
|
|
}
|
|
if err := uc.rolePerms.DeleteByRole(ctx, tenantID, id); err != nil {
|
|
return wrapRepoErr(err, "delete role permissions")
|
|
}
|
|
if err := uc.roles.Delete(ctx, tenantID, id); err != nil {
|
|
return wrapRepoErr(err, "delete role")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func validateRoleKey(key string) error {
|
|
key = strings.TrimSpace(key)
|
|
if key == "" {
|
|
return errb.InputMissingRequired("key")
|
|
}
|
|
if len(key) < domain.RoleKeyMinLength || len(key) > domain.RoleKeyMaxLength {
|
|
return errb.InputInvalidRange("key length")
|
|
}
|
|
if !roleKeyPattern.MatchString(key) {
|
|
return wrapRepoErr(domain.ErrRoleKeyInvalid)
|
|
}
|
|
for _, prefix := range domain.ReservedRoleKeyPrefixes {
|
|
if strings.HasPrefix(key, prefix) {
|
|
return wrapRepoErr(domain.ErrRoleKeyReserved)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var _ dom.RoleUseCase = (*roleUseCase)(nil)
|