template-monorepo/internal/model/permission/usecase/role_usecase.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)