mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: add administrator repository (#11803)
Introduces the `administrators` relational table and repository layer for the v3 backend, unifying instance, organization, project, and project-grant membership into a single scoped model. Closes #10220 ## Changes - **Domain**: `Administrator` struct, `AdministratorScope` enum, `AdministratorRepository` interface with typed CRUD, column, condition, and change helpers. Generated enumer and GoMock. - **Migration** (`016`): `zitadel.administrators` table with a generated `id`, scope-alignment check constraint, cascading foreign keys, and a child `administrator_roles` table. - **Repository**: CTE-based role mutations (`AddRole`, `RemoveRole`, `SetRoles`) for atomicity. Roles aggregated via `ARRAY_AGG`. - **Projection**: Event reducers for `member.added/changed/removed/cascade_removed` across all four admin scopes. - **Fixes**: `Rows.Close()` error wrapping; `TextArray` nil-safe scan + full `pgtype` array interface; `authorization` and `projectGrant` scan helpers fixed for `ARRAY_AGG` under `database/sql`; removed unused `ctx` parameter from `callReduce`. --------- Co-authored-by: Marco Ardizzone <[email protected]>
This commit is contained in:
co-authored by
Marco Ardizzone
parent
6d90a120a6
commit
f138082b64
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
internaldb "github.com/zitadel/zitadel/internal/database"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
@@ -28,7 +29,7 @@ func (p projectGrant) Get(ctx context.Context, client database.QueryExecutor, op
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getOne[domain.ProjectGrant](ctx, client, builder)
|
||||
return scanProjectGrant(ctx, client, builder)
|
||||
}
|
||||
|
||||
func (p projectGrant) List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*domain.ProjectGrant, error) {
|
||||
@@ -40,7 +41,7 @@ func (p projectGrant) List(ctx context.Context, client database.QueryExecutor, o
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getMany[domain.ProjectGrant](ctx, client, builder)
|
||||
return scanProjectGrants(ctx, client, builder)
|
||||
}
|
||||
|
||||
const insertProjectGrantRolesStmt = `WITH added_roles AS (
|
||||
@@ -307,3 +308,47 @@ func (p projectGrant) prepareQuery(opts []database.QueryOption) (*database.State
|
||||
|
||||
return builder, nil
|
||||
}
|
||||
|
||||
// rawProjectGrant is used for query collection because ARRAY_AGG role keys cannot be
|
||||
// scanned directly into the domain model's []string field when the client is backed
|
||||
// by [database/sql]. The intermediate TextArray field works with both [database/sql] and pgx.
|
||||
type rawProjectGrant struct {
|
||||
domain.ProjectGrant
|
||||
RoleKeys internaldb.TextArray[string] `db:"role_keys"`
|
||||
}
|
||||
|
||||
func (p *rawProjectGrant) toDomain() *domain.ProjectGrant {
|
||||
p.ProjectGrant.RoleKeys = []string(p.RoleKeys)
|
||||
return &p.ProjectGrant
|
||||
}
|
||||
|
||||
func scanProjectGrant(ctx context.Context, client database.QueryExecutor, builder *database.StatementBuilder) (*domain.ProjectGrant, error) {
|
||||
rows, err := client.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var projectGrant rawProjectGrant
|
||||
if err = rows.(database.CollectableRows).CollectExactlyOneRow(&projectGrant); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return projectGrant.toDomain(), nil
|
||||
}
|
||||
|
||||
func scanProjectGrants(ctx context.Context, client database.QueryExecutor, builder *database.StatementBuilder) ([]*domain.ProjectGrant, error) {
|
||||
rows, err := client.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var projectGrants []rawProjectGrant
|
||||
if err = rows.(database.CollectableRows).Collect(&projectGrants); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*domain.ProjectGrant, len(projectGrants))
|
||||
for i := range projectGrants {
|
||||
result[i] = projectGrants[i].toDomain()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user