mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:17:35 +00:00
cleanup
This commit is contained in:
33
backend/v3/api/org/v2/org.go
Normal file
33
backend/v3/api/org/v2/org.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package orgv2
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
|
||||
)
|
||||
|
||||
func CreateOrg(ctx context.Context, req *org.AddOrganizationRequest) (resp *org.AddOrganizationResponse, err error) {
|
||||
cmd := domain.NewAddOrgCommand(
|
||||
req.GetName(),
|
||||
addOrgAdminToCommand(req.GetAdmins()...)...,
|
||||
)
|
||||
err = domain.Invoke(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &org.AddOrganizationResponse{
|
||||
OrganizationId: cmd.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func addOrgAdminToCommand(admins ...*org.AddOrganizationRequest_Admin) []*domain.AddMemberCommand {
|
||||
cmds := make([]*domain.AddMemberCommand, len(admins))
|
||||
for i, admin := range admins {
|
||||
cmds[i] = &domain.AddMemberCommand{
|
||||
UserID: admin.GetUserId(),
|
||||
Roles: admin.GetRoles(),
|
||||
}
|
||||
}
|
||||
return cmds
|
||||
}
|
19
backend/v3/api/org/v2/server.go
Normal file
19
backend/v3/api/org/v2/server.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package orgv2
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/backend/v3/telemetry/logging"
|
||||
"github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||
)
|
||||
|
||||
var (
|
||||
logger logging.Logger
|
||||
tracer tracing.Tracer
|
||||
)
|
||||
|
||||
func SetLogger(l logging.Logger) {
|
||||
logger = l
|
||||
}
|
||||
|
||||
func SetTracer(t tracing.Tracer) {
|
||||
tracer = t
|
||||
}
|
@@ -3,6 +3,8 @@ package domain
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
@@ -19,16 +21,43 @@ func (i *Instance) Keys(index string) (key []string) {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
type InstanceRepository interface {
|
||||
ByID(ctx context.Context, id string) (*Instance, error)
|
||||
Create(ctx context.Context, instance *Instance) error
|
||||
On(id string) InstanceOperation
|
||||
type instanceColumns interface {
|
||||
// IDColumn returns the column for the id field.
|
||||
IDColumn() database.Column
|
||||
// NameColumn returns the column for the name field.
|
||||
NameColumn() 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
|
||||
}
|
||||
|
||||
type InstanceOperation interface {
|
||||
AdminRepository
|
||||
Update(ctx context.Context, instance *Instance) error
|
||||
Delete(ctx context.Context) error
|
||||
type instanceConditions interface {
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(instanceID string) database.Condition
|
||||
// NameCondition returns a filter on the name field.
|
||||
NameCondition(op database.TextOperation, name string) database.Condition
|
||||
}
|
||||
|
||||
type instanceChanges interface {
|
||||
// SetName sets the name column.
|
||||
SetName(name string) database.Change
|
||||
}
|
||||
|
||||
type InstanceRepository interface {
|
||||
instanceColumns
|
||||
instanceConditions
|
||||
instanceChanges
|
||||
|
||||
Member() MemberRepository
|
||||
|
||||
Get(ctx context.Context, opts ...database.QueryOption) (*Instance, error)
|
||||
|
||||
Create(ctx context.Context, instance *Instance) error
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||
Delete(ctx context.Context, condition database.Condition) error
|
||||
}
|
||||
|
||||
type CreateInstance struct {
|
||||
|
@@ -3,33 +3,95 @@ package domain
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type OrgState uint8
|
||||
|
||||
const (
|
||||
OrgStateActive OrgState = iota + 1
|
||||
OrgStateInactive
|
||||
)
|
||||
|
||||
type Org struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
State OrgState `json:"state"`
|
||||
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type orgColumns interface {
|
||||
// InstanceIDColumn returns the column for the instance id field.
|
||||
InstanceIDColumn() database.Column
|
||||
// IDColumn returns the column for the id field.
|
||||
IDColumn() database.Column
|
||||
// NameColumn returns the column for the name field.
|
||||
NameColumn() database.Column
|
||||
// StateColumn returns the column for the state field.
|
||||
StateColumn() 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
|
||||
}
|
||||
|
||||
type orgConditions interface {
|
||||
// InstanceIDCondition returns an equal filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(orgID string) database.Condition
|
||||
// NameCondition returns a filter on the name field.
|
||||
NameCondition(op database.TextOperation, name string) database.Condition
|
||||
// StateCondition returns a filter on the state field.
|
||||
StateCondition(op database.NumberOperation, state OrgState) database.Condition
|
||||
}
|
||||
|
||||
type orgChanges interface {
|
||||
// SetName sets the name column.
|
||||
SetName(name string) database.Change
|
||||
// SetState sets the state column.
|
||||
SetState(state OrgState) database.Change
|
||||
}
|
||||
|
||||
type OrgRepository interface {
|
||||
ByID(ctx context.Context, orgID string) (*Org, error)
|
||||
orgColumns
|
||||
orgConditions
|
||||
orgChanges
|
||||
|
||||
// Member returns the admin repository.
|
||||
Member() MemberRepository
|
||||
// Domain returns the domain repository.
|
||||
Domain() DomainRepository
|
||||
|
||||
// Get returns an org based on the given condition.
|
||||
Get(ctx context.Context, opts ...database.QueryOption) (*Org, error)
|
||||
// List returns a list of orgs based on the given condition.
|
||||
List(ctx context.Context, opts ...database.QueryOption) ([]*Org, error)
|
||||
// Create creates a new org.
|
||||
Create(ctx context.Context, org *Org) error
|
||||
On(id string) OrgOperation
|
||||
// Delete removes orgs based on the given condition.
|
||||
Delete(ctx context.Context, condition database.Condition) error
|
||||
// Update executes the given changes based on the given condition.
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) error
|
||||
}
|
||||
|
||||
type OrgOperation interface {
|
||||
AdminRepository
|
||||
MemberRepository
|
||||
DomainRepository
|
||||
Update(ctx context.Context, org *Org) error
|
||||
Delete(ctx context.Context) error
|
||||
}
|
||||
|
||||
type AdminRepository interface {
|
||||
AddAdmin(ctx context.Context, userID string, roles []string) error
|
||||
SetAdminRoles(ctx context.Context, userID string, roles []string) error
|
||||
RemoveAdmin(ctx context.Context, userID string) error
|
||||
type MemberRepository interface {
|
||||
AddMember(ctx context.Context, orgID, userID string, roles []string) error
|
||||
SetMemberRoles(ctx context.Context, orgID, userID string, roles []string) error
|
||||
RemoveMember(ctx context.Context, orgID, userID string) error
|
||||
}
|
||||
|
||||
type DomainRepository interface {
|
||||
|
@@ -2,15 +2,17 @@ package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/eventstore"
|
||||
)
|
||||
|
||||
type AddOrgCommand struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Admins []AddAdminCommand `json:"admins"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Admins []*AddMemberCommand `json:"admins"`
|
||||
}
|
||||
|
||||
func NewAddOrgCommand(name string, admins ...AddAdminCommand) *AddOrgCommand {
|
||||
func NewAddOrgCommand(name string, admins ...*AddMemberCommand) *AddOrgCommand {
|
||||
return &AddOrgCommand{
|
||||
Name: name,
|
||||
Admins: admins,
|
||||
@@ -39,11 +41,31 @@ func (cmd *AddOrgCommand) Execute(ctx context.Context, opts *CommandOpts) (err e
|
||||
return err
|
||||
}
|
||||
|
||||
for _, admin := range cmd.Admins {
|
||||
admin.orgID = cmd.ID
|
||||
if err = opts.Invoke(ctx, admin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Events implements [eventer].
|
||||
func (cmd *AddOrgCommand) Events() []*eventstore.Event {
|
||||
return []*eventstore.Event{
|
||||
{
|
||||
AggregateType: "org",
|
||||
AggregateID: cmd.ID,
|
||||
Type: "org.added",
|
||||
Payload: cmd,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*AddOrgCommand)(nil)
|
||||
_ eventer = (*AddOrgCommand)(nil)
|
||||
)
|
||||
|
||||
func (cmd *AddOrgCommand) ensureID() (err error) {
|
||||
@@ -54,21 +76,36 @@ func (cmd *AddOrgCommand) ensureID() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
type AddAdminCommand struct {
|
||||
type AddMemberCommand struct {
|
||||
orgID string
|
||||
UserID string `json:"userId"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
// Execute implements Commander.
|
||||
func (a *AddAdminCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
func (a *AddMemberCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
close, err := opts.EnsureTx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { err = close(ctx, err) }()
|
||||
return nil
|
||||
|
||||
return orgRepo(opts.DB).Member().AddMember(ctx, a.orgID, a.UserID, a.Roles)
|
||||
}
|
||||
|
||||
// Events implements [eventer].
|
||||
func (a *AddMemberCommand) Events() []*eventstore.Event {
|
||||
return []*eventstore.Event{
|
||||
{
|
||||
AggregateType: "org",
|
||||
AggregateID: a.UserID,
|
||||
Type: "member.added",
|
||||
Payload: a,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*AddAdminCommand)(nil)
|
||||
_ Commander = (*AddMemberCommand)(nil)
|
||||
_ eventer = (*AddMemberCommand)(nil)
|
||||
)
|
||||
|
@@ -3,11 +3,11 @@
|
||||
//
|
||||
// Generated by this command:
|
||||
//
|
||||
// mockgen -typed -package mock -destination ./mock/database.mock.go github.com/zitadel/zitadel/backend/v3/storage/database Pool,Client,Row,Rows,Transaction
|
||||
// mockgen -typed -package dbmock -destination ./dbmock/database.mock.go github.com/zitadel/zitadel/backend/v3/storage/database Pool,Client,Row,Rows,Transaction
|
||||
//
|
||||
|
||||
// Package mock is a generated GoMock package.
|
||||
package mock
|
||||
// Package dbmock is a generated GoMock package.
|
||||
package dbmock
|
||||
|
||||
import (
|
||||
context "context"
|
@@ -8,8 +8,8 @@ import (
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/storage/database/dialect/postgres"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dialect/postgres"
|
||||
)
|
||||
|
||||
type Hook struct {
|
||||
|
@@ -1,3 +1,3 @@
|
||||
package database
|
||||
|
||||
//go:generate mockgen -typed -package mock -destination ./mock/database.mock.go github.com/zitadel/zitadel/backend/v3/storage/database Pool,Client,Row,Rows,Transaction
|
||||
//go:generate mockgen -typed -package dbmock -destination ./dbmock/database.mock.go github.com/zitadel/zitadel/backend/v3/storage/database Pool,Client,Row,Rows,Transaction
|
||||
|
@@ -49,7 +49,7 @@ func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOp
|
||||
case TextOperationEqual, TextOperationNotEqual:
|
||||
col.Write(builder)
|
||||
builder.WriteString(textOperations[op])
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
case TextOperationEqualIgnoreCase, TextOperationNotEqualIgnoreCase:
|
||||
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
||||
ignoreCaseCol.WriteIgnoreCase(builder)
|
||||
@@ -60,12 +60,12 @@ func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOp
|
||||
}
|
||||
builder.WriteString(textOperations[op])
|
||||
builder.WriteString("LOWER(")
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
builder.WriteString(")")
|
||||
case TextOperationStartsWith:
|
||||
col.Write(builder)
|
||||
builder.WriteString(textOperations[op])
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
builder.WriteString(" || '%'")
|
||||
case TextOperationStartsWithIgnoreCase:
|
||||
if ignoreCaseCol, ok := col.(ignoreCaseColumn); ok {
|
||||
@@ -77,7 +77,7 @@ func writeTextOperation[T Text](builder *StatementBuilder, col Column, op TextOp
|
||||
}
|
||||
builder.WriteString(textOperations[op])
|
||||
builder.WriteString("LOWER(")
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
builder.WriteString(")")
|
||||
builder.WriteString(" || '%'")
|
||||
default:
|
||||
@@ -118,7 +118,7 @@ var numberOperations = map[NumberOperation]string{
|
||||
func writeNumberOperation[T Number](builder *StatementBuilder, col Column, op NumberOperation, value T) {
|
||||
col.Write(builder)
|
||||
builder.WriteString(numberOperations[op])
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
}
|
||||
|
||||
type Boolean interface {
|
||||
@@ -135,5 +135,5 @@ const (
|
||||
func writeBooleanOperation[T Boolean](builder *StatementBuilder, col Column, value T) {
|
||||
col.Write(builder)
|
||||
builder.WriteString(" IS ")
|
||||
builder.WriteString(builder.AppendArg(value))
|
||||
builder.WriteArg(value)
|
||||
}
|
||||
|
@@ -1,160 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
)
|
||||
|
||||
type field interface {
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
type fieldDescriptor struct {
|
||||
schema string
|
||||
table string
|
||||
name string
|
||||
}
|
||||
|
||||
func (f fieldDescriptor) String() string {
|
||||
return f.schema + "." + f.table + "." + f.name
|
||||
}
|
||||
|
||||
type ignoreCaseFieldDescriptor struct {
|
||||
fieldDescriptor
|
||||
fieldNameSuffix string
|
||||
}
|
||||
|
||||
func (f ignoreCaseFieldDescriptor) String() string {
|
||||
return f.fieldDescriptor.String() + f.fieldNameSuffix
|
||||
}
|
||||
|
||||
type textFieldDescriptor struct {
|
||||
field
|
||||
isIgnoreCase bool
|
||||
}
|
||||
|
||||
type clause[Op domain.Operation] struct {
|
||||
field field
|
||||
op Op
|
||||
}
|
||||
|
||||
const (
|
||||
schema = "zitadel"
|
||||
userTable = "users"
|
||||
)
|
||||
|
||||
var userFields = map[domain.UserField]field{
|
||||
domain.UserFieldInstanceID: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "instance_id",
|
||||
},
|
||||
domain.UserFieldOrgID: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "org_id",
|
||||
},
|
||||
domain.UserFieldID: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "id",
|
||||
},
|
||||
domain.UserFieldUsername: textFieldDescriptor{
|
||||
field: ignoreCaseFieldDescriptor{
|
||||
fieldDescriptor: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "username",
|
||||
},
|
||||
fieldNameSuffix: "_lower",
|
||||
},
|
||||
},
|
||||
domain.UserHumanFieldEmail: textFieldDescriptor{
|
||||
field: ignoreCaseFieldDescriptor{
|
||||
fieldDescriptor: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "email",
|
||||
},
|
||||
fieldNameSuffix: "_lower",
|
||||
},
|
||||
},
|
||||
domain.UserHumanFieldEmailVerified: fieldDescriptor{
|
||||
schema: schema,
|
||||
table: userTable,
|
||||
name: "email_is_verified",
|
||||
},
|
||||
}
|
||||
|
||||
type textClause[V domain.Text] struct {
|
||||
clause[domain.TextOperation]
|
||||
value V
|
||||
}
|
||||
|
||||
var textOp map[domain.TextOperation]string = map[domain.TextOperation]string{
|
||||
domain.TextOperationEqual: " = ",
|
||||
domain.TextOperationNotEqual: " <> ",
|
||||
domain.TextOperationStartsWith: " LIKE ",
|
||||
domain.TextOperationStartsWithIgnoreCase: " LIKE ",
|
||||
}
|
||||
|
||||
func (tc textClause[V]) Write(stmt *statement) {
|
||||
placeholder := stmt.appendArg(tc.value)
|
||||
var (
|
||||
left, right string
|
||||
)
|
||||
switch tc.clause.op {
|
||||
case domain.TextOperationEqual:
|
||||
left = tc.clause.field.String()
|
||||
right = placeholder
|
||||
case domain.TextOperationNotEqual:
|
||||
left = tc.clause.field.String()
|
||||
right = placeholder
|
||||
case domain.TextOperationStartsWith:
|
||||
left = tc.clause.field.String()
|
||||
right = placeholder + "%"
|
||||
case domain.TextOperationStartsWithIgnoreCase:
|
||||
left = tc.clause.field.String()
|
||||
if _, ok := tc.clause.field.(ignoreCaseFieldDescriptor); !ok {
|
||||
left = "LOWER(" + left + ")"
|
||||
}
|
||||
right = "LOWER(" + placeholder + "%)"
|
||||
}
|
||||
|
||||
stmt.builder.WriteString(left)
|
||||
stmt.builder.WriteString(textOp[tc.clause.op])
|
||||
stmt.builder.WriteString(right)
|
||||
}
|
||||
|
||||
type boolClause[V domain.Bool] struct {
|
||||
clause[domain.BoolOperation]
|
||||
value V
|
||||
}
|
||||
|
||||
func (bc boolClause[V]) Write(stmt *statement) {
|
||||
if !bc.value {
|
||||
stmt.builder.WriteString("NOT ")
|
||||
}
|
||||
stmt.builder.WriteString(bc.clause.field.String())
|
||||
}
|
||||
|
||||
type numberClause[V domain.Number] struct {
|
||||
clause[domain.NumberOperation]
|
||||
value V
|
||||
}
|
||||
|
||||
var numberOp map[domain.NumberOperation]string = map[domain.NumberOperation]string{
|
||||
domain.NumberOperationEqual: " = ",
|
||||
domain.NumberOperationNotEqual: " <> ",
|
||||
domain.NumberOperationLessThan: " < ",
|
||||
domain.NumberOperationLessThanOrEqual: " <= ",
|
||||
domain.NumberOperationGreaterThan: " > ",
|
||||
domain.NumberOperationGreaterThanOrEqual: " >= ",
|
||||
}
|
||||
|
||||
func (nc numberClause[V]) Write(stmt *statement) {
|
||||
stmt.builder.WriteString(nc.clause.field.String())
|
||||
stmt.builder.WriteString(numberOp[nc.clause.op])
|
||||
stmt.builder.WriteString(stmt.appendArg(nc.value))
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
)
|
||||
|
||||
type cryptoRepo struct {
|
||||
database.QueryExecutor
|
||||
}
|
||||
|
||||
func Crypto(db database.QueryExecutor) domain.CryptoRepository {
|
||||
return &cryptoRepo{
|
||||
QueryExecutor: db,
|
||||
}
|
||||
}
|
||||
|
||||
const getEncryptionConfigQuery = "SELECT" +
|
||||
" length" +
|
||||
", expiry" +
|
||||
", should_include_lower_letters" +
|
||||
", should_include_upper_letters" +
|
||||
", should_include_digits" +
|
||||
", should_include_symbols" +
|
||||
" FROM encryption_config"
|
||||
|
||||
func (repo *cryptoRepo) GetEncryptionConfig(ctx context.Context) (*crypto.GeneratorConfig, error) {
|
||||
var config crypto.GeneratorConfig
|
||||
row := repo.QueryRow(ctx, getEncryptionConfigQuery)
|
||||
err := row.Scan(
|
||||
&config.Length,
|
||||
&config.Expiry,
|
||||
&config.IncludeLowerLetters,
|
||||
&config.IncludeUpperLetters,
|
||||
&config.IncludeDigits,
|
||||
&config.IncludeSymbols,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
@@ -1,7 +0,0 @@
|
||||
// Repository package provides the database repository for the application.
|
||||
// It contains the implementation of the [repository pattern](https://martinfowler.com/eaaCatalog/repository.html) for the database.
|
||||
|
||||
// funcs which need to interact with the database should create interfaces which are implemented by the
|
||||
// [query] and [exec] structs respectively their factory methods [Query] and [Execute]. The [query] struct is used for read operations, while the [exec] struct is used for write operations.
|
||||
|
||||
package repository
|
@@ -1,54 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type instance struct {
|
||||
database.QueryExecutor
|
||||
}
|
||||
|
||||
func Instance(client database.QueryExecutor) domain.InstanceRepository {
|
||||
return &instance{QueryExecutor: client}
|
||||
}
|
||||
|
||||
func (i *instance) ByID(ctx context.Context, id string) (*domain.Instance, error) {
|
||||
var instance domain.Instance
|
||||
err := i.QueryExecutor.QueryRow(ctx, `SELECT id, name, created_at, updated_at, deleted_at FROM instances WHERE id = $1`, id).Scan(
|
||||
&instance.ID,
|
||||
&instance.Name,
|
||||
&instance.CreatedAt,
|
||||
&instance.UpdatedAt,
|
||||
&instance.DeletedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &instance, nil
|
||||
}
|
||||
|
||||
const createInstanceStmt = `INSERT INTO instances (id, name) VALUES ($1, $2) RETURNING created_at, updated_at`
|
||||
|
||||
// Create implements [domain.InstanceRepository].
|
||||
func (i *instance) Create(ctx context.Context, instance *domain.Instance) error {
|
||||
return i.QueryExecutor.QueryRow(ctx, createInstanceStmt,
|
||||
instance.ID,
|
||||
instance.Name,
|
||||
).Scan(
|
||||
&instance.CreatedAt,
|
||||
&instance.UpdatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
// On implements [domain.InstanceRepository].
|
||||
func (i *instance) On(id string) domain.InstanceOperation {
|
||||
return &instanceOperation{
|
||||
QueryExecutor: i.QueryExecutor,
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
var _ domain.InstanceRepository = (*instance)(nil)
|
@@ -1,52 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type instanceOperation struct {
|
||||
database.QueryExecutor
|
||||
id string
|
||||
}
|
||||
|
||||
const addInstanceAdminStmt = `INSERT INTO instance_admins (instance_id, user_id, roles) VALUES ($1, $2, $3)`
|
||||
|
||||
// AddAdmin implements [domain.InstanceOperation].
|
||||
func (i *instanceOperation) AddAdmin(ctx context.Context, userID string, roles []string) error {
|
||||
return i.QueryExecutor.Exec(ctx, addInstanceAdminStmt, i.id, userID, roles)
|
||||
}
|
||||
|
||||
// Delete implements [domain.InstanceOperation].
|
||||
func (i *instanceOperation) Delete(ctx context.Context) error {
|
||||
return i.QueryExecutor.Exec(ctx, `DELETE FROM instances WHERE id = $1`, i.id)
|
||||
}
|
||||
|
||||
const removeInstanceAdminStmt = `DELETE FROM instance_admins WHERE instance_id = $1 AND user_id = $2`
|
||||
|
||||
// RemoveAdmin implements [domain.InstanceOperation].
|
||||
func (i *instanceOperation) RemoveAdmin(ctx context.Context, userID string) error {
|
||||
return i.QueryExecutor.Exec(ctx, removeInstanceAdminStmt, i.id, userID)
|
||||
}
|
||||
|
||||
const setInstanceAdminRolesStmt = `UPDATE instance_admins SET roles = $1 WHERE instance_id = $2 AND user_id = $3`
|
||||
|
||||
// SetAdminRoles implements [domain.InstanceOperation].
|
||||
func (i *instanceOperation) SetAdminRoles(ctx context.Context, userID string, roles []string) error {
|
||||
return i.QueryExecutor.Exec(ctx, setInstanceAdminRolesStmt, roles, i.id, userID)
|
||||
}
|
||||
|
||||
const updateInstanceStmt = `UPDATE instances SET name = $1, updated_at = $2 WHERE id = $3 RETURNING updated_at`
|
||||
|
||||
// Update implements [domain.InstanceOperation].
|
||||
func (i *instanceOperation) Update(ctx context.Context, instance *domain.Instance) error {
|
||||
return i.QueryExecutor.QueryRow(ctx, updateInstanceStmt,
|
||||
instance.Name,
|
||||
instance.UpdatedAt,
|
||||
i.id,
|
||||
).Scan(&instance.UpdatedAt)
|
||||
}
|
||||
|
||||
var _ domain.InstanceOperation = (*instanceOperation)(nil)
|
136
backend/v3/storage/database/repository/org.go
Normal file
136
backend/v3/storage/database/repository/org.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
type org struct {
|
||||
repository
|
||||
}
|
||||
|
||||
func OrgRepository(client database.QueryExecutor) domain.OrgRepository {
|
||||
return &org{
|
||||
repository: repository{
|
||||
client: client,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Create implements [domain.OrgRepository].
|
||||
func (o *org) Create(ctx context.Context, org *domain.Org) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Delete implements [domain.OrgRepository].
|
||||
func (o *org) Delete(ctx context.Context, condition database.Condition) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Get implements [domain.OrgRepository].
|
||||
func (o *org) Get(ctx context.Context, opts ...database.QueryOption) (*domain.Org, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// List implements [domain.OrgRepository].
|
||||
func (o *org) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.Org, error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Update implements [domain.OrgRepository].
|
||||
func (o *org) Update(ctx context.Context, condition database.Condition, changes ...database.Change) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
func (o *org) Member() domain.MemberRepository {
|
||||
return &orgMember{o}
|
||||
}
|
||||
|
||||
func (o *org) Domain() domain.DomainRepository {
|
||||
return &orgDomain{o}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// SetName implements [domain.orgChanges].
|
||||
func (o *org) SetName(name string) database.Change {
|
||||
return database.NewChange(o.NameColumn(), name)
|
||||
}
|
||||
|
||||
// SetState implements [domain.orgChanges].
|
||||
func (o *org) SetState(state domain.OrgState) database.Change {
|
||||
return database.NewChange(o.StateColumn(), state)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// IDCondition implements [domain.orgConditions].
|
||||
func (o *org) IDCondition(orgID string) database.Condition {
|
||||
return database.NewTextCondition(o.IDColumn(), database.TextOperationEqual, orgID)
|
||||
}
|
||||
|
||||
// InstanceIDCondition implements [domain.orgConditions].
|
||||
func (o *org) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(o.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// NameCondition implements [domain.orgConditions].
|
||||
func (o *org) NameCondition(op database.TextOperation, name string) database.Condition {
|
||||
return database.NewTextCondition(o.NameColumn(), op, name)
|
||||
}
|
||||
|
||||
// StateCondition implements [domain.orgConditions].
|
||||
func (o *org) StateCondition(op database.NumberOperation, state domain.OrgState) database.Condition {
|
||||
return database.NewNumberCondition(o.StateColumn(), op, state)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// CreatedAtColumn implements [domain.orgColumns].
|
||||
func (o *org) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn("created_at")
|
||||
}
|
||||
|
||||
// DeletedAtColumn implements [domain.orgColumns].
|
||||
func (o *org) DeletedAtColumn() database.Column {
|
||||
return database.NewColumn("deleted_at")
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.orgColumns].
|
||||
func (o *org) IDColumn() database.Column {
|
||||
return database.NewColumn("id")
|
||||
}
|
||||
|
||||
// InstanceIDColumn implements [domain.orgColumns].
|
||||
func (o *org) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn("instance_id")
|
||||
}
|
||||
|
||||
// NameColumn implements [domain.orgColumns].
|
||||
func (o *org) NameColumn() database.Column {
|
||||
return database.NewColumn("name")
|
||||
}
|
||||
|
||||
// StateColumn implements [domain.orgColumns].
|
||||
func (o *org) StateColumn() database.Column {
|
||||
return database.NewColumn("state")
|
||||
}
|
||||
|
||||
// UpdatedAtColumn implements [domain.orgColumns].
|
||||
func (o *org) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn("updated_at")
|
||||
}
|
||||
|
||||
var _ domain.OrgRepository = (*org)(nil)
|
28
backend/v3/storage/database/repository/org_domain.go
Normal file
28
backend/v3/storage/database/repository/org_domain.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
)
|
||||
|
||||
type orgDomain struct {
|
||||
*org
|
||||
}
|
||||
|
||||
// AddDomain implements [domain.DomainRepository].
|
||||
func (o *orgDomain) AddDomain(ctx context.Context, domain string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// RemoveDomain implements [domain.DomainRepository].
|
||||
func (o *orgDomain) RemoveDomain(ctx context.Context, domain string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// SetDomainVerified implements [domain.DomainRepository].
|
||||
func (o *orgDomain) SetDomainVerified(ctx context.Context, domain string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
var _ domain.DomainRepository = (*orgDomain)(nil)
|
28
backend/v3/storage/database/repository/org_member.go
Normal file
28
backend/v3/storage/database/repository/org_member.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
)
|
||||
|
||||
type orgMember struct {
|
||||
*org
|
||||
}
|
||||
|
||||
// AddMember implements [domain.MemberRepository].
|
||||
func (o *orgMember) AddMember(ctx context.Context, orgID string, userID string, roles []string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// RemoveMember implements [domain.MemberRepository].
|
||||
func (o *orgMember) RemoveMember(ctx context.Context, orgID string, userID string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// SetMemberRoles implements [domain.MemberRepository].
|
||||
func (o *orgMember) SetMemberRoles(ctx context.Context, orgID string, userID string, roles []string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
var _ domain.MemberRepository = (*orgMember)(nil)
|
@@ -1,17 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type query struct{ database.Querier }
|
||||
|
||||
func Query(querier database.Querier) *query {
|
||||
return &query{Querier: querier}
|
||||
}
|
||||
|
||||
type executor struct{ database.Executor }
|
||||
|
||||
func Execute(exec database.Executor) *executor {
|
||||
return &executor{Executor: exec}
|
||||
}
|
8
backend/v3/storage/database/repository/repository.go
Normal file
8
backend/v3/storage/database/repository/repository.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package repository
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
type repository struct {
|
||||
builder database.StatementBuilder
|
||||
client database.QueryExecutor
|
||||
}
|
@@ -1,21 +0,0 @@
|
||||
package repository
|
||||
|
||||
import "strings"
|
||||
|
||||
type statement struct {
|
||||
builder strings.Builder
|
||||
args []any
|
||||
}
|
||||
|
||||
func (s *statement) appendArg(arg any) (placeholder string) {
|
||||
s.args = append(s.args, arg)
|
||||
return "$" + string(len(s.args))
|
||||
}
|
||||
|
||||
func (s *statement) appendArgs(args ...any) (placeholders []string) {
|
||||
placeholders = make([]string, len(args))
|
||||
for i, arg := range args {
|
||||
placeholders[i] = s.appendArg(arg)
|
||||
}
|
||||
return placeholders
|
||||
}
|
@@ -1,43 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Column[T any] interface {
|
||||
fmt.Stringer
|
||||
statementApplier[T]
|
||||
scanner(t *T) any
|
||||
}
|
||||
|
||||
type columnDescriptor[T any] struct {
|
||||
name string
|
||||
scan func(*T) any
|
||||
}
|
||||
|
||||
func (cd columnDescriptor[T]) scanner(t *T) any {
|
||||
return cd.scan(t)
|
||||
}
|
||||
|
||||
// Apply implements [Column].
|
||||
func (f columnDescriptor[T]) Apply(stmt *statement[T]) {
|
||||
stmt.builder.WriteString(stmt.columnPrefix())
|
||||
stmt.builder.WriteString(f.String())
|
||||
}
|
||||
|
||||
// String implements [Column].
|
||||
func (f columnDescriptor[T]) String() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
var _ Column[any] = (*columnDescriptor[any])(nil)
|
||||
|
||||
type ignoreCaseColumnDescriptor[T any] struct {
|
||||
columnDescriptor[T]
|
||||
fieldNameSuffix string
|
||||
}
|
||||
|
||||
func (f ignoreCaseColumnDescriptor[T]) ApplyIgnoreCase(stmt *statement[T]) {
|
||||
stmt.builder.WriteString(f.String())
|
||||
stmt.builder.WriteString(f.fieldNameSuffix)
|
||||
}
|
||||
|
||||
var _ Column[any] = (*ignoreCaseColumnDescriptor[any])(nil)
|
@@ -1,97 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import "fmt"
|
||||
|
||||
type statementApplier[T any] interface {
|
||||
// Apply writes the statement to the builder.
|
||||
Apply(stmt *statement[T])
|
||||
}
|
||||
|
||||
type Condition[T any] interface {
|
||||
statementApplier[T]
|
||||
}
|
||||
|
||||
type op interface {
|
||||
TextOperation | NumberOperation | ListOperation
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
type operation[T any, O op] struct {
|
||||
o O
|
||||
}
|
||||
|
||||
func (o operation[T, O]) String() string {
|
||||
return o.o.String()
|
||||
}
|
||||
|
||||
func (o operation[T, O]) Apply(stmt *statement[T]) {
|
||||
stmt.builder.WriteString(o.o.String())
|
||||
}
|
||||
|
||||
type condition[V, T any, OP op] struct {
|
||||
field Column[T]
|
||||
op OP
|
||||
value V
|
||||
}
|
||||
|
||||
func (c *condition[V, T, OP]) Apply(stmt *statement[T]) {
|
||||
// placeholder := stmt.appendArg(c.value)
|
||||
stmt.builder.WriteString(stmt.columnPrefix())
|
||||
stmt.builder.WriteString(c.field.String())
|
||||
// stmt.builder.WriteString(c.op)
|
||||
// stmt.builder.WriteString(placeholder)
|
||||
}
|
||||
|
||||
type and[T any] struct {
|
||||
conditions []Condition[T]
|
||||
}
|
||||
|
||||
func And[T any](conditions ...Condition[T]) *and[T] {
|
||||
return &and[T]{
|
||||
conditions: conditions,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply implements [Condition].
|
||||
func (a *and[T]) Apply(stmt *statement[T]) {
|
||||
if len(a.conditions) > 1 {
|
||||
stmt.builder.WriteString("(")
|
||||
defer stmt.builder.WriteString(")")
|
||||
}
|
||||
|
||||
for i, condition := range a.conditions {
|
||||
if i > 0 {
|
||||
stmt.builder.WriteString(" AND ")
|
||||
}
|
||||
condition.Apply(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition[any] = (*and[any])(nil)
|
||||
|
||||
type or[T any] struct {
|
||||
conditions []Condition[T]
|
||||
}
|
||||
|
||||
func Or[T any](conditions ...Condition[T]) *or[T] {
|
||||
return &or[T]{
|
||||
conditions: conditions,
|
||||
}
|
||||
}
|
||||
|
||||
// Apply implements [Condition].
|
||||
func (o *or[T]) Apply(stmt *statement[T]) {
|
||||
if len(o.conditions) > 1 {
|
||||
stmt.builder.WriteString("(")
|
||||
defer stmt.builder.WriteString(")")
|
||||
}
|
||||
|
||||
for i, condition := range o.conditions {
|
||||
if i > 0 {
|
||||
stmt.builder.WriteString(" OR ")
|
||||
}
|
||||
condition.Apply(stmt)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition[any] = (*or[any])(nil)
|
@@ -1,71 +0,0 @@
|
||||
package stmt
|
||||
|
||||
type ListEntry interface {
|
||||
Number | Text | any
|
||||
}
|
||||
|
||||
type ListCondition[E ListEntry, T any] struct {
|
||||
condition[[]E, T, ListOperation]
|
||||
}
|
||||
|
||||
func (lc *ListCondition[E, T]) Apply(stmt *statement[T]) {
|
||||
placeholder := stmt.appendArg(lc.value)
|
||||
|
||||
switch lc.op {
|
||||
case ListOperationEqual, ListOperationNotEqual:
|
||||
lc.field.Apply(stmt)
|
||||
operation[T, ListOperation]{lc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(placeholder)
|
||||
case ListOperationContainsAny, ListOperationContainsAll:
|
||||
lc.field.Apply(stmt)
|
||||
operation[T, ListOperation]{lc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(placeholder)
|
||||
case ListOperationNotContainsAny, ListOperationNotContainsAll:
|
||||
stmt.builder.WriteString("NOT (")
|
||||
lc.field.Apply(stmt)
|
||||
operation[T, ListOperation]{lc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(placeholder)
|
||||
stmt.builder.WriteString(")")
|
||||
default:
|
||||
panic("unknown list operation")
|
||||
}
|
||||
}
|
||||
|
||||
type ListOperation uint8
|
||||
|
||||
const (
|
||||
// ListOperationEqual checks if the arrays are equal including the order of the elements
|
||||
ListOperationEqual ListOperation = iota + 1
|
||||
// ListOperationNotEqual checks if the arrays are not equal including the order of the elements
|
||||
ListOperationNotEqual
|
||||
|
||||
// ListOperationContains checks if the array column contains all the values of the specified array
|
||||
ListOperationContainsAll
|
||||
// ListOperationContainsAny checks if the arrays have at least one value in common
|
||||
ListOperationContainsAny
|
||||
// ListOperationContainsAll checks if the array column contains all the values of the specified array
|
||||
|
||||
// ListOperationNotContainsAll checks if the specified array is not contained by the column
|
||||
ListOperationNotContainsAll
|
||||
// ListOperationNotContainsAny checks if the arrays column contains none of the values of the specified array
|
||||
ListOperationNotContainsAny
|
||||
)
|
||||
|
||||
var listOperations = map[ListOperation]string{
|
||||
// ListOperationEqual checks if the lists are equal
|
||||
ListOperationEqual: " = ",
|
||||
// ListOperationNotEqual checks if the lists are not equal
|
||||
ListOperationNotEqual: " <> ",
|
||||
// ListOperationContainsAny checks if the arrays have at least one value in common
|
||||
ListOperationContainsAny: " && ",
|
||||
// ListOperationContainsAll checks if the array column contains all the values of the specified array
|
||||
ListOperationContainsAll: " @> ",
|
||||
// ListOperationNotContainsAny checks if the arrays column contains none of the values of the specified array
|
||||
ListOperationNotContainsAny: " && ", // Base operator for NOT (A && B)
|
||||
// ListOperationNotContainsAll checks if the array column is not contained by the specified array
|
||||
ListOperationNotContainsAll: " <@ ", // Base operator for NOT (A <@ B)
|
||||
}
|
||||
|
||||
func (lo ListOperation) String() string {
|
||||
return listOperations[lo]
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type Number interface {
|
||||
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
||||
}
|
||||
|
||||
type between[N Number] struct {
|
||||
min, max N
|
||||
}
|
||||
|
||||
type NumberBetween[V Number, T any] struct {
|
||||
condition[between[V], T, NumberOperation]
|
||||
}
|
||||
|
||||
func (nb *NumberBetween[V, T]) Apply(stmt *statement[T]) {
|
||||
nb.field.Apply(stmt)
|
||||
stmt.builder.WriteString(" BETWEEN ")
|
||||
stmt.builder.WriteString(stmt.appendArg(nb.value.min))
|
||||
stmt.builder.WriteString(" AND ")
|
||||
stmt.builder.WriteString(stmt.appendArg(nb.value.max))
|
||||
}
|
||||
|
||||
type NumberCondition[V Number, T any] struct {
|
||||
condition[V, T, NumberOperation]
|
||||
}
|
||||
|
||||
func (nc *NumberCondition[V, T]) Apply(stmt *statement[T]) {
|
||||
nc.field.Apply(stmt)
|
||||
operation[T, NumberOperation]{nc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(stmt.appendArg(nc.value))
|
||||
}
|
||||
|
||||
type NumberOperation uint8
|
||||
|
||||
const (
|
||||
NumberOperationEqual NumberOperation = iota + 1
|
||||
NumberOperationNotEqual
|
||||
NumberOperationLessThan
|
||||
NumberOperationLessThanOrEqual
|
||||
NumberOperationGreaterThan
|
||||
NumberOperationGreaterThanOrEqual
|
||||
)
|
||||
|
||||
var numberOperations = map[NumberOperation]string{
|
||||
NumberOperationEqual: " = ",
|
||||
NumberOperationNotEqual: " <> ",
|
||||
NumberOperationLessThan: " < ",
|
||||
NumberOperationLessThanOrEqual: " <= ",
|
||||
NumberOperationGreaterThan: " > ",
|
||||
NumberOperationGreaterThanOrEqual: " >= ",
|
||||
}
|
||||
|
||||
func (no NumberOperation) String() string {
|
||||
return numberOperations[no]
|
||||
}
|
@@ -1,104 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type statement[T any] struct {
|
||||
builder strings.Builder
|
||||
client database.QueryExecutor
|
||||
|
||||
columns []Column[T]
|
||||
|
||||
schema string
|
||||
table string
|
||||
alias string
|
||||
|
||||
condition Condition[T]
|
||||
|
||||
limit uint32
|
||||
offset uint32
|
||||
// order by fieldname and sort direction false for asc true for desc
|
||||
// orderBy SortingColumns[C]
|
||||
args []any
|
||||
existingArgs map[any]string
|
||||
}
|
||||
|
||||
func (s *statement[T]) scanners(t *T) []any {
|
||||
scanners := make([]any, len(s.columns))
|
||||
for i, column := range s.columns {
|
||||
scanners[i] = column.scanner(t)
|
||||
}
|
||||
return scanners
|
||||
}
|
||||
|
||||
func (s *statement[T]) query() string {
|
||||
s.builder.WriteString(`SELECT `)
|
||||
for i, column := range s.columns {
|
||||
if i > 0 {
|
||||
s.builder.WriteString(", ")
|
||||
}
|
||||
column.Apply(s)
|
||||
}
|
||||
s.builder.WriteString(` FROM `)
|
||||
s.builder.WriteString(s.schema)
|
||||
s.builder.WriteRune('.')
|
||||
s.builder.WriteString(s.table)
|
||||
if s.alias != "" {
|
||||
s.builder.WriteString(" AS ")
|
||||
s.builder.WriteString(s.alias)
|
||||
}
|
||||
|
||||
s.builder.WriteString(` WHERE `)
|
||||
|
||||
s.condition.Apply(s)
|
||||
|
||||
if s.limit > 0 {
|
||||
s.builder.WriteString(` LIMIT `)
|
||||
s.builder.WriteString(s.appendArg(s.limit))
|
||||
}
|
||||
if s.offset > 0 {
|
||||
s.builder.WriteString(` OFFSET `)
|
||||
s.builder.WriteString(s.appendArg(s.offset))
|
||||
}
|
||||
|
||||
return s.builder.String()
|
||||
}
|
||||
|
||||
// func (s *statement[T]) Where(condition Condition[T]) *statement[T] {
|
||||
// s.condition = condition
|
||||
// return s
|
||||
// }
|
||||
|
||||
// func (s *statement[T]) Limit(limit uint32) *statement[T] {
|
||||
// s.limit = limit
|
||||
// return s
|
||||
// }
|
||||
|
||||
// func (s *statement[T]) Offset(offset uint32) *statement[T] {
|
||||
// s.offset = offset
|
||||
// return s
|
||||
// }
|
||||
|
||||
func (s *statement[T]) columnPrefix() string {
|
||||
if s.alias != "" {
|
||||
return s.alias + "."
|
||||
}
|
||||
return s.schema + "." + s.table + "."
|
||||
}
|
||||
|
||||
func (s *statement[T]) appendArg(arg any) string {
|
||||
if s.existingArgs == nil {
|
||||
s.existingArgs = make(map[any]string)
|
||||
}
|
||||
if existing, ok := s.existingArgs[arg]; ok {
|
||||
return existing
|
||||
}
|
||||
s.args = append(s.args, arg)
|
||||
placeholder := fmt.Sprintf("$%d", len(s.args))
|
||||
s.existingArgs[arg] = placeholder
|
||||
return placeholder
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
package stmt_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt"
|
||||
)
|
||||
|
||||
func Test_Bla(t *testing.T) {
|
||||
stmt.User(nil).Where(
|
||||
stmt.Or(
|
||||
stmt.UserIDCondition("123"),
|
||||
stmt.UserIDCondition("123"),
|
||||
stmt.UserUsernameCondition(stmt.TextOperationEqualIgnoreCase, "test"),
|
||||
),
|
||||
).Limit(1).Offset(1).Get(context.Background())
|
||||
}
|
@@ -1,72 +0,0 @@
|
||||
package stmt
|
||||
|
||||
type Text interface {
|
||||
~string | ~[]byte
|
||||
}
|
||||
|
||||
type TextCondition[V Text, T any] struct {
|
||||
condition[V, T, TextOperation]
|
||||
}
|
||||
|
||||
func (tc *TextCondition[V, T]) Apply(stmt *statement[T]) {
|
||||
placeholder := stmt.appendArg(tc.value)
|
||||
|
||||
switch tc.op {
|
||||
case TextOperationEqual, TextOperationNotEqual:
|
||||
tc.field.Apply(stmt)
|
||||
operation[T, TextOperation]{tc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(placeholder)
|
||||
case TextOperationEqualIgnoreCase:
|
||||
if desc, ok := tc.field.(ignoreCaseColumnDescriptor[T]); ok {
|
||||
desc.ApplyIgnoreCase(stmt)
|
||||
} else {
|
||||
stmt.builder.WriteString("LOWER(")
|
||||
tc.field.Apply(stmt)
|
||||
stmt.builder.WriteString(")")
|
||||
}
|
||||
operation[T, TextOperation]{tc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString("LOWER(")
|
||||
stmt.builder.WriteString(placeholder)
|
||||
stmt.builder.WriteString(")")
|
||||
case TextOperationStartsWith:
|
||||
tc.field.Apply(stmt)
|
||||
operation[T, TextOperation]{tc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString(placeholder)
|
||||
stmt.builder.WriteString("|| '%'")
|
||||
case TextOperationStartsWithIgnoreCase:
|
||||
if desc, ok := tc.field.(ignoreCaseColumnDescriptor[T]); ok {
|
||||
desc.ApplyIgnoreCase(stmt)
|
||||
} else {
|
||||
stmt.builder.WriteString("LOWER(")
|
||||
tc.field.Apply(stmt)
|
||||
stmt.builder.WriteString(")")
|
||||
}
|
||||
operation[T, TextOperation]{tc.op}.Apply(stmt)
|
||||
stmt.builder.WriteString("LOWER(")
|
||||
stmt.builder.WriteString(placeholder)
|
||||
stmt.builder.WriteString(")")
|
||||
stmt.builder.WriteString("|| '%'")
|
||||
}
|
||||
}
|
||||
|
||||
type TextOperation uint8
|
||||
|
||||
const (
|
||||
TextOperationEqual TextOperation = iota + 1
|
||||
TextOperationEqualIgnoreCase
|
||||
TextOperationNotEqual
|
||||
TextOperationStartsWith
|
||||
TextOperationStartsWithIgnoreCase
|
||||
)
|
||||
|
||||
var textOperations = map[TextOperation]string{
|
||||
TextOperationEqual: " = ",
|
||||
TextOperationEqualIgnoreCase: " = ",
|
||||
TextOperationNotEqual: " <> ",
|
||||
TextOperationStartsWith: " LIKE ",
|
||||
TextOperationStartsWithIgnoreCase: " LIKE ",
|
||||
}
|
||||
|
||||
func (to TextOperation) String() string {
|
||||
return textOperations[to]
|
||||
}
|
@@ -1,193 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type userStatement struct {
|
||||
statement[domain.User]
|
||||
}
|
||||
|
||||
func User(client database.QueryExecutor) *userStatement {
|
||||
return &userStatement{
|
||||
statement: statement[domain.User]{
|
||||
schema: "zitadel",
|
||||
table: "users",
|
||||
alias: "u",
|
||||
client: client,
|
||||
columns: []Column[domain.User]{
|
||||
userColumns[UserInstanceID],
|
||||
userColumns[UserOrgID],
|
||||
userColumns[UserColumnID],
|
||||
userColumns[UserColumnUsername],
|
||||
userColumns[UserCreatedAt],
|
||||
userColumns[UserUpdatedAt],
|
||||
userColumns[UserDeletedAt],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *userStatement) Where(condition Condition[domain.User]) *userStatement {
|
||||
s.condition = condition
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *userStatement) Limit(limit uint32) *userStatement {
|
||||
s.limit = limit
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *userStatement) Offset(offset uint32) *userStatement {
|
||||
s.offset = offset
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *userStatement) Get(ctx context.Context) (*domain.User, error) {
|
||||
var user domain.User
|
||||
err := s.client.QueryRow(ctx, s.query(), s.statement.args...).Scan(s.scanners(&user)...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (s *userStatement) List(ctx context.Context) ([]*domain.User, error) {
|
||||
var users []*domain.User
|
||||
rows, err := s.client.Query(ctx, s.query(), s.statement.args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var user domain.User
|
||||
err = rows.Scan(s.scanners(&user)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, &user)
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (s *userStatement) SetUsername(ctx context.Context, username string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type UserColumn uint8
|
||||
|
||||
var (
|
||||
userColumns map[UserColumn]Column[domain.User] = map[UserColumn]Column[domain.User]{
|
||||
UserInstanceID: columnDescriptor[domain.User]{
|
||||
name: "instance_id",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.InstanceID
|
||||
},
|
||||
},
|
||||
UserOrgID: columnDescriptor[domain.User]{
|
||||
name: "org_id",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.OrgID
|
||||
},
|
||||
},
|
||||
UserColumnID: columnDescriptor[domain.User]{
|
||||
name: "id",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.ID
|
||||
},
|
||||
},
|
||||
UserColumnUsername: ignoreCaseColumnDescriptor[domain.User]{
|
||||
columnDescriptor: columnDescriptor[domain.User]{
|
||||
name: "username",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.Username
|
||||
},
|
||||
},
|
||||
fieldNameSuffix: "_lower",
|
||||
},
|
||||
UserCreatedAt: columnDescriptor[domain.User]{
|
||||
name: "created_at",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.CreatedAt
|
||||
},
|
||||
},
|
||||
UserUpdatedAt: columnDescriptor[domain.User]{
|
||||
name: "updated_at",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.UpdatedAt
|
||||
},
|
||||
},
|
||||
UserDeletedAt: columnDescriptor[domain.User]{
|
||||
name: "deleted_at",
|
||||
scan: func(u *domain.User) any {
|
||||
return &u.DeletedAt
|
||||
},
|
||||
},
|
||||
}
|
||||
humanColumns = map[UserColumn]Column[domain.User]{
|
||||
UserHumanColumnEmail: ignoreCaseColumnDescriptor[domain.User]{
|
||||
columnDescriptor: columnDescriptor[domain.User]{
|
||||
name: "email",
|
||||
scan: func(u *domain.User) any {
|
||||
human, ok := u.Traits.(*domain.Human)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if human.Email == nil {
|
||||
human.Email = new(domain.Email)
|
||||
}
|
||||
return &human.Email.Address
|
||||
},
|
||||
},
|
||||
fieldNameSuffix: "_lower",
|
||||
},
|
||||
UserHumanColumnEmailVerified: columnDescriptor[domain.User]{
|
||||
name: "email_is_verified",
|
||||
scan: func(u *domain.User) any {
|
||||
human, ok := u.Traits.(*domain.Human)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if human.Email == nil {
|
||||
human.Email = new(domain.Email)
|
||||
}
|
||||
return &human.Email.IsVerified
|
||||
},
|
||||
},
|
||||
}
|
||||
machineColumns = map[UserColumn]Column[domain.User]{
|
||||
UserMachineDescription: columnDescriptor[domain.User]{
|
||||
name: "description",
|
||||
scan: func(u *domain.User) any {
|
||||
machine, ok := u.Traits.(*domain.Machine)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if machine == nil {
|
||||
machine = new(domain.Machine)
|
||||
}
|
||||
return &machine.Description
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
UserInstanceID UserColumn = iota + 1
|
||||
UserOrgID
|
||||
UserColumnID
|
||||
UserColumnUsername
|
||||
UserHumanColumnEmail
|
||||
UserHumanColumnEmailVerified
|
||||
UserMachineDescription
|
||||
UserCreatedAt
|
||||
UserUpdatedAt
|
||||
UserDeletedAt
|
||||
)
|
@@ -1,23 +0,0 @@
|
||||
package stmt
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/domain"
|
||||
|
||||
func UserIDCondition(id string) *TextCondition[string, domain.User] {
|
||||
return &TextCondition[string, domain.User]{
|
||||
condition: condition[string, domain.User, TextOperation]{
|
||||
field: userColumns[UserColumnID],
|
||||
op: TextOperationEqual,
|
||||
value: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func UserUsernameCondition(op TextOperation, username string) *TextCondition[string, domain.User] {
|
||||
return &TextCondition[string, domain.User]{
|
||||
condition: condition[string, domain.User, TextOperation]{
|
||||
field: userColumns[UserColumnUsername],
|
||||
op: op,
|
||||
value: username,
|
||||
},
|
||||
}
|
||||
}
|
@@ -1,135 +0,0 @@
|
||||
package stmt
|
||||
|
||||
// type table struct {
|
||||
// schema string
|
||||
// name string
|
||||
|
||||
// possibleJoins []*join
|
||||
|
||||
// columns []*col
|
||||
// }
|
||||
|
||||
// type col struct {
|
||||
// *table
|
||||
|
||||
// name string
|
||||
// }
|
||||
|
||||
// type join struct {
|
||||
// *table
|
||||
|
||||
// on []*joinColumns
|
||||
// }
|
||||
|
||||
// type joinColumns struct {
|
||||
// left, right *col
|
||||
// }
|
||||
|
||||
// var (
|
||||
// userTable = &table{
|
||||
// schema: "zitadel",
|
||||
// name: "users",
|
||||
// }
|
||||
// userColumns = []*col{
|
||||
// userInstanceIDColumn,
|
||||
// userOrgIDColumn,
|
||||
// userIDColumn,
|
||||
// userUsernameColumn,
|
||||
// }
|
||||
// userInstanceIDColumn = &col{
|
||||
// table: userTable,
|
||||
// name: "instance_id",
|
||||
// }
|
||||
// userOrgIDColumn = &col{
|
||||
// table: userTable,
|
||||
// name: "org_id",
|
||||
// }
|
||||
// userIDColumn = &col{
|
||||
// table: userTable,
|
||||
// name: "id",
|
||||
// }
|
||||
// userUsernameColumn = &col{
|
||||
// table: userTable,
|
||||
// name: "username",
|
||||
// }
|
||||
// userJoins = []*join{
|
||||
// {
|
||||
// table: instanceTable,
|
||||
// on: []*joinColumns{
|
||||
// {
|
||||
// left: instanceIDColumn,
|
||||
// right: userInstanceIDColumn,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// {
|
||||
// table: orgTable,
|
||||
// on: []*joinColumns{
|
||||
// {
|
||||
// left: orgIDColumn,
|
||||
// right: userOrgIDColumn,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
|
||||
// var (
|
||||
// instanceTable = &table{
|
||||
// schema: "zitadel",
|
||||
// name: "instances",
|
||||
// }
|
||||
// instanceColumns = []*col{
|
||||
// instanceIDColumn,
|
||||
// instanceNameColumn,
|
||||
// }
|
||||
// instanceIDColumn = &col{
|
||||
// table: instanceTable,
|
||||
// name: "id",
|
||||
// }
|
||||
// instanceNameColumn = &col{
|
||||
// table: instanceTable,
|
||||
// name: "name",
|
||||
// }
|
||||
// )
|
||||
|
||||
// var (
|
||||
// orgTable = &table{
|
||||
// schema: "zitadel",
|
||||
// name: "orgs",
|
||||
// }
|
||||
// orgColumns = []*col{
|
||||
// orgInstanceIDColumn,
|
||||
// orgIDColumn,
|
||||
// orgNameColumn,
|
||||
// }
|
||||
// orgInstanceIDColumn = &col{
|
||||
// table: orgTable,
|
||||
// name: "instance_id",
|
||||
// }
|
||||
// orgIDColumn = &col{
|
||||
// table: orgTable,
|
||||
// name: "id",
|
||||
// }
|
||||
// orgNameColumn = &col{
|
||||
// table: orgTable,
|
||||
// name: "name",
|
||||
// }
|
||||
// )
|
||||
|
||||
// func init() {
|
||||
// instanceTable.columns = instanceColumns
|
||||
// userTable.columns = userColumns
|
||||
|
||||
// userTable.possibleJoins = []join{
|
||||
// {
|
||||
// table: userTable,
|
||||
// on: []joinColumns{
|
||||
// {
|
||||
// left: userIDColumn,
|
||||
// right: userIDColumn,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }
|
||||
// }
|
@@ -1,55 +0,0 @@
|
||||
package v3
|
||||
|
||||
type Column interface {
|
||||
Name() string
|
||||
Write(builder statementBuilder)
|
||||
}
|
||||
|
||||
type ignoreCaseColumn interface {
|
||||
Column
|
||||
WriteIgnoreCase(builder statementBuilder)
|
||||
}
|
||||
|
||||
var (
|
||||
columnNameID = "id"
|
||||
columnNameName = "name"
|
||||
columnNameCreatedAt = "created_at"
|
||||
columnNameUpdatedAt = "updated_at"
|
||||
columnNameDeletedAt = "deleted_at"
|
||||
|
||||
columnNameInstanceID = "instance_id"
|
||||
|
||||
columnNameOrgID = "org_id"
|
||||
)
|
||||
|
||||
type column struct {
|
||||
table Table
|
||||
name string
|
||||
}
|
||||
|
||||
// Write implements Column.
|
||||
func (c *column) Write(builder statementBuilder) {
|
||||
c.table.writeOn(builder)
|
||||
builder.writeRune('.')
|
||||
builder.writeString(c.name)
|
||||
}
|
||||
|
||||
// Name implements [Column].
|
||||
func (c *column) Name() string {
|
||||
return c.name
|
||||
}
|
||||
|
||||
var _ Column = (*column)(nil)
|
||||
|
||||
type columnIgnoreCase struct {
|
||||
column
|
||||
suffix string
|
||||
}
|
||||
|
||||
// WriteIgnoreCase implements ignoreCaseColumn.
|
||||
func (c *columnIgnoreCase) WriteIgnoreCase(builder statementBuilder) {
|
||||
c.Write(builder)
|
||||
builder.writeString(c.suffix)
|
||||
}
|
||||
|
||||
var _ ignoreCaseColumn = (*columnIgnoreCase)(nil)
|
@@ -1,182 +0,0 @@
|
||||
package v3
|
||||
|
||||
type statementBuilder interface {
|
||||
write([]byte)
|
||||
writeString(string)
|
||||
writeRune(rune)
|
||||
|
||||
appendArg(any) (placeholder string)
|
||||
table() Table
|
||||
}
|
||||
|
||||
type Condition interface {
|
||||
writeOn(builder statementBuilder)
|
||||
}
|
||||
|
||||
type and struct {
|
||||
conditions []Condition
|
||||
}
|
||||
|
||||
func And(conditions ...Condition) *and {
|
||||
return &and{conditions: conditions}
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (a *and) writeOn(builder statementBuilder) {
|
||||
if len(a.conditions) > 1 {
|
||||
builder.writeString("(")
|
||||
defer builder.writeString(")")
|
||||
}
|
||||
|
||||
for i, condition := range a.conditions {
|
||||
if i > 0 {
|
||||
builder.writeString(" AND ")
|
||||
}
|
||||
condition.writeOn(builder)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition = (*and)(nil)
|
||||
|
||||
type or struct {
|
||||
conditions []Condition
|
||||
}
|
||||
|
||||
func Or(conditions ...Condition) *or {
|
||||
return &or{conditions: conditions}
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (o *or) writeOn(builder statementBuilder) {
|
||||
if len(o.conditions) > 1 {
|
||||
builder.writeString("(")
|
||||
defer builder.writeString(")")
|
||||
}
|
||||
|
||||
for i, condition := range o.conditions {
|
||||
if i > 0 {
|
||||
builder.writeString(" OR ")
|
||||
}
|
||||
condition.writeOn(builder)
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition = (*or)(nil)
|
||||
|
||||
type isNull struct {
|
||||
column Column
|
||||
}
|
||||
|
||||
func IsNull(column Column) *isNull {
|
||||
return &isNull{column: column}
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (cond *isNull) writeOn(builder statementBuilder) {
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(" IS NULL")
|
||||
}
|
||||
|
||||
var _ Condition = (*isNull)(nil)
|
||||
|
||||
type isNotNull struct {
|
||||
column Column
|
||||
}
|
||||
|
||||
func IsNotNull(column Column) *isNotNull {
|
||||
return &isNotNull{column: column}
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (cond *isNotNull) writeOn(builder statementBuilder) {
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(" IS NOT NULL")
|
||||
}
|
||||
|
||||
var _ Condition = (*isNotNull)(nil)
|
||||
|
||||
type condition[Op Operator, V Value] struct {
|
||||
column Column
|
||||
operator Op
|
||||
value V
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (cond condition[Op, V]) writeOn(builder statementBuilder) {
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(cond.operator.String())
|
||||
builder.writeString(builder.appendArg(cond.value))
|
||||
}
|
||||
|
||||
var _ Condition = (*condition[TextOperator, string])(nil)
|
||||
|
||||
type textCondition[V Text] struct {
|
||||
condition[TextOperator, V]
|
||||
}
|
||||
|
||||
func NewTextCondition[V Text](column Column, operator TextOperator, value V) *textCondition[V] {
|
||||
return &textCondition[V]{
|
||||
condition: condition[TextOperator, V]{
|
||||
column: column,
|
||||
operator: operator,
|
||||
value: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// writeOn implements [Condition].
|
||||
func (cond *textCondition[V]) writeOn(builder statementBuilder) {
|
||||
switch cond.operator {
|
||||
case TextOperatorEqual, TextOperatorNotEqual:
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(cond.operator.String())
|
||||
builder.writeString(builder.appendArg(cond.value))
|
||||
case TextOperatorEqualIgnoreCase, TextOperatorNotEqualIgnoreCase:
|
||||
if col, ok := cond.column.(ignoreCaseColumn); ok {
|
||||
col.WriteIgnoreCase(builder)
|
||||
} else {
|
||||
builder.writeString("LOWER(")
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(")")
|
||||
}
|
||||
builder.writeString(cond.operator.String())
|
||||
builder.writeString("LOWER(")
|
||||
builder.writeString(builder.appendArg(cond.value))
|
||||
builder.writeString(")")
|
||||
case TextOperatorStartsWith:
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(cond.operator.String())
|
||||
builder.writeString(builder.appendArg(cond.value))
|
||||
builder.writeString(" || '%'")
|
||||
case TextOperatorStartsWithIgnoreCase:
|
||||
if col, ok := cond.column.(ignoreCaseColumn); ok {
|
||||
col.WriteIgnoreCase(builder)
|
||||
} else {
|
||||
builder.writeString("LOWER(")
|
||||
cond.column.Write(builder)
|
||||
builder.writeString(")")
|
||||
}
|
||||
builder.writeString(cond.operator.String())
|
||||
builder.writeString("LOWER(")
|
||||
builder.writeString(builder.appendArg(cond.value))
|
||||
builder.writeString(") || '%'")
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition = (*textCondition[string])(nil)
|
||||
|
||||
type numberCondition[V Number] struct {
|
||||
condition[NumberOperator, V]
|
||||
}
|
||||
|
||||
func NewNumberCondition[V Number](column Column, operator NumberOperator, value V) *numberCondition[V] {
|
||||
return &numberCondition[V]{
|
||||
condition: condition[NumberOperator, V]{
|
||||
column: column,
|
||||
operator: operator,
|
||||
value: value,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
var _ Condition = (*numberCondition[int])(nil)
|
@@ -1,104 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type Instance struct {
|
||||
id string
|
||||
name string
|
||||
|
||||
createdAt time.Time
|
||||
updatedAt time.Time
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
// Columns implements [object].
|
||||
func (Instance) Columns(table Table) []Column {
|
||||
return []Column{
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameID,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameName,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameCreatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameUpdatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameDeletedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Scan implements [object].
|
||||
func (i Instance) Scan(row database.Scanner) error {
|
||||
return row.Scan(
|
||||
&i.id,
|
||||
&i.name,
|
||||
&i.createdAt,
|
||||
&i.updatedAt,
|
||||
&i.deletedAt,
|
||||
)
|
||||
}
|
||||
|
||||
type instanceTable struct {
|
||||
*table
|
||||
}
|
||||
|
||||
func InstanceTable() *instanceTable {
|
||||
table := &instanceTable{
|
||||
table: newTable[Instance]("zitadel", "instances"),
|
||||
}
|
||||
|
||||
table.possibleJoins = func(t Table) map[string]Column {
|
||||
switch on := t.(type) {
|
||||
case *instanceTable:
|
||||
return map[string]Column{
|
||||
columnNameID: on.IDColumn(),
|
||||
}
|
||||
case *orgTable:
|
||||
return map[string]Column{
|
||||
columnNameID: on.InstanceIDColumn(),
|
||||
}
|
||||
case *userTable:
|
||||
return map[string]Column{
|
||||
columnNameID: on.InstanceIDColumn(),
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (i *instanceTable) IDColumn() Column {
|
||||
return i.columns[columnNameID]
|
||||
}
|
||||
|
||||
func (i *instanceTable) NameColumn() Column {
|
||||
return i.columns[columnNameName]
|
||||
}
|
||||
|
||||
func (i *instanceTable) CreatedAtColumn() Column {
|
||||
return i.columns[columnNameCreatedAt]
|
||||
}
|
||||
|
||||
func (i *instanceTable) UpdatedAtColumn() Column {
|
||||
return i.columns[columnNameUpdatedAt]
|
||||
}
|
||||
|
||||
func (i *instanceTable) DeletedAtColumn() Column {
|
||||
return i.columns[columnNameDeletedAt]
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
package v3
|
||||
|
||||
type join struct {
|
||||
table Table
|
||||
conditions []joinCondition
|
||||
}
|
||||
|
||||
type joinCondition struct {
|
||||
left Column
|
||||
right Column
|
||||
}
|
@@ -1,82 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/exp/constraints"
|
||||
)
|
||||
|
||||
type Value interface {
|
||||
Bool | Number | Text
|
||||
}
|
||||
|
||||
type Text interface {
|
||||
~string | ~[]byte
|
||||
}
|
||||
|
||||
type Number interface {
|
||||
constraints.Integer | constraints.Float | constraints.Complex | time.Time | time.Duration
|
||||
}
|
||||
|
||||
type Bool interface {
|
||||
~bool
|
||||
}
|
||||
|
||||
type Operator interface {
|
||||
fmt.Stringer
|
||||
}
|
||||
|
||||
type TextOperator uint8
|
||||
|
||||
// String implements [Operator].
|
||||
func (t TextOperator) String() string {
|
||||
return textOperators[t]
|
||||
}
|
||||
|
||||
const (
|
||||
TextOperatorEqual TextOperator = iota + 1
|
||||
TextOperatorEqualIgnoreCase
|
||||
TextOperatorNotEqual
|
||||
TextOperatorNotEqualIgnoreCase
|
||||
TextOperatorStartsWith
|
||||
TextOperatorStartsWithIgnoreCase
|
||||
)
|
||||
|
||||
var textOperators = map[TextOperator]string{
|
||||
TextOperatorEqual: " = ",
|
||||
TextOperatorEqualIgnoreCase: " LIKE ",
|
||||
TextOperatorNotEqual: " <> ",
|
||||
TextOperatorNotEqualIgnoreCase: " NOT LIKE ",
|
||||
TextOperatorStartsWith: " LIKE ",
|
||||
TextOperatorStartsWithIgnoreCase: " LIKE ",
|
||||
}
|
||||
|
||||
var _ Operator = TextOperator(0)
|
||||
|
||||
type NumberOperator uint8
|
||||
|
||||
// String implements Operator.
|
||||
func (n NumberOperator) String() string {
|
||||
return numberOperators[n]
|
||||
}
|
||||
|
||||
const (
|
||||
NumberOperatorEqual NumberOperator = iota + 1
|
||||
NumberOperatorNotEqual
|
||||
NumberOperatorLessThan
|
||||
NumberOperatorLessThanOrEqual
|
||||
NumberOperatorGreaterThan
|
||||
NumberOperatorGreaterThanOrEqual
|
||||
)
|
||||
|
||||
var numberOperators = map[NumberOperator]string{
|
||||
NumberOperatorEqual: " = ",
|
||||
NumberOperatorNotEqual: " <> ",
|
||||
NumberOperatorLessThan: " < ",
|
||||
NumberOperatorLessThanOrEqual: " <= ",
|
||||
NumberOperatorGreaterThan: " > ",
|
||||
NumberOperatorGreaterThanOrEqual: " >= ",
|
||||
}
|
||||
|
||||
var _ Operator = NumberOperator(0)
|
@@ -1,117 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type Org struct {
|
||||
instanceID string
|
||||
id string
|
||||
|
||||
name string
|
||||
|
||||
createdAt time.Time
|
||||
updatedAt time.Time
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
// Columns implements [object].
|
||||
func (Org) Columns(table Table) []Column {
|
||||
return []Column{
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameInstanceID,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameID,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameName,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameCreatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameUpdatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameDeletedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Scan implements [object].
|
||||
func (o Org) Scan(row database.Scanner) error {
|
||||
return row.Scan(
|
||||
&o.instanceID,
|
||||
&o.id,
|
||||
&o.name,
|
||||
&o.createdAt,
|
||||
&o.updatedAt,
|
||||
&o.deletedAt,
|
||||
)
|
||||
}
|
||||
|
||||
type orgTable struct {
|
||||
*table
|
||||
}
|
||||
|
||||
func OrgTable() *orgTable {
|
||||
table := &orgTable{
|
||||
table: newTable[Org]("zitadel", "orgs"),
|
||||
}
|
||||
|
||||
table.possibleJoins = func(table Table) map[string]Column {
|
||||
switch on := table.(type) {
|
||||
case *instanceTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.IDColumn(),
|
||||
}
|
||||
case *orgTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.InstanceIDColumn(),
|
||||
columnNameID: on.IDColumn(),
|
||||
}
|
||||
case *userTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.InstanceIDColumn(),
|
||||
columnNameID: on.IDColumn(),
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (o *orgTable) InstanceIDColumn() Column {
|
||||
return o.columns[columnNameInstanceID]
|
||||
}
|
||||
|
||||
func (o *orgTable) IDColumn() Column {
|
||||
return o.columns[columnNameID]
|
||||
}
|
||||
|
||||
func (o *orgTable) NameColumn() Column {
|
||||
return o.columns[columnNameName]
|
||||
}
|
||||
|
||||
func (o *orgTable) CreatedAtColumn() Column {
|
||||
return o.columns[columnNameCreatedAt]
|
||||
}
|
||||
|
||||
func (o *orgTable) UpdatedAtColumn() Column {
|
||||
return o.columns[columnNameUpdatedAt]
|
||||
}
|
||||
|
||||
func (o *orgTable) DeletedAtColumn() Column {
|
||||
return o.columns[columnNameDeletedAt]
|
||||
}
|
@@ -1,188 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type Query[O object] interface {
|
||||
Where(condition Condition)
|
||||
Join(tables ...Table)
|
||||
Limit(limit uint32)
|
||||
Offset(offset uint32)
|
||||
OrderBy(columns ...Column)
|
||||
|
||||
Result(ctx context.Context, client database.Querier) (*O, error)
|
||||
Results(ctx context.Context, client database.Querier) ([]O, error)
|
||||
|
||||
fmt.Stringer
|
||||
statementBuilder
|
||||
}
|
||||
|
||||
type query[O object] struct {
|
||||
*statement[O]
|
||||
joins []join
|
||||
limit uint32
|
||||
offset uint32
|
||||
orderBy []Column
|
||||
}
|
||||
|
||||
func NewQuery[O object](table Table) Query[O] {
|
||||
return &query[O]{
|
||||
statement: newStatement[O](table),
|
||||
}
|
||||
}
|
||||
|
||||
// Result implements [Query].
|
||||
func (q *query[O]) Result(ctx context.Context, client database.Querier) (*O, error) {
|
||||
var object O
|
||||
row := client.QueryRow(ctx, q.String(), q.args...)
|
||||
if err := object.Scan(row); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &object, nil
|
||||
}
|
||||
|
||||
// Results implements [Query].
|
||||
func (q *query[O]) Results(ctx context.Context, client database.Querier) ([]O, error) {
|
||||
var objects []O
|
||||
rows, err := client.Query(ctx, q.String(), q.args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var object O
|
||||
if err := object.Scan(rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, object)
|
||||
}
|
||||
|
||||
return objects, rows.Err()
|
||||
}
|
||||
|
||||
// Join implements [Query].
|
||||
func (q *query[O]) Join(tables ...Table) {
|
||||
for _, tbl := range tables {
|
||||
cols := q.tbl.(*table).possibleJoins(tbl)
|
||||
if len(cols) == 0 {
|
||||
panic(fmt.Sprintf("table %q does not have any possible joins with table %q", q.tbl.Name(), tbl.Name()))
|
||||
}
|
||||
|
||||
q.joins = append(q.joins, join{
|
||||
table: tbl,
|
||||
conditions: make([]joinCondition, 0, len(cols)),
|
||||
})
|
||||
|
||||
for colName, col := range cols {
|
||||
q.joins[len(q.joins)-1].conditions = append(q.joins[len(q.joins)-1].conditions, joinCondition{
|
||||
left: q.tbl.(*table).columns[colName],
|
||||
right: col,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *query[O]) Limit(limit uint32) {
|
||||
q.limit = limit
|
||||
}
|
||||
|
||||
func (q *query[O]) Offset(offset uint32) {
|
||||
q.offset = offset
|
||||
}
|
||||
|
||||
func (q *query[O]) OrderBy(columns ...Column) {
|
||||
for _, allowedColumn := range q.columns {
|
||||
for _, column := range columns {
|
||||
if allowedColumn.Name() == column.Name() {
|
||||
q.orderBy = append(q.orderBy, column)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// String implements [fmt.Stringer] and [Query].
|
||||
func (q *query[O]) String() string {
|
||||
q.writeSelectColumns()
|
||||
q.writeFrom()
|
||||
q.writeJoins()
|
||||
q.writeCondition()
|
||||
q.writeOrderBy()
|
||||
q.writeLimit()
|
||||
q.writeOffset()
|
||||
q.writeGroupBy()
|
||||
return q.builder.String()
|
||||
}
|
||||
|
||||
func (q *query[O]) writeSelectColumns() {
|
||||
q.builder.WriteString("SELECT ")
|
||||
for i, column := range q.columns {
|
||||
if i > 0 {
|
||||
q.builder.WriteString(", ")
|
||||
}
|
||||
q.builder.WriteString(q.tbl.Alias())
|
||||
q.builder.WriteRune('.')
|
||||
q.builder.WriteString(column.Name())
|
||||
}
|
||||
}
|
||||
|
||||
func (q *query[O]) writeJoins() {
|
||||
for _, join := range q.joins {
|
||||
q.builder.WriteString(" JOIN ")
|
||||
q.builder.WriteString(join.table.Schema())
|
||||
q.builder.WriteRune('.')
|
||||
q.builder.WriteString(join.table.Name())
|
||||
if join.table.Alias() != "" {
|
||||
q.builder.WriteString(" AS ")
|
||||
q.builder.WriteString(join.table.Alias())
|
||||
}
|
||||
|
||||
q.builder.WriteString(" ON ")
|
||||
for i, condition := range join.conditions {
|
||||
if i > 0 {
|
||||
q.builder.WriteString(" AND ")
|
||||
}
|
||||
q.builder.WriteString(condition.left.Name())
|
||||
q.builder.WriteString(" = ")
|
||||
q.builder.WriteString(condition.right.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (q *query[O]) writeOrderBy() {
|
||||
if len(q.orderBy) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
q.builder.WriteString(" ORDER BY ")
|
||||
for i, order := range q.orderBy {
|
||||
if i > 0 {
|
||||
q.builder.WriteString(", ")
|
||||
}
|
||||
order.Write(q)
|
||||
}
|
||||
}
|
||||
|
||||
func (q *query[O]) writeLimit() {
|
||||
if q.limit == 0 {
|
||||
return
|
||||
}
|
||||
q.builder.WriteString(" LIMIT ")
|
||||
q.builder.WriteString(q.appendArg(q.limit))
|
||||
}
|
||||
|
||||
func (q *query[O]) writeOffset() {
|
||||
if q.offset == 0 {
|
||||
return
|
||||
}
|
||||
q.builder.WriteString(" OFFSET ")
|
||||
q.builder.WriteString(q.appendArg(q.offset))
|
||||
}
|
||||
|
||||
func (q *query[O]) writeGroupBy() {
|
||||
q.builder.WriteString(" GROUP BY ")
|
||||
}
|
@@ -1,85 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type statement[T object] struct {
|
||||
tbl Table
|
||||
columns []Column
|
||||
condition Condition
|
||||
|
||||
builder strings.Builder
|
||||
args []any
|
||||
existingArgs map[any]string
|
||||
}
|
||||
|
||||
func newStatement[O object](t Table) *statement[O] {
|
||||
var o O
|
||||
return &statement[O]{
|
||||
tbl: t,
|
||||
columns: o.Columns(t),
|
||||
}
|
||||
}
|
||||
|
||||
// Where implements [Query].
|
||||
func (stmt *statement[T]) Where(condition Condition) {
|
||||
stmt.condition = condition
|
||||
}
|
||||
|
||||
func (stmt *statement[T]) writeFrom() {
|
||||
stmt.builder.WriteString(" FROM ")
|
||||
stmt.builder.WriteString(stmt.tbl.Schema())
|
||||
stmt.builder.WriteRune('.')
|
||||
stmt.builder.WriteString(stmt.tbl.Name())
|
||||
if stmt.tbl.Alias() != "" {
|
||||
stmt.builder.WriteString(" AS ")
|
||||
stmt.builder.WriteString(stmt.tbl.Alias())
|
||||
}
|
||||
}
|
||||
|
||||
func (stmt *statement[T]) writeCondition() {
|
||||
if stmt.condition == nil {
|
||||
return
|
||||
}
|
||||
stmt.builder.WriteString(" WHERE ")
|
||||
stmt.condition.writeOn(stmt)
|
||||
}
|
||||
|
||||
// appendArg implements [statementBuilder].
|
||||
func (stmt *statement[T]) appendArg(arg any) (placeholder string) {
|
||||
if stmt.existingArgs == nil {
|
||||
stmt.existingArgs = make(map[any]string)
|
||||
}
|
||||
if placeholder, ok := stmt.existingArgs[arg]; ok {
|
||||
return placeholder
|
||||
}
|
||||
|
||||
stmt.args = append(stmt.args, arg)
|
||||
placeholder = fmt.Sprintf("$%d", len(stmt.args))
|
||||
stmt.existingArgs[arg] = placeholder
|
||||
return placeholder
|
||||
}
|
||||
|
||||
// table implements [statementBuilder].
|
||||
func (stmt *statement[T]) table() Table {
|
||||
return stmt.tbl
|
||||
}
|
||||
|
||||
// write implements [statementBuilder].
|
||||
func (stmt *statement[T]) write(data []byte) {
|
||||
stmt.builder.Write(data)
|
||||
}
|
||||
|
||||
// writeRune implements [statementBuilder].
|
||||
func (stmt *statement[T]) writeRune(r rune) {
|
||||
stmt.builder.WriteRune(r)
|
||||
}
|
||||
|
||||
// writeString implements [statementBuilder].
|
||||
func (stmt *statement[T]) writeString(s string) {
|
||||
stmt.builder.WriteString(s)
|
||||
}
|
||||
|
||||
var _ statementBuilder = (*statement[Instance])(nil)
|
@@ -1,84 +0,0 @@
|
||||
package v3
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
type object interface {
|
||||
User | Org | Instance
|
||||
Columns(t Table) []Column
|
||||
Scan(s database.Scanner) error
|
||||
}
|
||||
|
||||
type Table interface {
|
||||
Schema() string
|
||||
Name() string
|
||||
Alias() string
|
||||
Columns() []Column
|
||||
|
||||
writeOn(builder statementBuilder)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
schema string
|
||||
name string
|
||||
alias string
|
||||
|
||||
possibleJoins func(table Table) map[string]Column
|
||||
|
||||
columns map[string]Column
|
||||
colList []Column
|
||||
}
|
||||
|
||||
func newTable[O object](schema, name string) *table {
|
||||
t := &table{
|
||||
schema: schema,
|
||||
name: name,
|
||||
}
|
||||
|
||||
var o O
|
||||
t.colList = o.Columns(t)
|
||||
t.columns = make(map[string]Column, len(t.colList))
|
||||
for _, col := range t.colList {
|
||||
t.columns[col.Name()] = col
|
||||
}
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
// Columns implements [Table].
|
||||
func (t *table) Columns() []Column {
|
||||
if len(t.colList) > 0 {
|
||||
return t.colList
|
||||
}
|
||||
|
||||
t.colList = make([]Column, 0, len(t.columns))
|
||||
for _, column := range t.columns {
|
||||
t.colList = append(t.colList, column)
|
||||
}
|
||||
|
||||
return t.colList
|
||||
}
|
||||
|
||||
// Name implements [Table].
|
||||
func (t *table) Name() string {
|
||||
return t.name
|
||||
}
|
||||
|
||||
// Schema implements [Table].
|
||||
func (t *table) Schema() string {
|
||||
return t.schema
|
||||
}
|
||||
|
||||
// Alias implements [Table].
|
||||
func (t *table) Alias() string {
|
||||
if t.alias != "" {
|
||||
return t.alias
|
||||
}
|
||||
return t.schema + "." + t.name
|
||||
}
|
||||
|
||||
// writeOn implements [Table].
|
||||
func (t *table) writeOn(builder statementBuilder) {
|
||||
builder.writeString(t.Alias())
|
||||
}
|
||||
|
||||
var _ Table = (*table)(nil)
|
@@ -1,170 +0,0 @@
|
||||
package v3
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
instanceID string
|
||||
orgID string
|
||||
id string
|
||||
username string
|
||||
|
||||
createdAt time.Time
|
||||
updatedAt time.Time
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
// Columns implements [object].
|
||||
func (u User) Columns(table Table) []Column {
|
||||
return []Column{
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameInstanceID,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameOrgID,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameID,
|
||||
},
|
||||
&columnIgnoreCase{
|
||||
column: column{
|
||||
table: table,
|
||||
name: userTableUsernameColumn,
|
||||
},
|
||||
suffix: "_lower",
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameCreatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameUpdatedAt,
|
||||
},
|
||||
&column{
|
||||
table: table,
|
||||
name: columnNameDeletedAt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Scan implements [object].
|
||||
func (u User) Scan(row database.Scanner) error {
|
||||
return row.Scan(
|
||||
&u.instanceID,
|
||||
&u.orgID,
|
||||
&u.id,
|
||||
&u.username,
|
||||
&u.createdAt,
|
||||
&u.updatedAt,
|
||||
&u.deletedAt,
|
||||
)
|
||||
}
|
||||
|
||||
type userTable struct {
|
||||
*table
|
||||
}
|
||||
|
||||
const (
|
||||
userTableUsernameColumn = "username"
|
||||
)
|
||||
|
||||
func UserTable() *userTable {
|
||||
table := &userTable{
|
||||
table: newTable[User]("zitadel", "users"),
|
||||
}
|
||||
|
||||
table.possibleJoins = func(table Table) map[string]Column {
|
||||
switch on := table.(type) {
|
||||
case *userTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.InstanceIDColumn(),
|
||||
columnNameOrgID: on.OrgIDColumn(),
|
||||
columnNameID: on.IDColumn(),
|
||||
}
|
||||
case *orgTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.InstanceIDColumn(),
|
||||
columnNameOrgID: on.IDColumn(),
|
||||
}
|
||||
case *instanceTable:
|
||||
return map[string]Column{
|
||||
columnNameInstanceID: on.IDColumn(),
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return table
|
||||
}
|
||||
|
||||
func (t *userTable) InstanceIDColumn() Column {
|
||||
return t.columns[columnNameInstanceID]
|
||||
}
|
||||
|
||||
func (t *userTable) OrgIDColumn() Column {
|
||||
return t.columns[columnNameOrgID]
|
||||
}
|
||||
|
||||
func (t *userTable) IDColumn() Column {
|
||||
return t.columns[columnNameID]
|
||||
}
|
||||
|
||||
func (t *userTable) UsernameColumn() Column {
|
||||
return t.columns[userTableUsernameColumn]
|
||||
}
|
||||
|
||||
func (t *userTable) CreatedAtColumn() Column {
|
||||
return t.columns[columnNameCreatedAt]
|
||||
}
|
||||
|
||||
func (t *userTable) UpdatedAtColumn() Column {
|
||||
return t.columns[columnNameUpdatedAt]
|
||||
}
|
||||
|
||||
func (t *userTable) DeletedAtColumn() Column {
|
||||
return t.columns[columnNameDeletedAt]
|
||||
}
|
||||
|
||||
func NewUserQuery() Query[User] {
|
||||
q := NewQuery[User](UserTable())
|
||||
return q
|
||||
}
|
||||
|
||||
type userByIDCondition[T Text] struct {
|
||||
id T
|
||||
}
|
||||
|
||||
func UserByID[T Text](id T) Condition {
|
||||
return &userByIDCondition[T]{id: id}
|
||||
}
|
||||
|
||||
// writeOn implements Condition.
|
||||
func (u *userByIDCondition[T]) writeOn(builder statementBuilder) {
|
||||
NewTextCondition(builder.table().(*userTable).IDColumn(), TextOperatorEqual, u.id).writeOn(builder)
|
||||
}
|
||||
|
||||
var _ Condition = (*userByIDCondition[string])(nil)
|
||||
|
||||
type userByUsernameCondition[T Text] struct {
|
||||
username T
|
||||
operator TextOperator
|
||||
}
|
||||
|
||||
func UserByUsername[T Text](username T, operator TextOperator) Condition {
|
||||
return &userByUsernameCondition[T]{username: username, operator: operator}
|
||||
}
|
||||
|
||||
// writeOn implements Condition.
|
||||
func (u *userByUsernameCondition[T]) writeOn(builder statementBuilder) {
|
||||
NewTextCondition(builder.table().(*userTable).UsernameColumn(), u.operator, u.username).writeOn(builder)
|
||||
}
|
||||
|
||||
var _ Condition = (*userByUsernameCondition[string])(nil)
|
@@ -1,25 +0,0 @@
|
||||
package v3_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
v3 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v3"
|
||||
)
|
||||
|
||||
type user struct{}
|
||||
|
||||
func TestUser(t *testing.T) {
|
||||
query := v3.NewUserQuery()
|
||||
query.Where(
|
||||
v3.Or(
|
||||
v3.UserByID("123"),
|
||||
v3.UserByUsername("test", v3.TextOperatorStartsWithIgnoreCase),
|
||||
),
|
||||
)
|
||||
query.Limit(10)
|
||||
query.Offset(5)
|
||||
// query.OrderBy(
|
||||
|
||||
query.Result(context.TODO(), nil)
|
||||
}
|
@@ -1,2 +0,0 @@
|
||||
// this test focuses on queries rather than on tables
|
||||
package v4
|
@@ -1,17 +0,0 @@
|
||||
package v4
|
||||
|
||||
type Org struct {
|
||||
InstanceID string
|
||||
ID string
|
||||
Name string
|
||||
}
|
||||
|
||||
type GetOrg struct{}
|
||||
|
||||
type ListOrgs struct{}
|
||||
|
||||
type CreateOrg struct{}
|
||||
|
||||
type UpdateOrg struct{}
|
||||
|
||||
type DeleteOrg struct{}
|
@@ -1,284 +0,0 @@
|
||||
package v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
|
||||
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
|
||||
` FROM users_view`
|
||||
|
||||
type user struct {
|
||||
builder database.StatementBuilder
|
||||
client database.QueryExecutor
|
||||
}
|
||||
|
||||
func UserRepository(client database.QueryExecutor) domain.UserRepository {
|
||||
return &user{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
var _ domain.UserRepository = (*user)(nil)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// Human implements [domain.UserRepository].
|
||||
func (u *user) Human() domain.HumanRepository {
|
||||
return &userHuman{user: u}
|
||||
}
|
||||
|
||||
// Machine implements [domain.UserRepository].
|
||||
func (u *user) Machine() domain.MachineRepository {
|
||||
return &userMachine{user: u}
|
||||
}
|
||||
|
||||
// List implements [domain.UserRepository].
|
||||
func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []*domain.User, err error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
u.builder.WriteString(queryUserStmt)
|
||||
options.WriteCondition(&u.builder)
|
||||
options.WriteOrderBy(&u.builder)
|
||||
options.WriteLimit(&u.builder)
|
||||
options.WriteOffset(&u.builder)
|
||||
|
||||
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
closeErr := rows.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = closeErr
|
||||
}()
|
||||
for rows.Next() {
|
||||
user, err := scanUser(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Get implements [domain.UserRepository].
|
||||
func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.User, error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
u.builder.WriteString(queryUserStmt)
|
||||
options.WriteCondition(&u.builder)
|
||||
options.WriteOrderBy(&u.builder)
|
||||
options.WriteLimit(&u.builder)
|
||||
options.WriteOffset(&u.builder)
|
||||
|
||||
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...))
|
||||
}
|
||||
|
||||
const (
|
||||
createHumanStmt = `INSERT INTO human_users (instance_id, org_id, user_id, username, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
|
||||
` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` +
|
||||
` RETURNING created_at, updated_at`
|
||||
createMachineStmt = `INSERT INTO user_machines (instance_id, org_id, user_id, username, description)` +
|
||||
` VALUES ($1, $2, $3, $4, $5)` +
|
||||
` RETURNING created_at, updated_at`
|
||||
)
|
||||
|
||||
// Create implements [domain.UserRepository].
|
||||
func (u *user) Create(ctx context.Context, user *domain.User) error {
|
||||
u.builder.AppendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
|
||||
switch trait := user.Traits.(type) {
|
||||
case *domain.Human:
|
||||
u.builder.WriteString(createHumanStmt)
|
||||
u.builder.AppendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
|
||||
case *domain.Machine:
|
||||
u.builder.WriteString(createMachineStmt)
|
||||
u.builder.AppendArgs(trait.Description)
|
||||
}
|
||||
return u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
|
||||
}
|
||||
|
||||
// Delete implements [domain.UserRepository].
|
||||
func (u *user) Delete(ctx context.Context, condition database.Condition) error {
|
||||
u.builder.WriteString("DELETE FROM users")
|
||||
u.writeCondition(condition)
|
||||
return u.client.Exec(ctx, u.builder.String(), u.builder.Args()...)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// SetUsername implements [domain.userChanges].
|
||||
func (u user) SetUsername(username string) database.Change {
|
||||
return database.NewChange(u.UsernameColumn(), username)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// InstanceIDCondition implements [domain.userConditions].
|
||||
func (u user) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// OrgIDCondition implements [domain.userConditions].
|
||||
func (u user) OrgIDCondition(orgID string) database.Condition {
|
||||
return database.NewTextCondition(u.OrgIDColumn(), database.TextOperationEqual, orgID)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.userConditions].
|
||||
func (u user) IDCondition(userID string) database.Condition {
|
||||
return database.NewTextCondition(u.IDColumn(), database.TextOperationEqual, userID)
|
||||
}
|
||||
|
||||
// UsernameCondition implements [domain.userConditions].
|
||||
func (u user) UsernameCondition(op database.TextOperation, username string) database.Condition {
|
||||
return database.NewTextCondition(u.UsernameColumn(), op, username)
|
||||
}
|
||||
|
||||
// CreatedAtCondition implements [domain.userConditions].
|
||||
func (u user) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.CreatedAtColumn(), op, createdAt)
|
||||
}
|
||||
|
||||
// UpdatedAtCondition implements [domain.userConditions].
|
||||
func (u user) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
|
||||
}
|
||||
|
||||
// DeletedCondition implements [domain.userConditions].
|
||||
func (u user) DeletedCondition(isDeleted bool) database.Condition {
|
||||
if isDeleted {
|
||||
return database.IsNotNull(u.DeletedAtColumn())
|
||||
}
|
||||
return database.IsNull(u.DeletedAtColumn())
|
||||
}
|
||||
|
||||
// DeletedAtCondition implements [domain.userConditions].
|
||||
func (u user) DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.DeletedAtColumn(), op, deletedAt)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// InstanceIDColumn implements [domain.userColumns].
|
||||
func (user) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn("instance_id")
|
||||
}
|
||||
|
||||
// OrgIDColumn implements [domain.userColumns].
|
||||
func (user) OrgIDColumn() database.Column {
|
||||
return database.NewColumn("org_id")
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.userColumns].
|
||||
func (user) IDColumn() database.Column {
|
||||
return database.NewColumn("id")
|
||||
}
|
||||
|
||||
// UsernameColumn implements [domain.userColumns].
|
||||
func (user) UsernameColumn() database.Column {
|
||||
return database.NewIgnoreCaseColumn("username", "_lower")
|
||||
}
|
||||
|
||||
// FirstNameColumn implements [domain.userColumns].
|
||||
func (user) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn("created_at")
|
||||
}
|
||||
|
||||
// UpdatedAtColumn implements [domain.userColumns].
|
||||
func (user) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn("updated_at")
|
||||
}
|
||||
|
||||
// DeletedAtColumn implements [domain.userColumns].
|
||||
func (user) DeletedAtColumn() database.Column {
|
||||
return database.NewColumn("deleted_at")
|
||||
}
|
||||
|
||||
func (u *user) writeCondition(condition database.Condition) {
|
||||
if condition == nil {
|
||||
return
|
||||
}
|
||||
u.builder.WriteString(" WHERE ")
|
||||
condition.Write(&u.builder)
|
||||
}
|
||||
|
||||
func (u user) columns() database.Columns {
|
||||
return database.Columns{
|
||||
u.InstanceIDColumn(),
|
||||
u.OrgIDColumn(),
|
||||
u.IDColumn(),
|
||||
u.UsernameColumn(),
|
||||
u.CreatedAtColumn(),
|
||||
u.UpdatedAtColumn(),
|
||||
u.DeletedAtColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
func scanUser(scanner database.Scanner) (*domain.User, error) {
|
||||
var (
|
||||
user domain.User
|
||||
human domain.Human
|
||||
email domain.Email
|
||||
phone domain.Phone
|
||||
machine domain.Machine
|
||||
typ domain.UserType
|
||||
)
|
||||
err := scanner.Scan(
|
||||
&user.InstanceID,
|
||||
&user.OrgID,
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&typ,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.DeletedAt,
|
||||
&human.FirstName,
|
||||
&human.LastName,
|
||||
&email.Address,
|
||||
&email.VerifiedAt,
|
||||
&phone.Number,
|
||||
&phone.VerifiedAt,
|
||||
&machine.Description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch typ {
|
||||
case domain.UserTypeHuman:
|
||||
if email.Address != "" {
|
||||
human.Email = &email
|
||||
}
|
||||
if phone.Number != "" {
|
||||
human.Phone = &phone
|
||||
}
|
||||
user.Traits = &human
|
||||
case domain.UserTypeMachine:
|
||||
user.Traits = &machine
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
@@ -1,39 +1,285 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
const queryUserStmt = `SELECT instance_id, org_id, id, username, type, created_at, updated_at, deleted_at,` +
|
||||
` first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at, description` +
|
||||
` FROM users_view`
|
||||
|
||||
type user struct {
|
||||
database.QueryExecutor
|
||||
repository
|
||||
}
|
||||
|
||||
func User(client database.QueryExecutor) domain.UserRepository {
|
||||
// return &user{QueryExecutor: client}
|
||||
return nil
|
||||
}
|
||||
|
||||
// On implements [domain.UserRepository].
|
||||
func (exec *user) On(clauses ...domain.UserClause) domain.UserOperation {
|
||||
return &userOperation{
|
||||
QueryExecutor: exec.QueryExecutor,
|
||||
clauses: clauses,
|
||||
func UserRepository(client database.QueryExecutor) domain.UserRepository {
|
||||
return &user{
|
||||
repository: repository{
|
||||
client: client,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// OnHuman implements [domain.UserRepository].
|
||||
func (exec *user) OnHuman(clauses ...domain.UserClause) domain.HumanOperation {
|
||||
return &humanOperation{
|
||||
userOperation: *exec.On(clauses...).(*userOperation),
|
||||
var _ domain.UserRepository = (*user)(nil)
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// repository
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// Human implements [domain.UserRepository].
|
||||
func (u *user) Human() domain.HumanRepository {
|
||||
return &userHuman{user: u}
|
||||
}
|
||||
|
||||
// Machine implements [domain.UserRepository].
|
||||
func (u *user) Machine() domain.MachineRepository {
|
||||
return &userMachine{user: u}
|
||||
}
|
||||
|
||||
// List implements [domain.UserRepository].
|
||||
func (u *user) List(ctx context.Context, opts ...database.QueryOption) (users []*domain.User, err error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
u.builder.WriteString(queryUserStmt)
|
||||
options.WriteCondition(&u.builder)
|
||||
options.WriteOrderBy(&u.builder)
|
||||
options.WriteLimit(&u.builder)
|
||||
options.WriteOffset(&u.builder)
|
||||
|
||||
rows, err := u.client.Query(ctx, u.builder.String(), u.builder.Args()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
closeErr := rows.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = closeErr
|
||||
}()
|
||||
for rows.Next() {
|
||||
user, err := scanUser(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users = append(users, user)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Get implements [domain.UserRepository].
|
||||
func (u *user) Get(ctx context.Context, opts ...database.QueryOption) (*domain.User, error) {
|
||||
options := new(database.QueryOpts)
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
u.builder.WriteString(queryUserStmt)
|
||||
options.WriteCondition(&u.builder)
|
||||
options.WriteOrderBy(&u.builder)
|
||||
options.WriteLimit(&u.builder)
|
||||
options.WriteOffset(&u.builder)
|
||||
|
||||
return scanUser(u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...))
|
||||
}
|
||||
|
||||
const (
|
||||
createHumanStmt = `INSERT INTO human_users (instance_id, org_id, user_id, username, first_name, last_name, email_address, email_verified_at, phone_number, phone_verified_at)` +
|
||||
` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)` +
|
||||
` RETURNING created_at, updated_at`
|
||||
createMachineStmt = `INSERT INTO user_machines (instance_id, org_id, user_id, username, description)` +
|
||||
` VALUES ($1, $2, $3, $4, $5)` +
|
||||
` RETURNING created_at, updated_at`
|
||||
)
|
||||
|
||||
// Create implements [domain.UserRepository].
|
||||
func (u *user) Create(ctx context.Context, user *domain.User) error {
|
||||
u.builder.AppendArgs(user.InstanceID, user.OrgID, user.ID, user.Username, user.Traits.Type())
|
||||
switch trait := user.Traits.(type) {
|
||||
case *domain.Human:
|
||||
u.builder.WriteString(createHumanStmt)
|
||||
u.builder.AppendArgs(trait.FirstName, trait.LastName, trait.Email.Address, trait.Email.VerifiedAt, trait.Phone.Number, trait.Phone.VerifiedAt)
|
||||
case *domain.Machine:
|
||||
u.builder.WriteString(createMachineStmt)
|
||||
u.builder.AppendArgs(trait.Description)
|
||||
}
|
||||
return u.client.QueryRow(ctx, u.builder.String(), u.builder.Args()...).Scan(&user.CreatedAt, &user.UpdatedAt)
|
||||
}
|
||||
|
||||
// Delete implements [domain.UserRepository].
|
||||
func (u *user) Delete(ctx context.Context, condition database.Condition) error {
|
||||
u.builder.WriteString("DELETE FROM users")
|
||||
u.writeCondition(condition)
|
||||
return u.client.Exec(ctx, u.builder.String(), u.builder.Args()...)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// changes
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// SetUsername implements [domain.userChanges].
|
||||
func (u user) SetUsername(username string) database.Change {
|
||||
return database.NewChange(u.UsernameColumn(), username)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// conditions
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// InstanceIDCondition implements [domain.userConditions].
|
||||
func (u user) InstanceIDCondition(instanceID string) database.Condition {
|
||||
return database.NewTextCondition(u.InstanceIDColumn(), database.TextOperationEqual, instanceID)
|
||||
}
|
||||
|
||||
// OrgIDCondition implements [domain.userConditions].
|
||||
func (u user) OrgIDCondition(orgID string) database.Condition {
|
||||
return database.NewTextCondition(u.OrgIDColumn(), database.TextOperationEqual, orgID)
|
||||
}
|
||||
|
||||
// IDCondition implements [domain.userConditions].
|
||||
func (u user) IDCondition(userID string) database.Condition {
|
||||
return database.NewTextCondition(u.IDColumn(), database.TextOperationEqual, userID)
|
||||
}
|
||||
|
||||
// UsernameCondition implements [domain.userConditions].
|
||||
func (u user) UsernameCondition(op database.TextOperation, username string) database.Condition {
|
||||
return database.NewTextCondition(u.UsernameColumn(), op, username)
|
||||
}
|
||||
|
||||
// CreatedAtCondition implements [domain.userConditions].
|
||||
func (u user) CreatedAtCondition(op database.NumberOperation, createdAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.CreatedAtColumn(), op, createdAt)
|
||||
}
|
||||
|
||||
// UpdatedAtCondition implements [domain.userConditions].
|
||||
func (u user) UpdatedAtCondition(op database.NumberOperation, updatedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.UpdatedAtColumn(), op, updatedAt)
|
||||
}
|
||||
|
||||
// DeletedCondition implements [domain.userConditions].
|
||||
func (u user) DeletedCondition(isDeleted bool) database.Condition {
|
||||
if isDeleted {
|
||||
return database.IsNotNull(u.DeletedAtColumn())
|
||||
}
|
||||
return database.IsNull(u.DeletedAtColumn())
|
||||
}
|
||||
|
||||
// DeletedAtCondition implements [domain.userConditions].
|
||||
func (u user) DeletedAtCondition(op database.NumberOperation, deletedAt time.Time) database.Condition {
|
||||
return database.NewNumberCondition(u.DeletedAtColumn(), op, deletedAt)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------
|
||||
// columns
|
||||
// -------------------------------------------------------------
|
||||
|
||||
// InstanceIDColumn implements [domain.userColumns].
|
||||
func (user) InstanceIDColumn() database.Column {
|
||||
return database.NewColumn("instance_id")
|
||||
}
|
||||
|
||||
// OrgIDColumn implements [domain.userColumns].
|
||||
func (user) OrgIDColumn() database.Column {
|
||||
return database.NewColumn("org_id")
|
||||
}
|
||||
|
||||
// IDColumn implements [domain.userColumns].
|
||||
func (user) IDColumn() database.Column {
|
||||
return database.NewColumn("id")
|
||||
}
|
||||
|
||||
// UsernameColumn implements [domain.userColumns].
|
||||
func (user) UsernameColumn() database.Column {
|
||||
return database.NewIgnoreCaseColumn("username", "_lower")
|
||||
}
|
||||
|
||||
// FirstNameColumn implements [domain.userColumns].
|
||||
func (user) CreatedAtColumn() database.Column {
|
||||
return database.NewColumn("created_at")
|
||||
}
|
||||
|
||||
// UpdatedAtColumn implements [domain.userColumns].
|
||||
func (user) UpdatedAtColumn() database.Column {
|
||||
return database.NewColumn("updated_at")
|
||||
}
|
||||
|
||||
// DeletedAtColumn implements [domain.userColumns].
|
||||
func (user) DeletedAtColumn() database.Column {
|
||||
return database.NewColumn("deleted_at")
|
||||
}
|
||||
|
||||
func (u *user) writeCondition(condition database.Condition) {
|
||||
if condition == nil {
|
||||
return
|
||||
}
|
||||
u.builder.WriteString(" WHERE ")
|
||||
condition.Write(&u.builder)
|
||||
}
|
||||
|
||||
func (u user) columns() database.Columns {
|
||||
return database.Columns{
|
||||
u.InstanceIDColumn(),
|
||||
u.OrgIDColumn(),
|
||||
u.IDColumn(),
|
||||
u.UsernameColumn(),
|
||||
u.CreatedAtColumn(),
|
||||
u.UpdatedAtColumn(),
|
||||
u.DeletedAtColumn(),
|
||||
}
|
||||
}
|
||||
|
||||
// OnMachine implements [domain.UserRepository].
|
||||
func (exec *user) OnMachine(clauses ...domain.UserClause) domain.MachineOperation {
|
||||
return &machineOperation{
|
||||
userOperation: *exec.On(clauses...).(*userOperation),
|
||||
func scanUser(scanner database.Scanner) (*domain.User, error) {
|
||||
var (
|
||||
user domain.User
|
||||
human domain.Human
|
||||
email domain.Email
|
||||
phone domain.Phone
|
||||
machine domain.Machine
|
||||
typ domain.UserType
|
||||
)
|
||||
err := scanner.Scan(
|
||||
&user.InstanceID,
|
||||
&user.OrgID,
|
||||
&user.ID,
|
||||
&user.Username,
|
||||
&typ,
|
||||
&user.CreatedAt,
|
||||
&user.UpdatedAt,
|
||||
&user.DeletedAt,
|
||||
&human.FirstName,
|
||||
&human.LastName,
|
||||
&email.Address,
|
||||
&email.VerifiedAt,
|
||||
&phone.Number,
|
||||
&phone.VerifiedAt,
|
||||
&machine.Description,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// var _ domain.UserRepository = (*user)(nil)
|
||||
switch typ {
|
||||
case domain.UserTypeHuman:
|
||||
if email.Address != "" {
|
||||
human.Email = &email
|
||||
}
|
||||
if phone.Number != "" {
|
||||
human.Phone = &phone
|
||||
}
|
||||
user.Traits = &human
|
||||
case domain.UserTypeMachine:
|
||||
user.Traits = &machine
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package v4
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
@@ -1,36 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
)
|
||||
|
||||
type humanOperation struct {
|
||||
userOperation
|
||||
}
|
||||
|
||||
// GetEmail implements domain.HumanOperation.
|
||||
func (h *humanOperation) GetEmail(ctx context.Context) (*domain.Email, error) {
|
||||
var email domain.Email
|
||||
err := h.QueryExecutor.QueryRow(ctx, `SELECT email, is_email_verified FROM human_users WHERE id = $1`, h.clauses).Scan(
|
||||
&email.Address,
|
||||
&email.IsVerified,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &email, nil
|
||||
}
|
||||
|
||||
// SetEmail implements domain.HumanOperation.
|
||||
func (h *humanOperation) SetEmail(ctx context.Context, email string) error {
|
||||
return h.QueryExecutor.Exec(ctx, `UPDATE human_users SET email = $1 WHERE id = $2`, email, h.clauses)
|
||||
}
|
||||
|
||||
// SetEmailVerified implements domain.HumanOperation.
|
||||
func (h *humanOperation) SetEmailVerified(ctx context.Context, email string) error {
|
||||
return h.QueryExecutor.Exec(ctx, `UPDATE human_users SET is_email_verified = $1 WHERE id = $2 AND email = $3`, true, h.clauses, email)
|
||||
}
|
||||
|
||||
var _ domain.HumanOperation = (*humanOperation)(nil)
|
@@ -1,4 +1,4 @@
|
||||
package v4
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
@@ -1,18 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
)
|
||||
|
||||
type machineOperation struct {
|
||||
userOperation
|
||||
}
|
||||
|
||||
// SetDescription implements domain.MachineOperation.
|
||||
func (m *machineOperation) SetDescription(ctx context.Context, description string) error {
|
||||
return m.QueryExecutor.Exec(ctx, `UPDATE machines SET description = $1 WHERE id = $2`, description, m.clauses)
|
||||
}
|
||||
|
||||
var _ domain.MachineOperation = (*machineOperation)(nil)
|
@@ -1,68 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type userOperation struct {
|
||||
database.QueryExecutor
|
||||
clauses []domain.UserClause
|
||||
}
|
||||
|
||||
// Delete implements [domain.UserOperation].
|
||||
func (u *userOperation) Delete(ctx context.Context) error {
|
||||
return u.QueryExecutor.Exec(ctx, `DELETE FROM users WHERE id = $1`, u.clauses)
|
||||
}
|
||||
|
||||
// SetUsername implements [domain.UserOperation].
|
||||
func (u *userOperation) SetUsername(ctx context.Context, username string) error {
|
||||
var stmt statement
|
||||
|
||||
stmt.builder.WriteString(`UPDATE users SET username = $1 WHERE `)
|
||||
stmt.appendArg(username)
|
||||
clausesToSQL(&stmt, u.clauses)
|
||||
return u.QueryExecutor.Exec(ctx, stmt.builder.String(), stmt.args...)
|
||||
}
|
||||
|
||||
var _ domain.UserOperation = (*userOperation)(nil)
|
||||
|
||||
func UserIDQuery(id string) domain.UserClause {
|
||||
return textClause[string]{
|
||||
clause: clause[database.TextOperation]{
|
||||
field: userFields[domain.UserFieldID],
|
||||
op: database.TextOperationEqual,
|
||||
},
|
||||
value: id,
|
||||
}
|
||||
}
|
||||
|
||||
func HumanEmailQuery(op database.TextOperation, email string) domain.UserClause {
|
||||
return textClause[string]{
|
||||
clause: clause[database.TextOperation]{
|
||||
field: userFields[domain.UserHumanFieldEmail],
|
||||
op: op,
|
||||
},
|
||||
value: email,
|
||||
}
|
||||
}
|
||||
|
||||
func HumanEmailVerifiedQuery(op database.BoolOperation) domain.UserClause {
|
||||
return boolClause[database.BoolOperation]{
|
||||
clause: clause[database.BoolOperation]{
|
||||
field: userFields[domain.UserHumanFieldEmailVerified],
|
||||
op: op,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func clausesToSQL(stmt *statement, clauses []domain.UserClause) {
|
||||
for _, clause := range clauses {
|
||||
|
||||
stmt.builder.WriteString(userFields[clause.Field()].String())
|
||||
stmt.builder.WriteString(clause.Operation().String())
|
||||
stmt.appendArg(clause.Args()...)
|
||||
}
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package v4_test
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
@@ -6,12 +6,16 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
v4 "github.com/zitadel/zitadel/backend/v3/storage/database/repository/stmt/v4"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/dbmock"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"go.uber.org/mock/gomock"
|
||||
)
|
||||
|
||||
func TestQueryUser(t *testing.T) {
|
||||
t.Run("User filters", func(t *testing.T) {
|
||||
user := v4.UserRepository(nil)
|
||||
client := dbmock.NewMockClient(gomock.NewController(t))
|
||||
|
||||
user := repository.UserRepository(client)
|
||||
u, err := user.Get(context.Background(),
|
||||
database.WithCondition(
|
||||
database.And(
|
||||
@@ -30,7 +34,9 @@ func TestQueryUser(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("machine and human filters", func(t *testing.T) {
|
||||
user := v4.UserRepository(nil)
|
||||
client := dbmock.NewMockClient(gomock.NewController(t))
|
||||
|
||||
user := repository.UserRepository(client)
|
||||
machine := user.Machine()
|
||||
human := user.Human()
|
||||
email, err := human.GetEmail(context.Background(), database.And(
|
||||
@@ -62,7 +68,7 @@ func TestArg(t *testing.T) {
|
||||
|
||||
func TestWriteUser(t *testing.T) {
|
||||
t.Run("update user", func(t *testing.T) {
|
||||
user := v4.UserRepository(nil)
|
||||
user := repository.UserRepository(nil)
|
||||
user.Human().Update(context.Background(), user.IDCondition("test"), user.SetUsername("test"))
|
||||
})
|
||||
}
|
@@ -12,16 +12,19 @@ const (
|
||||
NullInstruction Instruction = "NULL"
|
||||
)
|
||||
|
||||
// StatementBuilder is a helper to build SQL statement.
|
||||
type StatementBuilder struct {
|
||||
strings.Builder
|
||||
args []any
|
||||
existingArgs map[any]string
|
||||
}
|
||||
|
||||
// WriteArgs adds the argument to the statement and writes the placeholder to the query.
|
||||
func (b *StatementBuilder) WriteArg(arg any) {
|
||||
b.WriteString(b.AppendArg(arg))
|
||||
}
|
||||
|
||||
// AppebdArg adds the argument to the statement and returns the placeholder.
|
||||
func (b *StatementBuilder) AppendArg(arg any) (placeholder string) {
|
||||
if b.existingArgs == nil {
|
||||
b.existingArgs = make(map[any]string)
|
||||
@@ -39,12 +42,14 @@ func (b *StatementBuilder) AppendArg(arg any) (placeholder string) {
|
||||
return placeholder
|
||||
}
|
||||
|
||||
// AppendArgs adds the arguments to the statement and doesn't return the placeholders.
|
||||
func (b *StatementBuilder) AppendArgs(args ...any) {
|
||||
for _, arg := range args {
|
||||
b.AppendArg(arg)
|
||||
}
|
||||
}
|
||||
|
||||
// Args returns the arguments added to the statement.
|
||||
func (b *StatementBuilder) Args() []any {
|
||||
return b.args
|
||||
}
|
||||
|
Reference in New Issue
Block a user