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

+9 -9
View File
@@ -61,9 +61,9 @@ func (r *contentScopeRecorder) actions() []string {
type contentScopeRoleRepo struct {
role.Repository
gets int
getByNames int
listGrantsN int
gets int
getByNames int
lists int
}
func (c *contentScopeRoleRepo) Get(id uint32) (role.Role, error) {
@@ -76,13 +76,13 @@ func (c *contentScopeRoleRepo) GetByName(name string) (role.Role, error) {
return c.Repository.GetByName(name)
}
func (c *contentScopeRoleRepo) ListGrants(roleID uint32) ([]role.Grant, error) {
c.listGrantsN++
return c.Repository.ListGrants(roleID)
func (c *contentScopeRoleRepo) List() ([]role.Role, error) {
c.lists++
return c.Repository.List()
}
func (c *contentScopeRoleRepo) touched() int {
return c.gets + c.getByNames + c.listGrantsN
return c.gets + c.getByNames + c.lists
}
type contentScopeUserRepo struct {
@@ -219,7 +219,7 @@ func (w *contentScopeWorld) wire() {
c.AuthenticateAPIKeyUseCase = useCases.NewAuthenticateAPIKeyUseCase(w.keys, w.generator, w.userRepo, clock)
c.ResolveArticleSEOUseCase = useCases.NewResolveArticleSEOUseCase(w.articles, plainSites(), w.images)
c.CheckArticleSEOUseCase = useCases.NewCheckArticleSEOUseCase(w.articles, plainSites(), w.images)
c.CreateContentTypeUseCase = useCases.NewCreateContentTypeUseCase(w.typeRepo, clock)
c.CreateContentTypeUseCase = useCases.NewCreateContentTypeUseCase(w.typeRepo, w.roles, clock)
c.CreateEntryUseCase = useCases.NewCreateEntryUseCase(w.typeRepo, w.entries, w.images, clock)
c.GetEntryUseCase = useCases.NewGetEntryUseCase(w.typeRepo, w.entries, w.images)
c.ListEntriesUseCase = useCases.NewListEntriesUseCase(w.typeRepo, w.entries, w.images)
@@ -234,7 +234,7 @@ func (w *contentScopeWorld) do(t *testing.T, caller uint32, method string, path
}
func (w *contentScopeWorld) resetCounters() {
w.roleRepo.gets, w.roleRepo.getByNames, w.roleRepo.listGrantsN = 0, 0, 0
w.roleRepo.gets, w.roleRepo.getByNames, w.roleRepo.lists = 0, 0, 0
w.userRepo.gets = 0
w.typeRepo.lookups = 0
}