mirror of
https://github.com/andwati/zitadel.git
synced 2026-11-10 08:05:12 +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"
|
||||
)
|
||||
|
||||
var _ domain.AuthorizationRepository = (*authorization)(nil)
|
||||
@@ -93,7 +94,7 @@ func (a authorization) Get(ctx context.Context, client database.QueryExecutor, o
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getOne[domain.Authorization](ctx, client, builder)
|
||||
return scanAuthorization(ctx, client, builder)
|
||||
}
|
||||
|
||||
// List implements [domain.AuthorizationRepository].
|
||||
@@ -106,7 +107,7 @@ func (a authorization) List(ctx context.Context, client database.QueryExecutor,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return getMany[domain.Authorization](ctx, client, builder)
|
||||
return scanAuthorizations(ctx, client, builder)
|
||||
}
|
||||
|
||||
func (a authorization) prepareQuery(opts []database.QueryOption) (*database.StatementBuilder, error) {
|
||||
@@ -196,6 +197,50 @@ func (a authorization) Delete(ctx context.Context, client database.QueryExecutor
|
||||
return client.Exec(ctx, builder.String(), builder.Args()...)
|
||||
}
|
||||
|
||||
// rawAuthorization is used for query collection because ARRAY_AGG roles 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 rawAuthorization struct {
|
||||
domain.Authorization
|
||||
Roles internaldb.TextArray[string] `db:"roles"`
|
||||
}
|
||||
|
||||
func (a *rawAuthorization) toDomain() *domain.Authorization {
|
||||
a.Authorization.Roles = []string(a.Roles)
|
||||
return &a.Authorization
|
||||
}
|
||||
|
||||
func scanAuthorization(ctx context.Context, client database.QueryExecutor, builder *database.StatementBuilder) (*domain.Authorization, error) {
|
||||
rows, err := client.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var authorization rawAuthorization
|
||||
if err = rows.(database.CollectableRows).CollectExactlyOneRow(&authorization); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return authorization.toDomain(), nil
|
||||
}
|
||||
|
||||
func scanAuthorizations(ctx context.Context, client database.QueryExecutor, builder *database.StatementBuilder) ([]*domain.Authorization, error) {
|
||||
rows, err := client.Query(ctx, builder.String(), builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var authorizations []rawAuthorization
|
||||
if err = rows.(database.CollectableRows).Collect(&authorizations); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*domain.Authorization, len(authorizations))
|
||||
for i := range authorizations {
|
||||
result[i] = authorizations[i].toDomain()
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user