feat: /v1/role list, get, create, update and delete. Reassign members on delete. Write-time validation (ValidateGrants, duplicates, typed keys against real content types). Name uniqueness (role_name_taken), role_locked, role_has_members, role_conflict on version mismatch. No-escalation: nobody grants a permission they don't hold, and roles.edit_owner_only guards the Owner role. Activity events for role changes. Typed-key exposure in /auth/me. Cleanup of typed rows when a content type is deleted. | Backend only. Will carry the deferred ListGrants decision.

This commit is contained in:
Florian Sylvain committed 2026-10-27 00:45:24 +01:00
1 parent a043f7f41a
commit 094df63a7f
51 files changed
+4822 -115

No files matched your search

@@ -0,0 +1,56 @@
package useCases
import (
"RenewCMS/internal/domain/audit"
"RenewCMS/internal/domain/contenttype"
"RenewCMS/internal/domain/role"
"RenewCMS/internal/domain/user"
"context"
)
type CreateRoleUseCase struct {
roles role.Repository
contentTypes contenttype.Repository
recorder audit.Recorder
viewer roleViewer
}
type CreateRoleCommand struct {
Caller ContentCaller
Name string
Description string
Grants []role.Grant
}
func NewCreateRoleUseCase(roles role.Repository, users user.Repository, contentTypes contenttype.Repository, recorder audit.Recorder) *CreateRoleUseCase {
return &CreateRoleUseCase{roles: roles, contentTypes: contentTypes, recorder: recorder, viewer: roleViewer{roles, users, contentTypes}}
}
func (u *CreateRoleUseCase) Create(ctx context.Context, command CreateRoleCommand) (RoleView, error) {
name, err := role.NormalizeName(command.Name)
if err != nil {
return RoleView{}, err
}
if err := validateRoleGrants(u.contentTypes, command.Grants); err != nil {
return RoleView{}, err
}
draft := role.Role{Name: name, Description: command.Description, Version: 1, Grants: normalizedGrants(command.Grants)}
if err := ensureCovered(command.Caller, u.contentTypes, draft); err != nil {
return RoleView{}, err
}
if err := ensureNameFree(u.roles, name, 0); err != nil {
return RoleView{}, err
}
event := audit.NewEvent(audit.ActionRoleCreate, audit.CategoryRoles)
event.Target = audit.Target{Type: audit.TargetRole, Name: name}
created, err := u.roles.Create(draft)
if err != nil {
recordRoleWriteFailure(ctx, u.recorder, event, err)
return RoleView{}, err
}
event.Target = roleTarget(created)
event.Params[audit.ParamAdded] = created.EffectiveGrants()
u.recorder.Record(ctx, event)
return u.viewer.one(created, 0)
}