mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-28 01:52:16 +00:00
This pull request introduces a new feature that allows adding, updating, and querying project grants The changes are primarily in the backend and include new database tables, repositories, and domain logic to support project grants. ## Changes * New `project_grants` table: A new table `zitadel.project_grants` is introduced to store project grants It includes columns for `instance_id`, `id`, `state`, `project_id`, `granted_org_id` and `granting_org_id`. * New `ProjectGrantRepository`: A new repository `ProjectGrantRepository` is created to handle all database operations for project grants. It provides methods to `Get`, `List`, `Create`, `Update`, and `Delete`. * New `project_grant_roles` table: A new table `zitadel.project_grant_roles` is introduced to store project grant roles It includes columns for `instance_id`, `grant_id`, `key`, `project_id` and `project_org_id`. * New `ProjectGrantRoleRepository`: A new repository `ProjectGrantRoleRepository` is created to handle all database operations for project grant roles. It provides methods to `Get`, `List`, `Add`, and `Delete`. * New `project_grant_relational_projection`: A new projection `project_grant_relational_projection` is added to update the `zitadel.project_grants` and `zitadel.project_grant_roles` tables based on events. It handles all project grant events. * Updated `ProjectRepository`: The `ProjectRepository` is updated to list granted projects. # Additional Context Closes https://github.com/zitadel/zitadel/issues/10766 --------- Co-authored-by: Stefan Benz <[email protected]> Co-authored-by: Marco A. <[email protected]> Co-authored-by: Silvan <[email protected]>
111 lines
3.1 KiB
Go
111 lines
3.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/zitadel/zitadel/backend/v3/domain"
|
|
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
|
)
|
|
|
|
func writeCondition(
|
|
builder *database.StatementBuilder,
|
|
condition database.Condition,
|
|
) {
|
|
if condition == nil {
|
|
return
|
|
}
|
|
builder.WriteString(" WHERE ")
|
|
condition.Write(builder)
|
|
}
|
|
|
|
func checkRestrictingColumns(
|
|
condition database.Condition,
|
|
requiredColumns ...database.Column,
|
|
) error {
|
|
for _, col := range requiredColumns {
|
|
if !condition.IsRestrictingColumn(col) {
|
|
return database.NewMissingConditionError(col)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// checkPKCondition checks if the Primary Key columns are part of the condition.
|
|
// This can ensure only a single row is affected by updates and deletes.
|
|
func checkPKCondition(
|
|
repo domain.Repository,
|
|
condition database.Condition,
|
|
) error {
|
|
return checkRestrictingColumns(
|
|
condition,
|
|
repo.PrimaryKeyColumns()...,
|
|
)
|
|
}
|
|
|
|
func getOne[Target any](ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*Target, error) {
|
|
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var target Target
|
|
if err := rows.(database.CollectableRows).CollectExactlyOneRow(&target); err != nil {
|
|
return nil, err
|
|
}
|
|
return &target, nil
|
|
}
|
|
|
|
func getMany[Target any](ctx context.Context, querier database.Querier, builder *database.StatementBuilder) ([]*Target, error) {
|
|
rows, err := querier.Query(ctx, builder.String(), builder.Args()...)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var targets []*Target
|
|
if err := rows.(database.CollectableRows).Collect(&targets); err != nil {
|
|
return nil, err
|
|
}
|
|
return targets, nil
|
|
}
|
|
|
|
type updatable interface {
|
|
PrimaryKeyColumns() []database.Column
|
|
UpdatedAtColumn() database.Column
|
|
qualifiedTableName() string
|
|
}
|
|
|
|
func updateOne[Target updatable](ctx context.Context, client database.QueryExecutor, target Target, condition database.Condition, changes ...database.Change) (int64, error) {
|
|
if len(changes) == 0 {
|
|
return 0, database.ErrNoChanges
|
|
}
|
|
if err := checkPKCondition(target, condition); err != nil {
|
|
return 0, err
|
|
}
|
|
if !database.Changes(changes).IsOnColumn(target.UpdatedAtColumn()) {
|
|
changes = append(changes, database.NewChange(target.UpdatedAtColumn(), database.NullInstruction))
|
|
}
|
|
builder := database.NewStatementBuilder("UPDATE ")
|
|
builder.WriteString(target.qualifiedTableName())
|
|
builder.WriteString(" SET ")
|
|
database.Changes(changes).Write(builder)
|
|
writeCondition(builder, condition)
|
|
|
|
return client.Exec(ctx, builder.String(), builder.Args()...)
|
|
}
|
|
|
|
type deletable interface {
|
|
PrimaryKeyColumns() []database.Column
|
|
qualifiedTableName() string
|
|
}
|
|
|
|
func deleteOne[Target deletable](ctx context.Context, client database.QueryExecutor, target Target, condition database.Condition) (int64, error) {
|
|
if err := checkPKCondition(target, condition); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
builder := database.NewStatementBuilder("DELETE FROM ")
|
|
builder.WriteString(target.qualifiedTableName())
|
|
builder.WriteRune(' ')
|
|
writeCondition(builder, condition)
|
|
|
|
return client.Exec(ctx, builder.String(), builder.Args()...)
|
|
}
|