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

+45
View File
@@ -0,0 +1,45 @@
package role
type GrantChange struct {
Key string `json:"key"`
From Scope `json:"from"`
To Scope `json:"to"`
}
type GrantDiff struct {
Added []Grant `json:"added"`
Removed []Grant `json:"removed"`
Changed []GrantChange `json:"changed"`
}
func (d GrantDiff) Empty() bool {
return len(d.Added) == 0 && len(d.Removed) == 0 && len(d.Changed) == 0
}
func DiffGrants(before []Grant, after []Grant) GrantDiff {
diff := GrantDiff{Added: []Grant{}, Removed: []Grant{}, Changed: []GrantChange{}}
previous := scopesByKey(before)
current := scopesByKey(after)
for _, grant := range after {
held, present := previous[grant.Key]
if !present {
diff.Added = append(diff.Added, grant)
} else if held != grant.Scope {
diff.Changed = append(diff.Changed, GrantChange{Key: grant.Key, From: held, To: grant.Scope})
}
}
for _, grant := range before {
if _, present := current[grant.Key]; !present {
diff.Removed = append(diff.Removed, grant)
}
}
return diff
}
func scopesByKey(grants []Grant) map[string]Scope {
scopes := make(map[string]Scope, len(grants))
for _, grant := range grants {
scopes[grant.Key] = grant.Scope
}
return scopes
}