mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 21:52:54 +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]>
187 lines
6.0 KiB
Go
187 lines
6.0 KiB
Go
package database
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"golang.org/x/exp/constraints"
|
|
)
|
|
|
|
type wrappedValue[V Value] struct {
|
|
value V
|
|
fn function
|
|
}
|
|
|
|
func LowerValue[T Value](v T) wrappedValue[T] {
|
|
return wrappedValue[T]{value: v, fn: functionLower}
|
|
}
|
|
|
|
func SHA256Value[T Value](v T) wrappedValue[T] {
|
|
return wrappedValue[T]{value: v, fn: functionSHA256}
|
|
}
|
|
|
|
func (b wrappedValue[V]) WriteArg(builder *StatementBuilder) {
|
|
builder.Grow(len(b.fn) + 5)
|
|
builder.WriteString(string(b.fn))
|
|
builder.WriteRune('(')
|
|
builder.WriteArg(b.value)
|
|
builder.WriteRune(')')
|
|
}
|
|
|
|
var _ argWriter = (*wrappedValue[string])(nil)
|
|
|
|
type Value interface {
|
|
Boolean | Number | Text | Instruction | Bytes | any
|
|
}
|
|
|
|
//go:generate enumer -type NumberOperation,TextOperation,BytesOperation -linecomment -output ./operators_enumer.go
|
|
type Operation interface {
|
|
NumberOperation | TextOperation | BytesOperation
|
|
}
|
|
|
|
type Text interface {
|
|
~string | Bytes
|
|
}
|
|
|
|
// TextOperation are operations that can be performed on text values.
|
|
type TextOperation uint8
|
|
|
|
const (
|
|
// TextOperationEqual compares two strings for equality.
|
|
TextOperationEqual TextOperation = iota + 1 // =
|
|
// TextOperationNotEqual compares two strings for inequality.
|
|
TextOperationNotEqual // <>
|
|
// TextOperationStartsWith checks if the first string starts with the second.
|
|
TextOperationStartsWith // LIKE
|
|
// TextOperationContains checks if the first string contains the second
|
|
TextOperationContains // LIKE
|
|
// TextOperationEndsWith checks if the first string ends with the second.
|
|
TextOperationEndsWith // LIKE
|
|
|
|
// TextOperationContainsIgnoreCase checks if the first string contains the second ignoring case
|
|
TextOperationContainsIgnoreCase // LIKE
|
|
// TextOperationEndsWithIgnoreCase checks if the first string ends with the second ignoring case
|
|
TextOperationEndsWithIgnoreCase // LIKE
|
|
// TextOperationStartsWithIgnoreCase checks if the first string starts with the second ignoring case
|
|
TextOperationStartsWithIgnoreCase // LIKE
|
|
// TextOperationEqualIgnoreCase compares two strings for equality ignoring case
|
|
TextOperationEqualIgnoreCase // =
|
|
// TextOperationNotEqualIgnoreCase compares two strings for inequality ignoring case
|
|
TextOperationNotEqualIgnoreCase // <>
|
|
)
|
|
|
|
func (op TextOperation) IsIgnoreCaseOperation() bool {
|
|
return op == TextOperationContainsIgnoreCase ||
|
|
op == TextOperationEndsWithIgnoreCase ||
|
|
op == TextOperationStartsWithIgnoreCase ||
|
|
op == TextOperationEqualIgnoreCase ||
|
|
op == TextOperationNotEqualIgnoreCase
|
|
}
|
|
|
|
func (op TextOperation) Value() TextOperation {
|
|
switch op {
|
|
case TextOperationStartsWith, TextOperationContains, TextOperationEndsWith, TextOperationEqual, TextOperationNotEqual:
|
|
return op
|
|
case TextOperationContainsIgnoreCase:
|
|
return TextOperationContains
|
|
case TextOperationEndsWithIgnoreCase:
|
|
return TextOperationEndsWith
|
|
case TextOperationStartsWithIgnoreCase:
|
|
return TextOperationStartsWith
|
|
case TextOperationEqualIgnoreCase:
|
|
return TextOperationEqual
|
|
case TextOperationNotEqualIgnoreCase:
|
|
return TextOperationNotEqual
|
|
default:
|
|
panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op))
|
|
}
|
|
}
|
|
|
|
func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOperation, value any) {
|
|
switch op {
|
|
case TextOperationContains:
|
|
writeOperation[T](builder, col, op.String()+" '%' ||", value)
|
|
builder.WriteString(" || '%'")
|
|
|
|
case TextOperationEndsWith:
|
|
writeOperation[T](builder, col, op.String()+" '%' ||", value)
|
|
|
|
case TextOperationEqual, TextOperationNotEqual:
|
|
writeOperation[T](builder, col, op.String(), value)
|
|
case TextOperationStartsWith:
|
|
writeOperation[T](builder, col, op.String(), value)
|
|
builder.WriteString(" || '%'")
|
|
case TextOperationContainsIgnoreCase, TextOperationEndsWithIgnoreCase, TextOperationStartsWithIgnoreCase, TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
|
|
fallthrough
|
|
default:
|
|
panic(fmt.Sprintf("unexpected database.TextOperation: %#v", op))
|
|
}
|
|
}
|
|
|
|
type Number interface {
|
|
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
|
}
|
|
|
|
// NumberOperation are operations that can be performed on number values.
|
|
type NumberOperation uint8
|
|
|
|
const (
|
|
// NumberOperationEqual compares two numbers for equality.
|
|
NumberOperationEqual NumberOperation = iota + 1 // =
|
|
// NumberOperationNotEqual compares two numbers for inequality.
|
|
NumberOperationNotEqual // <>
|
|
// NumberOperationLessThan compares two numbers to check if the first is less than the second.
|
|
NumberOperationLessThan // <
|
|
// NumberOperationLessThanOrEqual compares two numbers to check if the first is less than or equal to the second.
|
|
NumberOperationLessThanOrEqual // <=
|
|
// NumberOperationGreaterThan compares two numbers to check if the first is greater than the second.
|
|
NumberOperationGreaterThan // >
|
|
// NumberOperationGreaterThanOrEqual compares two numbers to check if the first is greater than or equal to the second.
|
|
NumberOperationGreaterThanOrEqual // >=
|
|
)
|
|
|
|
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value any) {
|
|
writeOperation[T](builder, col, op.String(), value)
|
|
}
|
|
|
|
type Boolean interface {
|
|
~bool
|
|
}
|
|
|
|
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value any) {
|
|
writeOperation[T](builder, col, "=", value)
|
|
}
|
|
|
|
type Bytes interface {
|
|
~[]byte
|
|
}
|
|
|
|
// BytesOperation are operations that can be performed on bytea values.
|
|
type BytesOperation uint8
|
|
|
|
const (
|
|
BytesOperationEqual BytesOperation = iota + 1 // =
|
|
BytesOperationNotEqual // <>
|
|
)
|
|
|
|
func writeBytesOperation[T Bytes](builder *StatementBuilder, col Column, op BytesOperation, value any) {
|
|
writeOperation[T](builder, col, op.String(), value)
|
|
}
|
|
|
|
func writeOperation[V Value](builder *StatementBuilder, col Column, op string, value any) {
|
|
if op == "" {
|
|
panic("unsupported operation")
|
|
}
|
|
|
|
switch value.(type) {
|
|
case V, wrappedValue[V], *wrappedValue[V]:
|
|
default:
|
|
panic("unsupported value type")
|
|
}
|
|
col.WriteQualified(builder)
|
|
builder.WriteRune(' ')
|
|
builder.WriteString(op)
|
|
builder.WriteRune(' ')
|
|
builder.WriteArg(value)
|
|
}
|