From bb2d0aff3f7581f757b65a6c308bea3984841dbf Mon Sep 17 00:00:00 2001 From: adlerhurst <27845747+adlerhurst@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:32:50 +0200 Subject: [PATCH] get / list implemented --- backend/v3/domain/instance_domain.go | 8 +++ backend/v3/domain/organization.go | 6 -- backend/v3/domain/organization_domain.go | 8 +++ backend/v3/storage/database/query.go | 21 ++++++ .../database/repository/instance_domain.go | 66 ++++++++++++++++- backend/v3/storage/database/repository/org.go | 39 +--------- .../storage/database/repository/org_domain.go | 71 +++++++++++++++++-- backend/v3/storage/database/statement.go | 2 +- 8 files changed, 168 insertions(+), 53 deletions(-) diff --git a/backend/v3/domain/instance_domain.go b/backend/v3/domain/instance_domain.go index 96fb4971b9..db648b6740 100644 --- a/backend/v3/domain/instance_domain.go +++ b/backend/v3/domain/instance_domain.go @@ -52,6 +52,14 @@ type InstanceDomainRepository interface { instanceDomainConditions instanceDomainChanges + // Get returns a single domain based on the criteria. + // If no domain is found, it returns an error of type [database.ErrNotFound]. + // If multiple domains are found, it returns an error of type [database.ErrMultipleRows]. + Get(ctx context.Context, opts ...database.QueryOption) (*InstanceDomain, error) + // List returns a list of domains based on the criteria. + // If no domains are found, it returns an empty slice. + List(ctx context.Context, opts ...database.QueryOption) ([]*InstanceDomain, error) + // Add adds a new domain to the instance. Add(ctx context.Context, domain *AddInstanceDomain) error // Update updates an existing domain in the instance. diff --git a/backend/v3/domain/organization.go b/backend/v3/domain/organization.go index d8ee6f80cc..df497d2c43 100644 --- a/backend/v3/domain/organization.go +++ b/backend/v3/domain/organization.go @@ -94,9 +94,3 @@ type MemberRepository interface { RemoveMember(ctx context.Context, orgID, userID string) error } -// DomainRepository is a sub repository of the org repository and maybe the instance repository. -type DomainRepository interface { - AddDomain(ctx context.Context, domain string) error - SetDomainVerified(ctx context.Context, domain string) error - RemoveDomain(ctx context.Context, domain string) error -} diff --git a/backend/v3/domain/organization_domain.go b/backend/v3/domain/organization_domain.go index 94b0386b0f..7e244dd401 100644 --- a/backend/v3/domain/organization_domain.go +++ b/backend/v3/domain/organization_domain.go @@ -56,6 +56,14 @@ type OrganizationDomainRepository interface { organizationDomainConditions organizationDomainChanges + // Get returns a single domain based on the criteria. + // If no domain is found, it returns an error of type [database.ErrNotFound]. + // If multiple domains are found, it returns an error of type [database.ErrMultipleRows]. + Get(ctx context.Context, opts ...database.QueryOption) (*OrganizationDomain, error) + // List returns a list of domains based on the criteria. + // If no domains are found, it returns an empty slice. + List(ctx context.Context, opts ...database.QueryOption) ([]*OrganizationDomain, error) + // Add adds a new domain to the organization. Add(ctx context.Context, domain *AddOrganizationDomain) error // Update updates an existing domain in the organization. diff --git a/backend/v3/storage/database/query.go b/backend/v3/storage/database/query.go index d0ef78f633..9ae8b10804 100644 --- a/backend/v3/storage/database/query.go +++ b/backend/v3/storage/database/query.go @@ -2,37 +2,58 @@ package database type QueryOption func(opts *QueryOpts) +// WithCondition sets the condition for the query. func WithCondition(condition Condition) QueryOption { return func(opts *QueryOpts) { opts.Condition = condition } } +// WithOrderBy sets the columns to order the results by. func WithOrderBy(orderBy ...Column) QueryOption { return func(opts *QueryOpts) { opts.OrderBy = orderBy } } +// WithLimit sets the maximum number of results to return. func WithLimit(limit uint32) QueryOption { return func(opts *QueryOpts) { opts.Limit = limit } } +// WithOffset sets the number of results to skip before returning the results. func WithOffset(offset uint32) QueryOption { return func(opts *QueryOpts) { opts.Offset = offset } } +// QueryOpts holds the options for a query. +// It is used to build the SQL SELECT statement. type QueryOpts struct { + // Condition is the condition to filter the results. + // It is used to build the WHERE clause of the SQL statement. Condition Condition + // OrderBy is the columns to order the results by. + // It is used to build the ORDER BY clause of the SQL statement. OrderBy Columns + // Limit is the maximum number of results to return. + // It is used to build the LIMIT clause of the SQL statement. Limit uint32 + // Offset is the number of results to skip before returning the results. + // It is used to build the OFFSET clause of the SQL statement. Offset uint32 } +func (opts *QueryOpts) Write(builder *StatementBuilder) { + opts.WriteCondition(builder) + opts.WriteOrderBy(builder) + opts.WriteLimit(builder) + opts.WriteOffset(builder) +} + func (opts *QueryOpts) WriteCondition(builder *StatementBuilder) { if opts.Condition == nil { return diff --git a/backend/v3/storage/database/repository/instance_domain.go b/backend/v3/storage/database/repository/instance_domain.go index 9e09a96f85..ae3169e83e 100644 --- a/backend/v3/storage/database/repository/instance_domain.go +++ b/backend/v3/storage/database/repository/instance_domain.go @@ -18,6 +18,39 @@ type instanceDomain struct { // repository // ------------------------------------------------------------- +const queryInstanceDomainStmt = `SELECT instance_id, domain, is_verified, is_primary, verification_type, created_at, updated_at ` + + `FROM zitadel.instance_domains` + +// Get implements [domain.InstanceDomainRepository]. +// Subtle: this method shadows the method ([domain.InstanceRepository]).Get of instanceDomain.instance. +func (i *instanceDomain) Get(ctx context.Context, opts ...database.QueryOption) (*domain.InstanceDomain, error) { + options := new(database.QueryOpts) + for _, opt := range opts { + opt(options) + } + + var builder database.StatementBuilder + builder.WriteString(queryInstanceDomainStmt) + options.Write(&builder) + + return scanInstanceDomain(ctx, i.client, &builder) +} + +// List implements [domain.InstanceDomainRepository]. +// Subtle: this method shadows the method ([domain.InstanceRepository]).List of instanceDomain.instance. +func (i *instanceDomain) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.InstanceDomain, error) { + options := new(database.QueryOpts) + for _, opt := range opts { + opt(options) + } + + var builder database.StatementBuilder + builder.WriteString(queryInstanceDomainStmt) + options.Write(&builder) + + return scanInstanceDomains(ctx, i.client, &builder) +} + // Add implements [domain.InstanceDomainRepository]. func (i *instanceDomain) Add(ctx context.Context, domain *domain.AddInstanceDomain) error { var builder database.StatementBuilder @@ -42,7 +75,7 @@ func (i *instanceDomain) Remove(ctx context.Context, condition database.Conditio } // Update implements [domain.InstanceDomainRepository]. -// Subtle: this method shadows the method (instance).Update of instanceDomain.instance. +// Subtle: this method shadows the method ([domain.InstanceRepository]).Update of instanceDomain.instance. func (i *instanceDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) { var builder database.StatementBuilder @@ -102,7 +135,7 @@ func (i instanceDomain) IsVerifiedCondition(isVerified bool) database.Condition // ------------------------------------------------------------- // CreatedAtColumn implements [domain.InstanceDomainRepository]. -// Subtle: this method shadows the method (instance).CreatedAtColumn of instanceDomain.instance. +// Subtle: this method shadows the method ([domain.InstanceRepository]).CreatedAtColumn of instanceDomain.instance. func (instanceDomain) CreatedAtColumn() database.Column { return database.NewColumn("created_at") } @@ -128,7 +161,7 @@ func (instanceDomain) IsVerifiedColumn() database.Column { } // UpdatedAtColumn implements [domain.InstanceDomainRepository]. -// Subtle: this method shadows the method (instance).UpdatedAtColumn of instanceDomain.instance. +// Subtle: this method shadows the method ([domain.InstanceRepository]).UpdatedAtColumn of instanceDomain.instance. func (instanceDomain) UpdatedAtColumn() database.Column { return database.NewColumn("updated_at") } @@ -146,3 +179,30 @@ func (instanceDomain) IsGeneratedColumn() database.Column { // ------------------------------------------------------------- // scanners // ------------------------------------------------------------- + +func scanInstanceDomains(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) ([]*domain.InstanceDomain, error) { + rows, err := querier.Query(ctx, builder.String(), builder.Args()...) + if err != nil { + return nil, err + } + + var instanceDomains []*domain.InstanceDomain + if err := rows.(database.CollectableRows).Collect(&instanceDomains); err != nil { + return nil, err + } + + return instanceDomains, nil +} + +func scanInstanceDomain(ctx context.Context, querier database.Querier, builder *database.StatementBuilder) (*domain.InstanceDomain, error) { + rows, err := querier.Query(ctx, builder.String(), builder.Args()...) + if err != nil { + return nil, err + } + instanceDomain := &domain.InstanceDomain{} + if err := rows.(database.CollectableRows).CollectExactlyOneRow(instanceDomain); err != nil { + return nil, err + } + + return instanceDomain, nil +} \ No newline at end of file diff --git a/backend/v3/storage/database/repository/org.go b/backend/v3/storage/database/repository/org.go index 4e85c313cf..470bed8af9 100644 --- a/backend/v3/storage/database/repository/org.go +++ b/backend/v3/storage/database/repository/org.go @@ -4,8 +4,6 @@ import ( "context" "errors" - "github.com/jackc/pgx/v5/pgconn" - "github.com/zitadel/zitadel/backend/v3/domain" "github.com/zitadel/zitadel/backend/v3/storage/database" ) @@ -73,44 +71,9 @@ func (o *org) Create(ctx context.Context, organization *domain.Organization) err builder.AppendArgs(organization.ID, organization.Name, organization.InstanceID, organization.State) builder.WriteString(createOrganizationStmt) - err := o.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&organization.CreatedAt, &organization.UpdatedAt) - if err != nil { - return checkCreateOrgErr(err) - } - return nil + return o.client.QueryRow(ctx, builder.String(), builder.Args()...).Scan(&organization.CreatedAt, &organization.UpdatedAt) } -func checkCreateOrgErr(err error) error { - var pgErr *pgconn.PgError - if !errors.As(err, &pgErr) { - return err - } - // constraint violation - if pgErr.Code == "23514" { - if pgErr.ConstraintName == "organizations_name_check" { - return errors.New("organization name not provided") - } - if pgErr.ConstraintName == "organizations_id_check" { - return errors.New("organization id not provided") - } - } - // duplicate - if pgErr.Code == "23505" { - if pgErr.ConstraintName == "organizations_pkey" { - return errors.New("organization id already exists") - } - if pgErr.ConstraintName == "org_unique_instance_id_name_idx" { - return errors.New("organization name already exists for instance") - } - } - // invalid instance id - if pgErr.Code == "23503" { - if pgErr.ConstraintName == "organizations_instance_id_fkey" { - return errors.New("invalid instance id") - } - } - return err -} // Update implements [domain.OrganizationRepository]. func (o *org) Update(ctx context.Context, id domain.OrgIdentifierCondition, instanceID string, changes ...database.Change) (int64, error) { diff --git a/backend/v3/storage/database/repository/org_domain.go b/backend/v3/storage/database/repository/org_domain.go index 393ca4606a..74b6c403a2 100644 --- a/backend/v3/storage/database/repository/org_domain.go +++ b/backend/v3/storage/database/repository/org_domain.go @@ -14,10 +14,45 @@ type orgDomain struct { *org } + + // ------------------------------------------------------------- // repository // ------------------------------------------------------------- +const queryOrganizationDomainStmt = `SELECT instance_id, org_id, domain, is_verified, is_primary, verification_type, created_at, updated_at ` + + `FROM zitadel.organization_domains` + +// Get implements [domain.OrganizationDomainRepository]. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).Get of orgDomain.org. +func (o *orgDomain) Get(ctx context.Context, opts ...database.QueryOption) (*domain.OrganizationDomain, error) { + options := new(database.QueryOpts) + for _, opt := range opts { + opt(options) + } + + var builder database.StatementBuilder + builder.WriteString(queryOrganizationDomainStmt) + options.Write(&builder) + + return scanOrganizationDomain(ctx, o.client, &builder) +} + +// List implements [domain.OrganizationDomainRepository]. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).List of orgDomain.org. +func (o *orgDomain) List(ctx context.Context, opts ...database.QueryOption) ([]*domain.OrganizationDomain, error) { + options := new(database.QueryOpts) + for _, opt := range opts { + opt(options) + } + + var builder database.StatementBuilder + builder.WriteString(queryOrganizationDomainStmt) + options.Write(&builder) + + return scanOrganizationDomains(ctx, o.client, &builder) +} + // Add implements [domain.OrganizationDomainRepository]. func (o *orgDomain) Add(ctx context.Context, domain *domain.AddOrganizationDomain) error { var builder database.StatementBuilder @@ -32,7 +67,7 @@ func (o *orgDomain) Add(ctx context.Context, domain *domain.AddOrganizationDomai } // Update implements [domain.OrganizationDomainRepository]. -// Subtle: this method shadows the method (*org).Update of orgDomain.org. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).Update of orgDomain.org. func (o *orgDomain) Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error) { var builder database.StatementBuilder @@ -83,7 +118,7 @@ func (o orgDomain) DomainCondition(op database.TextOperation, domain string) dat } // InstanceIDCondition implements [domain.OrganizationDomainRepository]. -// Subtle: this method shadows the method (*org).InstanceIDCondition of orgDomain.org. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).InstanceIDCondition of orgDomain.org. func (o orgDomain) InstanceIDCondition(instanceID string) database.Condition { return database.NewTextCondition(o.InstanceIDColumn(), database.TextOperationEqual, instanceID) } @@ -108,7 +143,7 @@ func (o orgDomain) OrgIDCondition(orgID string) database.Condition { // ------------------------------------------------------------- // CreatedAtColumn implements [domain.OrganizationDomainRepository]. -// Subtle: this method shadows the method (*org).CreatedAtColumn of orgDomain.org. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).CreatedAtColumn of orgDomain.org. func (orgDomain) CreatedAtColumn() database.Column { return database.NewColumn("created_at") } @@ -119,7 +154,7 @@ func (orgDomain) DomainColumn() database.Column { } // InstanceIDColumn implements [domain.OrganizationDomainRepository]. -// Subtle: this method shadows the method (*org).InstanceIDColumn of orgDomain.org. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).InstanceIDColumn of orgDomain.org. func (orgDomain) InstanceIDColumn() database.Column { return database.NewColumn("instance_id") } @@ -140,7 +175,7 @@ func (orgDomain) OrgIDColumn() database.Column { } // UpdatedAtColumn implements [domain.OrganizationDomainRepository]. -// Subtle: this method shadows the method (*org).UpdatedAtColumn of orgDomain.org. +// Subtle: this method shadows the method ([domain.OrganizationRepository]).UpdatedAtColumn of orgDomain.org. func (orgDomain) UpdatedAtColumn() database.Column { return database.NewColumn("updated_at") } @@ -153,3 +188,29 @@ func (orgDomain) VerificationTypeColumn() database.Column { // ------------------------------------------------------------- // scanners // ------------------------------------------------------------- + +func scanOrganizationDomain(ctx context.Context, client database.Querier, builder *database.StatementBuilder) (*domain.OrganizationDomain, error) { + rows, err := client.Query(ctx, builder.String(), builder.Args()...) + if err != nil { + return nil, err + } + + organizationDomain := &domain.OrganizationDomain{} + if err := rows.(database.CollectableRows).CollectExactlyOneRow(organizationDomain); err != nil { + return nil, err + } + return organizationDomain, nil +} + +func scanOrganizationDomains(ctx context.Context, client database.Querier, builder *database.StatementBuilder) ([]*domain.OrganizationDomain, error) { + rows, err := client.Query(ctx, builder.String(), builder.Args()...) + if err != nil { + return nil, err + } + + var organizationDomains []*domain.OrganizationDomain + if err := rows.(database.CollectableRows).Collect(&organizationDomains); err != nil { + return nil, err + } + return organizationDomains, nil +} diff --git a/backend/v3/storage/database/statement.go b/backend/v3/storage/database/statement.go index 7d779fe360..2a038af549 100644 --- a/backend/v3/storage/database/statement.go +++ b/backend/v3/storage/database/statement.go @@ -24,7 +24,7 @@ func (b *StatementBuilder) WriteArg(arg any) { b.WriteString(b.AppendArg(arg)) } -// AppebdArg adds the argument to the statement and returns the placeholder. +// AppendArg 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)