mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-25 00:32:15 +00:00
## Summary This PR introduces the relational user repository for backend v3, including schema, repository APIs, reducers/projections, and tests for both human and machine users. ## What changed * Added user-focused relational migrations (users, verifications, metadata, PATs, machine keys, passkeys, identity provider links) and related FK wiring. * Implemented/expanded repository domain contracts and storage implementations for human and machine user flows. * Added support for machine secret handling and aligned access token type modeling. * Extended user domain models (verifications, password/email/phone/TOTP state, passkeys, metadata, invite handling, identity provider links). * Updated projection/reduction coverage and integration tests for key user lifecycle events. * Regenerated and updated mocks/enums plus supporting database change/column utilities. ## Notes for reviewers * This PR is large because it establishes the foundational relational user storage layer and related test scaffolding. Sorry for that. ## Closes Closes #10218 Closes #10224 Closes #10225 Closes #10209 Closes #10802 --------- Co-authored-by: Iraq Jaber <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: Marco A. <[email protected]> Co-authored-by: Tim Möhlmann <[email protected]>
113 lines
3.1 KiB
Go
113 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 ")
|
|
if err := database.Changes(changes).Write(builder); err != nil {
|
|
return 0, err
|
|
}
|
|
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()...)
|
|
}
|