package useCases import ( "RenewCMS/internal/domain/audit" "RenewCMS/internal/domain/builtintype" "RenewCMS/internal/domain/contenttype" "RenewCMS/internal/domain/role" "RenewCMS/internal/domain/user" "context" "errors" "slices" ) func builtInRole(roles role.Repository, name string) (role.Role, error) { found, err := roles.GetByName(name) if err != nil { return role.Role{}, err } if !found.BuiltIn { return role.Role{}, role.ErrRoleNotFound } return found, nil } func roleTarget(found role.Role) audit.Target { return audit.NewTarget(audit.TargetRole, found.ID, found.Name) } func allowedTypeKeys(contentTypes contenttype.Repository) ([]string, error) { types, err := contentTypes.List() if err != nil { return nil, err } keys := []string{builtintype.KeyArticle} for _, contentType := range types { keys = append(keys, contentType.Key) } slices.Sort(keys) return keys, nil } func ensureCovered(caller ContentCaller, contentTypes contenttype.Repository, candidate role.Role) error { typeKeys, err := allowedTypeKeys(contentTypes) if err != nil { return err } return ensureCoveredBy(caller, typeKeys, candidate) } func ensureCoveredBy(caller ContentCaller, typeKeys []string, candidate role.Role) error { return caller.Grants.Covers(candidate.GrantSet(), typeKeys) } func assignableRole(roles role.Repository, id uint32) (role.Role, error) { if id == 0 { return role.Role{}, role.ErrUnknownRole } found, err := roles.Get(id) if errors.Is(err, role.ErrRoleNotFound) { return role.Role{}, role.ErrUnknownRole } if err != nil { return role.Role{}, err } if found.Locked { return role.Role{}, role.ErrRoleLocked } return found, nil } func coveredAssignableRole(caller ContentCaller, roles role.Repository, contentTypes contenttype.Repository, id uint32) (role.Role, error) { found, err := assignableRole(roles, id) if err != nil { return role.Role{}, err } if err := ensureCovered(caller, contentTypes, found); err != nil { return role.Role{}, err } return found, nil } func ensureNotOwnRole(roles role.Repository, users user.Repository, caller ContentCaller, target role.Role) error { account, err := users.Get(caller.UserID) if err != nil { return err } own, err := resolveRole(roles, account) if errors.Is(err, role.ErrRoleNotFound) { return nil } if err != nil { return err } if own.ID == target.ID { return role.ErrOwnRole } return nil } func ensureEditable(target role.Role) error { if target.Locked { return role.ErrRoleLocked } return nil } func ensureNotBuiltIn(target role.Role) error { if target.BuiltIn { return role.ErrBuiltInRole } return nil } func ensureNameFree(roles role.Repository, name string, excludedID uint32) error { existing, err := roles.List() if err != nil { return err } for _, other := range existing { if other.ID != excludedID && role.SameName(other.Name, name) { return role.ErrRoleNameTaken } } return nil } func validateRoleGrants(contentTypes contenttype.Repository, grants []role.Grant) error { if err := role.ValidateGrants(false, grants); err != nil { return err } typeKeys, err := allowedTypeKeys(contentTypes) if err != nil { return err } return role.ValidateGrantTypes(grants, typeKeys) } func normalizedGrants(grants []role.Grant) []role.Grant { return role.Role{Grants: grants}.EffectiveGrants() } var writeRefusals = []error{role.ErrRoleConflict, role.ErrRoleHasMembers, role.ErrRoleNotFound, role.ErrRoleNameTaken, role.ErrInvalidReassign} func recordRoleWriteFailure(ctx context.Context, recorder audit.Recorder, event audit.Event, err error) { for _, refusal := range writeRefusals { if errors.Is(err, refusal) { return } } recordFailure(ctx, recorder, event, err) } func grantsParams(diff role.GrantDiff, members int) map[string]any { return map[string]any{ audit.ParamAdded: diff.Added, audit.ParamRemoved: diff.Removed, audit.ParamChanged: diff.Changed, audit.ParamMembers: members, } }