mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-19 14:12:52 +00:00
# Which Problems Are Solved Relational table's primary keys are part of the SQL table definition. However, by abstraction through the repository interfaces callers (service layer) have no clear understanding of the primary key definion. This might lead to confusion during implementation: - The repository makes a sanity check on required conditions before query execution and returns an error if the condition was not met. - When a Get method is executed, an error is returned if not exactly one row is found. Get replaces the legacy GetXxxByID methods we have on projections. However, a list of flexible conditions can be passed. - Operations like UPDATE and DELETE should often only affect a single row, identified by the primary key. If the primary key conditions are missing an error is returned. Because there is no "type safety" enforced here, the errors are returned during runtime. When all possible combinations of API arguments are not properly tested such errors are actually bugs in the experience of the end-user. (The API should do it's own input validation, but a programmer's error is easily made). # How the Problems Are Solved Add `PrimaryKeyColumns` and `PrimaryKeyCondition` to existing repository intefaces. # Additional Changes - Where reducers already used repositories, the PrimaryKeyConditions are used where possible. # Additional Context - Part of https://github.com/zitadel/zitadel/wiki/Decision-Log#expose-primary-key-definition-in-repository-interfaces --------- Co-authored-by: Marco A. <[email protected]>
246 lines
9.4 KiB
Go
246 lines
9.4 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 {
|
|
// PrimaryKeyCondition returns a filter on the primary key fields.
|
|
PrimaryKeyCondition(instanceID, userID string) database.Condition
|
|
// 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 {
|
|
Repository
|
|
|
|
userColumns
|
|
userConditions
|
|
userChanges
|
|
// Get returns a user based on the given condition.
|
|
Get(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) (*User, error)
|
|
// List returns a list of users based on the given condition.
|
|
List(ctx context.Context, client database.QueryExecutor, opts ...database.QueryOption) ([]*User, error)
|
|
// Create creates a new user.
|
|
Create(ctx context.Context, client database.QueryExecutor, user *User) error
|
|
// Delete removes users based on the given condition.
|
|
Delete(ctx context.Context, client database.QueryExecutor, 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, client database.QueryExecutor, condition database.Condition) (*Email, error)
|
|
// Update updates human users based on the given condition and changes.
|
|
Update(ctx context.Context, client database.QueryExecutor, 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, client database.QueryExecutor, 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)
|