Files
zitadel/backend/v3/domain/user.go
T
61cab8878e feat(backend): state persisted objects (#9870)
This PR initiates the rework of Zitadel's backend to state-persisted
objects. This change is a step towards a more scalable and maintainable
architecture.

## Changes

* **New `/backend/v3` package**: A new package structure has been
introduced to house the reworked backend logic. This includes:
* `domain`: Contains the core business logic, commands, and repository
interfaces.
* `storage`: Implements the repository interfaces for database
interactions with new transactional tables.
  * `telemetry`: Provides logging and tracing capabilities.
* **Transactional Tables**: New database tables have been defined for
`instances`, `instance_domains`, `organizations`, and `org_domains`.
* **Projections**: New projections have been created to populate the new
relational tables from the existing event store, ensuring data
consistency during the migration.
* **Repositories**: New repositories provide an abstraction layer for
accessing and manipulating the data in the new tables.
* **Setup**: A new setup step for `TransactionalTables` has been added
to manage the database migrations for the new tables.

This PR lays the foundation for future work to fully transition to
state-persisted objects for these components, which will improve
performance and simplify data access patterns.

This PR initiates the rework of ZITADEL's backend to state-persisted
objects. This is a foundational step towards a new architecture that
will improve performance and maintainability.

The following objects are migrated from event-sourced aggregates to
state-persisted objects:

* Instances
  * incl. Domains
* Orgs
  * incl. Domains

The structure of the new backend implementation follows the software
architecture defined in this [wiki
page](https://github.com/zitadel/zitadel/wiki/Software-Architecturel).

This PR includes:

* The initial implementation of the new transactional repositories for
the objects listed above.
* Projections to populate the new relational tables from the existing
event store.
* Adjustments to the build and test process to accommodate the new
backend structure.

This is a work in progress and further changes will be made to complete
the migration.

---------

Co-authored-by: Copilot <[email protected]>
Co-authored-by: Iraq Jaber <[email protected]>
Co-authored-by: Iraq <[email protected]>
Co-authored-by: Tim Möhlmann <[email protected]>
2025-09-05 09:54:34 +01:00

242 lines
9.0 KiB
Go

package domain
import (
"context"
"time"
"github.com/zitadel/zitadel/backend/v3/storage/database"
)
// userColumns define all the columns of the user table.
type userColumns interface {
// InstanceIDColumn returns the column for the instance id field.
InstanceIDColumn() database.Column
// OrgIDColumn returns the column for the org id field.
OrgIDColumn() database.Column
// IDColumn returns the column for the id field.
IDColumn() database.Column
// UsernameColumn returns the column for the username field.
UsernameColumn() database.Column
// CreatedAtColumn returns the column for the created at field.
CreatedAtColumn() database.Column
// UpdatedAtColumn returns the column for the updated at field.
UpdatedAtColumn() database.Column
// DeletedAtColumn returns the column for the deleted at field.
DeletedAtColumn() database.Column
}
// userConditions define all the conditions for the user table.
type userConditions interface {
// InstanceIDCondition returns an equal filter on the instance id field.
InstanceIDCondition(instanceID string) database.Condition
// OrgIDCondition returns an equal filter on the org id field.
OrgIDCondition(orgID string) database.Condition
// IDCondition returns an equal filter on the id field.
IDCondition(userID string) database.Condition
// UsernameCondition returns a filter on the username field.
UsernameCondition(op database.TextOperation, username string) database.Condition
// CreatedAtCondition returns a filter on the created at field.
CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition
// UpdatedAtCondition returns a filter on the updated at field.
UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition
// DeletedAtCondition filters for deleted users is isDeleted is set to true otherwise only not deleted users must be filtered.
DeletedCondition(isDeleted bool) database.Condition
// DeletedAtCondition filters for deleted users based on the given parameters.
DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition
}
// userChanges define all the changes for the user table.
type userChanges interface {
// SetUsername sets the username column.
SetUsername(username string) database.Change
}
// UserRepository is the interface for the user repository.
type UserRepository interface {
userColumns
userConditions
userChanges
// Get returns a user based on the given condition.
Get(ctx context.Context, opts ...database.QueryOption) (*User, error)
// List returns a list of users based on the given condition.
List(ctx context.Context, opts ...database.QueryOption) ([]*User, error)
// Create creates a new user.
Create(ctx context.Context, user *User) error
// Delete removes users based on the given condition.
Delete(ctx context.Context, condition database.Condition) error
// Human returns the [HumanRepository].
Human() HumanRepository
// Machine returns the [MachineRepository].
Machine() MachineRepository
}
// humanColumns define all the columns of the human table which inherits the user table.
type humanColumns interface {
userColumns
// FirstNameColumn returns the column for the first name field.
FirstNameColumn() database.Column
// LastNameColumn returns the column for the last name field.
LastNameColumn() database.Column
// EmailAddressColumn returns the column for the email address field.
EmailAddressColumn() database.Column
// EmailVerifiedAtColumn returns the column for the email verified at field.
EmailVerifiedAtColumn() database.Column
// PhoneNumberColumn returns the column for the phone number field.
PhoneNumberColumn() database.Column
// PhoneVerifiedAtColumn returns the column for the phone verified at field.
PhoneVerifiedAtColumn() database.Column
}
// humanConditions define all the conditions for the human table which inherits the user table.
type humanConditions interface {
userConditions
// FirstNameCondition returns a filter on the first name field.
FirstNameCondition(op database.TextOperation, firstName string) database.Condition
// LastNameCondition returns a filter on the last name field.
LastNameCondition(op database.TextOperation, lastName string) database.Condition
// EmailAddressCondition returns a filter on the email address field.
EmailAddressCondition(op database.TextOperation, email string) database.Condition
// EmailVerifiedCondition returns a filter that checks if the email is verified or not.
EmailVerifiedCondition(isVerified bool) database.Condition
// EmailVerifiedAtCondition returns a filter on the email verified at field.
EmailVerifiedAtCondition(op database.NumberOperation, emailVerifiedAt time.Time) database.Condition
// PhoneNumberCondition returns a filter on the phone number field.
PhoneNumberCondition(op database.TextOperation, phoneNumber string) database.Condition
// PhoneVerifiedCondition returns a filter that checks if the phone is verified or not.
PhoneVerifiedCondition(isVerified bool) database.Condition
// PhoneVerifiedAtCondition returns a filter on the phone verified at field.
PhoneVerifiedAtCondition(op database.NumberOperation, phoneVerifiedAt time.Time) database.Condition
}
// humanChanges define all the changes for the human table which inherits the user table.
type humanChanges interface {
userChanges
// SetFirstName sets the first name field of the human.
SetFirstName(firstName string) database.Change
// SetLastName sets the last name field of the human.
SetLastName(lastName string) database.Change
// SetEmail sets the email address and verified field of the email
// if verifiedAt is nil the email is not verified
SetEmail(address string, verifiedAt *time.Time) database.Change
// SetEmailAddress sets the email address field of the email
SetEmailAddress(email string) database.Change
// SetEmailVerifiedAt sets the verified column of the email
// if at is zero the statement uses the database timestamp
SetEmailVerifiedAt(at time.Time) database.Change
// SetPhone sets the phone number and verified field
// if verifiedAt is nil the phone is not verified
SetPhone(number string, verifiedAt *time.Time) database.Change
// SetPhoneNumber sets the phone number field
SetPhoneNumber(phoneNumber string) database.Change
// SetPhoneVerifiedAt sets the verified field of the phone
// if at is zero the statement uses the database timestamp
SetPhoneVerifiedAt(at time.Time) database.Change
}
// HumanRepository is the interface for the human repository it inherits the user repository.
type HumanRepository interface {
humanColumns
humanConditions
humanChanges
// Get returns an email based on the given condition.
GetEmail(ctx context.Context, condition database.Condition) (*Email, error)
// Update updates human users based on the given condition and changes.
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
}
// machineColumns define all the columns of the machine table which inherits the user table.
type machineColumns interface {
userColumns
// DescriptionColumn returns the column for the description field.
DescriptionColumn() database.Column
}
// machineConditions define all the conditions for the machine table which inherits the user table.
type machineConditions interface {
userConditions
// DescriptionCondition returns a filter on the description field.
DescriptionCondition(op database.TextOperation, description string) database.Condition
}
// machineChanges define all the changes for the machine table which inherits the user table.
type machineChanges interface {
userChanges
// SetDescription sets the description field of the machine.
SetDescription(description string) database.Change
}
// MachineRepository is the interface for the machine repository it inherits the user repository.
type MachineRepository interface {
// Update updates machine users based on the given condition and changes.
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
machineColumns
machineConditions
machineChanges
}
// UserTraits is implemented by [Human] and [Machine].
type UserTraits interface {
Type() UserType
}
type UserType string
const (
UserTypeHuman UserType = "human"
UserTypeMachine UserType = "machine"
)
type User struct {
InstanceID string
OrgID string
ID string
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt time.Time
Username string
Traits UserTraits
}
type Human struct {
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Email *Email `json:"email,omitempty"`
Phone *Phone `json:"phone,omitempty"`
}
// Type implements [UserTraits].
func (h *Human) Type() UserType {
return UserTypeHuman
}
var _ UserTraits = (*Human)(nil)
type Email struct {
Address string `json:"address"`
VerifiedAt time.Time `json:"verifiedAt"`
}
type Phone struct {
Number string `json:"number"`
VerifiedAt time.Time `json:"verifiedAt"`
}
type Machine struct {
Description string `json:"description"`
}
// Type implements [UserTraits].
func (m *Machine) Type() UserType {
return UserTypeMachine
}
var _ UserTraits = (*Machine)(nil)