mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:07:31 +00:00
implementation done
This commit is contained in:
23
backend/v3/api/database.go
Normal file
23
backend/v3/api/database.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
v2beta "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
)
|
||||
|
||||
func V2BetaTextFilterToDatabase(filter v2beta.TextQueryMethod) database.TextOperation {
|
||||
switch filter {
|
||||
case v2beta.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS:
|
||||
return database.TextOperationEqual
|
||||
case v2beta.TextQueryMethod_TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE:
|
||||
return database.TextOperationEqualIgnoreCase
|
||||
case v2beta.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH:
|
||||
return database.TextOperationStartsWith
|
||||
case v2beta.TextQueryMethod_TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE:
|
||||
return database.TextOperationStartsWithIgnoreCase
|
||||
case v2beta.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS, v2beta.TextQueryMethod_TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE, v2beta.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH, v2beta.TextQueryMethod_TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE:
|
||||
panic("unimplemented text query method: " + filter.String())
|
||||
default:
|
||||
panic("unknown text query method: " + filter.String())
|
||||
}
|
||||
}
|
27
backend/v3/api/domain.go
Normal file
27
backend/v3/api/domain.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
)
|
||||
|
||||
func V2BetaOrgStateToDomain(state org.OrgState) domain.OrgState {
|
||||
switch state {
|
||||
case org.OrgState_ORG_STATE_ACTIVE:
|
||||
return domain.OrgStateActive
|
||||
case org.OrgState_ORG_STATE_INACTIVE:
|
||||
return domain.OrgStateInactive
|
||||
default:
|
||||
// TODO: removed is not supported in the domain
|
||||
panic("unknown org state: " + state.String())
|
||||
}
|
||||
}
|
||||
|
||||
func V2BetaPaginationToDomain(pagination *filter.PaginationRequest) domain.Pagination {
|
||||
return domain.Pagination{
|
||||
Limit: pagination.Limit,
|
||||
Offset: uint32(pagination.Offset),
|
||||
Ascending: pagination.Asc,
|
||||
}
|
||||
}
|
@@ -2,70 +2,197 @@ package org
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api"
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
org "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
)
|
||||
|
||||
// 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
|
||||
// }
|
||||
|
||||
// ActivateOrganization implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) ActivateOrganization(ctx context.Context, req *connect.Request[org.ActivateOrganizationRequest]) (*connect.Response[org.ActivateOrganizationResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.ActivateOrganization(ctx, req)
|
||||
err := domain.Invoke(ctx, domain.NewActivateOrganizationCommand(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
req.Msg.GetId(),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// DISCUSS(adlerhurst): does returning the ChangeDate bring any value?
|
||||
return connect.NewResponse(&org.ActivateOrganizationResponse{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// CreateOrganization implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) CreateOrganization(ctx context.Context, req *connect.Request[org.CreateOrganizationRequest]) (*connect.Response[org.CreateOrganizationResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.CreateOrganization(ctx, req)
|
||||
// TODO: Implement admins
|
||||
opts := make([]domain.CreateOrganizationCommandOpts, 0, 1+len(req.Msg.Admins))
|
||||
if req.Msg.Id != nil {
|
||||
opts = append(opts, domain.WithOrganizationID(req.Msg.GetId()))
|
||||
}
|
||||
cmd := domain.NewCreateOrganizationCommand(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
req.Msg.GetName(),
|
||||
opts...,
|
||||
)
|
||||
err := domain.Invoke(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&org.CreateOrganizationResponse{
|
||||
Id: cmd.ID,
|
||||
CreationDate: timestamppb.New(cmd.CreatedAt),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// DeactivateOrganization implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) DeactivateOrganization(ctx context.Context, req *connect.Request[org.DeactivateOrganizationRequest]) (*connect.Response[org.DeactivateOrganizationResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.DeactivateOrganization(ctx, req)
|
||||
err := domain.Invoke(ctx, domain.NewDeactivateOrganizationCommand(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
req.Msg.GetId(),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// DISCUSS(adlerhurst): does returning the ChangeDate bring any value?
|
||||
return connect.NewResponse(&org.DeactivateOrganizationResponse{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// DeleteOrganization implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) DeleteOrganization(ctx context.Context, req *connect.Request[org.DeleteOrganizationRequest]) (*connect.Response[org.DeleteOrganizationResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.DeleteOrganization(ctx, req)
|
||||
err := domain.Invoke(ctx, domain.NewDeleteOrganizationCommand(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
req.Msg.GetId(),
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// DISCUSS(adlerhurst): does returning the DeletionDate bring any value?
|
||||
return connect.NewResponse(&org.DeleteOrganizationResponse{
|
||||
DeletionDate: timestamppb.Now(),
|
||||
}), nil
|
||||
}
|
||||
|
||||
// ListOrganizations implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) ListOrganizations(ctx context.Context, req *connect.Request[org.ListOrganizationsRequest]) (*connect.Response[org.ListOrganizationsResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.ListOrganizations(ctx, req)
|
||||
opts := orgFiltersToDomain(req.Msg.GetFilter())
|
||||
opts = slices.Grow(opts, 2)
|
||||
opts = append(opts, api.V2BetaPaginationToDomain(req.Msg.GetPagination()))
|
||||
if req.Msg.SortingColumn != org.OrgFieldName_ORG_FIELD_NAME_UNSPECIFIED {
|
||||
opts = append(opts, domain.WithOrgQuerySortingColumn(orgFieldNameToDatabase(req.Msg.SortingColumn)))
|
||||
}
|
||||
|
||||
query := domain.NewOrgsQuery(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
opts...,
|
||||
)
|
||||
err := domain.Invoke(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&org.ListOrganizationsResponse{
|
||||
Organizations: orgsFromDomain(query.Result),
|
||||
Pagination: &filter.PaginationResponse{
|
||||
AppliedLimit: uint64(req.Msg.Pagination.Limit),
|
||||
// TotalResult: TODO(adlerhurst): needs implementation in lower layers
|
||||
},
|
||||
}), nil
|
||||
}
|
||||
|
||||
// UpdateOrganization implements [orgconnect.OrganizationServiceHandler].
|
||||
func (s *Server) UpdateOrganization(ctx context.Context, req *connect.Request[org.UpdateOrganizationRequest]) (*connect.Response[org.UpdateOrganizationResponse], error) {
|
||||
return s.UnimplementedOrganizationServiceHandler.UpdateOrganization(ctx, req)
|
||||
opts := make([]domain.UpdateOrganizationCommandOpts, 0, 1)
|
||||
if req.Msg.Name != "" {
|
||||
opts = append(opts, domain.WithOrganizationName(req.Msg.GetName()))
|
||||
}
|
||||
|
||||
err := domain.Invoke(ctx, domain.NewUpdateOrganizationCommand(
|
||||
authz.GetInstance(ctx).InstanceID(),
|
||||
req.Msg.GetId(),
|
||||
opts...,
|
||||
))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return connect.NewResponse(&org.UpdateOrganizationResponse{
|
||||
ChangeDate: timestamppb.Now(),
|
||||
}), nil
|
||||
}
|
||||
|
||||
func orgFiltersToDomain(filters []*org.OrganizationSearchFilter) []domain.OrgsQueryOpts {
|
||||
opts := make([]domain.OrgsQueryOpts, len(filters))
|
||||
for i, filter := range filters {
|
||||
opts[i] = orgFilterToDomain(filter)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func orgFilterToDomain(filter *org.OrganizationSearchFilter) domain.OrgsQueryOpts {
|
||||
switch f := filter.Filter.(type) {
|
||||
case *org.OrganizationSearchFilter_NameFilter:
|
||||
return domain.WithOrgByNameQuery(api.V2BetaTextFilterToDatabase(f.NameFilter.Method), f.NameFilter.Name)
|
||||
case *org.OrganizationSearchFilter_DomainFilter:
|
||||
return domain.WithOrgByDomainQuery(api.V2BetaTextFilterToDatabase(f.DomainFilter.Method), f.DomainFilter.Domain)
|
||||
case *org.OrganizationSearchFilter_IdFilter:
|
||||
return domain.WithOrgByIDQuery(f.IdFilter.Id)
|
||||
case *org.OrganizationSearchFilter_StateFilter:
|
||||
return domain.WithOrgByStateQuery(api.V2BetaOrgStateToDomain(f.StateFilter.State))
|
||||
default:
|
||||
panic("unknown organization search filter: " + filter.String())
|
||||
}
|
||||
}
|
||||
|
||||
func orgsFromDomain(orgs []*domain.Organization) []*org.Organization {
|
||||
result := make([]*org.Organization, len(orgs))
|
||||
for i, o := range orgs {
|
||||
result[i] = &org.Organization{
|
||||
Id: o.ID,
|
||||
Name: o.Name,
|
||||
State: orgStateFromDomain(o.State),
|
||||
CreationDate: timestamppb.New(o.CreatedAt),
|
||||
ChangedDate: timestamppb.New(o.UpdatedAt),
|
||||
PrimaryDomain: orgPrimaryDomainFromDomain(o.Domains),
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func orgStateFromDomain(state domain.OrgState) org.OrgState {
|
||||
switch state {
|
||||
case domain.OrgStateActive:
|
||||
return org.OrgState_ORG_STATE_ACTIVE
|
||||
case domain.OrgStateInactive:
|
||||
return org.OrgState_ORG_STATE_INACTIVE
|
||||
default:
|
||||
return org.OrgState_ORG_STATE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func orgPrimaryDomainFromDomain(domains []*domain.OrganizationDomain) string {
|
||||
for _, d := range domains {
|
||||
if d.IsPrimary {
|
||||
return d.Domain
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func orgFieldNameToDatabase(fieldName org.OrgFieldName) func(query *domain.OrgsQuery) database.Column {
|
||||
switch fieldName {
|
||||
case org.OrgFieldName_ORG_FIELD_NAME_NAME:
|
||||
return domain.OrderOrgsByName
|
||||
case org.OrgFieldName_ORG_FIELD_NAME_CREATION_DATE:
|
||||
return domain.OrderOrgsByCreationDate
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@@ -25,81 +25,275 @@ type Organization struct {
|
||||
Domains []*OrganizationDomain `json:"domains,omitempty" db:"-"`
|
||||
}
|
||||
|
||||
// OrgIdentifierCondition is used to help specify a single Organization,
|
||||
// it will either be used as the organization ID or organization name,
|
||||
// as organizations can be identified either using (instanceID + ID) OR (instanceID + name)
|
||||
type OrgIdentifierCondition interface {
|
||||
database.Condition
|
||||
}
|
||||
var _ Commander = (*CreateOrganizationCommand)(nil)
|
||||
|
||||
// organizationColumns define all the columns of the instance table.
|
||||
type organizationColumns interface {
|
||||
// IDColumn returns the column for the id field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
IDColumn(qualified bool) database.Column
|
||||
// NameColumn returns the column for the name field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
NameColumn(qualified bool) database.Column
|
||||
// InstanceIDColumn returns the column for the default org id field
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
InstanceIDColumn(qualified bool) database.Column
|
||||
// StateColumn returns the column for the name field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
StateColumn(qualified bool) database.Column
|
||||
// CreatedAtColumn returns the column for the created at field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
CreatedAtColumn(qualified bool) database.Column
|
||||
// UpdatedAtColumn returns the column for the updated at field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
UpdatedAtColumn(qualified bool) database.Column
|
||||
}
|
||||
|
||||
// organizationConditions define all the conditions for the instance table.
|
||||
type organizationConditions interface {
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(instanceID string) OrgIdentifierCondition
|
||||
// NameCondition returns a filter on the name field.
|
||||
NameCondition(name string) OrgIdentifierCondition
|
||||
// InstanceIDCondition returns a filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// StateCondition returns a filter on the name field.
|
||||
StateCondition(state OrgState) database.Condition
|
||||
}
|
||||
|
||||
// organizationChanges define all the changes for the instance table.
|
||||
type organizationChanges interface {
|
||||
// SetName sets the name column.
|
||||
SetName(name string) database.Change
|
||||
// SetState sets the name column.
|
||||
SetState(state OrgState) database.Change
|
||||
}
|
||||
|
||||
// OrganizationRepository is the interface for the instance repository.
|
||||
type OrganizationRepository interface {
|
||||
organizationColumns
|
||||
organizationConditions
|
||||
organizationChanges
|
||||
|
||||
Get(ctx context.Context, opts ...database.QueryOption) (*Organization, error)
|
||||
List(ctx context.Context, opts ...database.QueryOption) ([]*Organization, error)
|
||||
|
||||
Create(ctx context.Context, instance *Organization) error
|
||||
Update(ctx context.Context, id OrgIdentifierCondition, instance_id string, changes ...database.Change) (int64, error)
|
||||
Delete(ctx context.Context, id OrgIdentifierCondition, instance_id string) (int64, error)
|
||||
|
||||
// Domains returns the domain sub repository for the organization.
|
||||
// If shouldLoad is true, the domains will be loaded from the database and written to the [Organization].Domains field.
|
||||
// If shouldLoad is set to true once, the Domains field will be set even if shouldLoad is false in the future.
|
||||
Domains(shouldLoad bool) OrganizationDomainRepository
|
||||
}
|
||||
|
||||
type CreateOrganization struct {
|
||||
type CreateOrganizationCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
// ID is optional, if not set a new ID will be generated.
|
||||
// It can be set using the [WithOrganizationID] option in [NewCreateOrganizationCommand].
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name"`
|
||||
|
||||
// CreatedAt MUST NOT be set by the caller.
|
||||
CreatedAt time.Time `json:"createdAt,omitzero"`
|
||||
|
||||
// Admins represent the commands to create the administrators.
|
||||
// The Commanders MUST either be [AddOrgMemberCommand] or [CreateOrgMemberCommand].
|
||||
Admins []Commander `json:"admins,omitempty"`
|
||||
}
|
||||
|
||||
// MemberRepository is a sub repository of the org repository and maybe the instance repository.
|
||||
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 CreateOrganizationCommandOpts interface {
|
||||
applyOnCreateOrganizationCommand(cmd *CreateOrganizationCommand)
|
||||
}
|
||||
|
||||
func NewCreateOrganizationCommand(instanceID, name string, opts ...CreateOrganizationCommandOpts) *CreateOrganizationCommand {
|
||||
cmd := &CreateOrganizationCommand{
|
||||
InstanceID: instanceID,
|
||||
Name: name,
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt.applyOnCreateOrganizationCommand(cmd)
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
//
|
||||
// DISCUSS(adlerhurst): As we need to do validation to make sure a command contains all the data required
|
||||
// we can consider the following options:
|
||||
// 1. Validate the command before executing it, which is what we do here.
|
||||
// 2. Create an invoker which checks if the struct has a `Validate() error` method and call it in the chain of invokers.
|
||||
// While the the first one is more straightforward it bloats the execute method with validation logic.
|
||||
// The second one would allow us to keep the execute method clean, but could be more error prone if the method gets missed during implementation.
|
||||
func (cmd *CreateOrganizationCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
if cmd.ID == "" {
|
||||
cmd.ID, err = generateID()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
close, err := opts.EnsureTx(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { err = close(ctx, err) }()
|
||||
|
||||
err = orgRepo(opts.DB).Create(ctx, &Organization{
|
||||
ID: cmd.ID,
|
||||
Name: cmd.Name,
|
||||
InstanceID: cmd.InstanceID,
|
||||
State: OrgStateActive,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, admin := range cmd.Admins {
|
||||
if err = opts.Invoke(ctx, admin); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (CreateOrganizationCommand) String() string {
|
||||
return "CreateOrganizationCommand"
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*ActivateOrganizationCommand)(nil)
|
||||
)
|
||||
|
||||
type ActivateOrganizationCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
OrgID string `json:"orgId"`
|
||||
|
||||
// UpdatedAt MUST NOT be set by the caller.
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero"`
|
||||
}
|
||||
|
||||
func NewActivateOrganizationCommand(instanceID, orgID string) *ActivateOrganizationCommand {
|
||||
return &ActivateOrganizationCommand{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (cmd *ActivateOrganizationCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
repo := orgRepo(opts.DB)
|
||||
_, err = repo.Update(ctx,
|
||||
repo.IDCondition(cmd.OrgID),
|
||||
cmd.InstanceID,
|
||||
repo.SetState(OrgStateActive),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (ActivateOrganizationCommand) String() string {
|
||||
return "ActivateOrganizationCommand"
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*DeactivateOrganizationCommand)(nil)
|
||||
)
|
||||
|
||||
type DeactivateOrganizationCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
OrgID string `json:"orgId"`
|
||||
|
||||
// UpdatedAt MUST NOT be set by the caller.
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero"`
|
||||
}
|
||||
|
||||
func NewDeactivateOrganizationCommand(instanceID, orgID string) *DeactivateOrganizationCommand {
|
||||
return &DeactivateOrganizationCommand{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (cmd *DeactivateOrganizationCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
repo := orgRepo(opts.DB)
|
||||
_, err = repo.Update(ctx,
|
||||
repo.IDCondition(cmd.OrgID),
|
||||
cmd.InstanceID,
|
||||
repo.SetState(OrgStateInactive),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (DeactivateOrganizationCommand) String() string {
|
||||
return "DeactivateOrganizationCommand"
|
||||
}
|
||||
|
||||
var (
|
||||
_ Commander = (*DeleteOrganizationCommand)(nil)
|
||||
)
|
||||
|
||||
type DeleteOrganizationCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
OrgID string `json:"orgId"`
|
||||
}
|
||||
|
||||
func NewDeleteOrganizationCommand(instanceID, orgID string) *DeleteOrganizationCommand {
|
||||
return &DeleteOrganizationCommand{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (cmd *DeleteOrganizationCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
repo := orgRepo(opts.DB)
|
||||
_, err = repo.Delete(ctx,
|
||||
repo.IDCondition(cmd.OrgID),
|
||||
cmd.InstanceID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (DeleteOrganizationCommand) String() string {
|
||||
return "DeleteOrganizationCommand"
|
||||
}
|
||||
|
||||
var _ Commander = (*UpdateOrganizationCommand)(nil)
|
||||
|
||||
type UpdateOrganizationCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
OrgID string `json:"orgId"`
|
||||
|
||||
repo OrganizationRepository
|
||||
changes database.Changes
|
||||
opts []UpdateOrganizationCommandOpts
|
||||
}
|
||||
|
||||
func NewUpdateOrganizationCommand(instanceID, orgID string, opts ...UpdateOrganizationCommandOpts) *UpdateOrganizationCommand {
|
||||
return &UpdateOrganizationCommand{
|
||||
InstanceID: instanceID,
|
||||
OrgID: orgID,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
type UpdateOrganizationCommandOpts interface {
|
||||
applyOnUpdateOrganizationCommand(cmd *UpdateOrganizationCommand)
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (cmd *UpdateOrganizationCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
cmd.repo = orgRepo(opts.DB)
|
||||
for _, opt := range cmd.opts {
|
||||
opt.applyOnUpdateOrganizationCommand(cmd)
|
||||
}
|
||||
|
||||
if len(cmd.changes) == 0 {
|
||||
return nil // No update needed if no changes are provided.
|
||||
}
|
||||
|
||||
_, err = cmd.repo.Update(ctx,
|
||||
cmd.repo.IDCondition(cmd.OrgID),
|
||||
cmd.InstanceID,
|
||||
cmd.changes,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (UpdateOrganizationCommand) String() string {
|
||||
return "UpdateOrganizationCommand"
|
||||
}
|
||||
|
||||
type OrgsQueryOpts interface {
|
||||
applyOnOrgsQuery(query *OrgsQuery)
|
||||
}
|
||||
|
||||
var _ Commander = (*OrgsQuery)(nil)
|
||||
|
||||
type OrgsQuery struct {
|
||||
InstanceID string
|
||||
|
||||
opts []OrgsQueryOpts
|
||||
repo OrganizationRepository
|
||||
domainRepo OrganizationDomainRepository
|
||||
conditions []database.Condition
|
||||
pagination Pagination
|
||||
|
||||
Result []*Organization
|
||||
}
|
||||
|
||||
func NewOrgsQuery(instanceID string, opts ...OrgsQueryOpts) *OrgsQuery {
|
||||
return &OrgsQuery{
|
||||
InstanceID: instanceID,
|
||||
opts: opts,
|
||||
}
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (q *OrgsQuery) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
q.repo = orgRepo(opts.DB)
|
||||
q.domainRepo = q.repo.Domains(true)
|
||||
q.conditions = append(q.conditions, q.repo.InstanceIDCondition(q.InstanceID))
|
||||
for _, opt := range q.opts {
|
||||
opt.applyOnOrgsQuery(q)
|
||||
}
|
||||
|
||||
q.Result, err = q.repo.List(ctx,
|
||||
database.WithCondition(database.And(q.conditions...)),
|
||||
database.WithLimit(q.pagination.Limit),
|
||||
database.WithOffset(q.pagination.Offset),
|
||||
database.WithOrderBy(!q.pagination.Ascending, q.pagination.OrderColumns...),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (OrgsQuery) String() string {
|
||||
return "OrgsQuery"
|
||||
}
|
||||
|
@@ -1,10 +1,7 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type OrganizationDomain struct {
|
||||
@@ -34,54 +31,3 @@ type AddOrganizationDomain struct {
|
||||
// It is set by the repository and should not be set by the caller.
|
||||
UpdatedAt time.Time `json:"updatedAt,omitzero" db:"updated_at"`
|
||||
}
|
||||
|
||||
type organizationDomainColumns interface {
|
||||
domainColumns
|
||||
// OrgIDColumn returns the column for the org id field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
OrgIDColumn(qualified bool) database.Column
|
||||
// IsVerifiedColumn returns the column for the is verified field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
IsVerifiedColumn(qualified bool) database.Column
|
||||
// ValidationTypeColumn returns the column for the verification type field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
ValidationTypeColumn(qualified bool) database.Column
|
||||
}
|
||||
|
||||
type organizationDomainConditions interface {
|
||||
domainConditions
|
||||
// OrgIDCondition returns a filter on the org id field.
|
||||
OrgIDCondition(orgID string) database.Condition
|
||||
// IsVerifiedCondition returns a filter on the is verified field.
|
||||
IsVerifiedCondition(isVerified bool) database.Condition
|
||||
}
|
||||
|
||||
type organizationDomainChanges interface {
|
||||
domainChanges
|
||||
// SetVerified sets the is verified column to true.
|
||||
SetVerified() database.Change
|
||||
// SetValidationType sets the verification type column.
|
||||
// If the domain is already verified, this is a no-op.
|
||||
SetValidationType(verificationType DomainValidationType) database.Change
|
||||
}
|
||||
|
||||
type OrganizationDomainRepository interface {
|
||||
organizationDomainColumns
|
||||
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.
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error)
|
||||
// Remove removes a domain from the organization.
|
||||
Remove(ctx context.Context, condition database.Condition) (int64, error)
|
||||
}
|
||||
|
58
backend/v3/domain/organization_domain_repository.go
Normal file
58
backend/v3/domain/organization_domain_repository.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
type OrganizationDomainRepository interface {
|
||||
organizationDomainColumns
|
||||
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.
|
||||
Update(ctx context.Context, condition database.Condition, changes ...database.Change) (int64, error)
|
||||
// Remove removes a domain from the organization.
|
||||
Remove(ctx context.Context, condition database.Condition) (int64, error)
|
||||
}
|
||||
|
||||
type organizationDomainColumns interface {
|
||||
domainColumns
|
||||
// OrgIDColumn returns the column for the org id field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
OrgIDColumn(qualified bool) database.Column
|
||||
// IsVerifiedColumn returns the column for the is verified field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
IsVerifiedColumn(qualified bool) database.Column
|
||||
// ValidationTypeColumn returns the column for the verification type field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
ValidationTypeColumn(qualified bool) database.Column
|
||||
}
|
||||
|
||||
type organizationDomainConditions interface {
|
||||
domainConditions
|
||||
// OrgIDCondition returns a filter on the org id field.
|
||||
OrgIDCondition(orgID string) database.Condition
|
||||
// IsVerifiedCondition returns a filter on the is verified field.
|
||||
IsVerifiedCondition(isVerified bool) database.Condition
|
||||
}
|
||||
|
||||
type organizationDomainChanges interface {
|
||||
domainChanges
|
||||
// SetVerified sets the is verified column to true.
|
||||
SetVerified() database.Change
|
||||
// SetValidationType sets the verification type column.
|
||||
// If the domain is already verified, this is a no-op.
|
||||
SetValidationType(verificationType DomainValidationType) database.Change
|
||||
}
|
45
backend/v3/domain/organization_member.go
Normal file
45
backend/v3/domain/organization_member.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package domain
|
||||
|
||||
import "context"
|
||||
|
||||
var _ Commander = (*AddOrgMemberCommand)(nil)
|
||||
|
||||
// AddOrgMemberCommand adds an existing user as an organization member.
|
||||
type AddOrgMemberCommand struct {
|
||||
InstanceID string `json:"instanceId"`
|
||||
OrgID string `json:"orgId"`
|
||||
UserID string `json:"userId"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (a *AddOrgMemberCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (a *AddOrgMemberCommand) String() string {
|
||||
return "AddOrgMemberCommand"
|
||||
}
|
||||
|
||||
var _ Commander = (*CreateOrgMemberCommand)(nil)
|
||||
|
||||
// CreateOrgMemberCommand creates a new user and adds them as an organization member.
|
||||
type CreateOrgMemberCommand struct{}
|
||||
|
||||
// Execute implements [Commander].
|
||||
func (c *CreateOrgMemberCommand) Execute(ctx context.Context, opts *CommandOpts) (err error) {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// String implements [Commander].
|
||||
func (c *CreateOrgMemberCommand) String() string {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// MemberRepository is a sub repository of the org repository and maybe the instance repository.
|
||||
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
|
||||
}
|
127
backend/v3/domain/organization_options.go
Normal file
127
backend/v3/domain/organization_options.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package domain
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
var _ CreateOrganizationCommandOpts = (*withOrganizationID)(nil)
|
||||
|
||||
type withOrganizationID struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func WithOrganizationID(id string) *withOrganizationID {
|
||||
return &withOrganizationID{
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *withOrganizationID) applyOnCreateOrganizationCommand(cmd *CreateOrganizationCommand) {
|
||||
cmd.ID = opt.id
|
||||
}
|
||||
|
||||
var _ UpdateOrganizationCommandOpts = (*withOrganizationName)(nil)
|
||||
|
||||
type withOrganizationName struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func WithOrganizationName(name string) *withOrganizationName {
|
||||
return &withOrganizationName{
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *withOrganizationName) applyOnUpdateOrganizationCommand(cmd *UpdateOrganizationCommand) {
|
||||
cmd.changes = append(cmd.changes, cmd.repo.SetName(opt.name))
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*orgByNameQueryOpt)(nil)
|
||||
|
||||
type orgByNameQueryOpt struct {
|
||||
name string
|
||||
op database.TextOperation
|
||||
}
|
||||
|
||||
func WithOrgByNameQuery(op database.TextOperation, name string) *orgByNameQueryOpt {
|
||||
return &orgByNameQueryOpt{
|
||||
name: name,
|
||||
op: op,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *orgByNameQueryOpt) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.conditions = append(query.conditions, query.repo.NameCondition(opt.op, opt.name))
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*orgByDomainQueryOpt)(nil)
|
||||
|
||||
type orgByDomainQueryOpt struct {
|
||||
name string
|
||||
op database.TextOperation
|
||||
}
|
||||
|
||||
func WithOrgByDomainQuery(op database.TextOperation, name string) *orgByDomainQueryOpt {
|
||||
return &orgByDomainQueryOpt{
|
||||
name: name,
|
||||
op: op,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *orgByDomainQueryOpt) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.conditions = append(query.conditions, query.domainRepo.DomainCondition(opt.op, opt.name))
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*orgByIDQueryOpt)(nil)
|
||||
|
||||
type orgByIDQueryOpt struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func WithOrgByIDQuery(id string) *orgByIDQueryOpt {
|
||||
return &orgByIDQueryOpt{
|
||||
id: id,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *orgByIDQueryOpt) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.conditions = append(query.conditions, query.repo.IDCondition(opt.id))
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*orgByIDQueryOpt)(nil)
|
||||
|
||||
type orgByStateQueryOpt struct {
|
||||
state OrgState
|
||||
}
|
||||
|
||||
func WithOrgByStateQuery(state OrgState) *orgByStateQueryOpt {
|
||||
return &orgByStateQueryOpt{
|
||||
state: state,
|
||||
}
|
||||
}
|
||||
|
||||
func (opt *orgByStateQueryOpt) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.conditions = append(query.conditions, query.repo.StateCondition(opt.state))
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*orgByIDQueryOpt)(nil)
|
||||
|
||||
type orgQuerySortingColumnOpt struct {
|
||||
getColumn func(query *OrgsQuery) database.Column
|
||||
}
|
||||
|
||||
func WithOrgQuerySortingColumn(getColumn func(query *OrgsQuery) database.Column) *orgQuerySortingColumnOpt {
|
||||
return &orgQuerySortingColumnOpt{
|
||||
getColumn: getColumn,
|
||||
}
|
||||
}
|
||||
|
||||
func OrderOrgsByCreationDate(query *OrgsQuery) database.Column {
|
||||
return query.repo.CreatedAtColumn(true)
|
||||
}
|
||||
|
||||
func OrderOrgsByName(query *OrgsQuery) database.Column {
|
||||
return query.repo.NameColumn(true)
|
||||
}
|
||||
|
||||
func (opt *orgQuerySortingColumnOpt) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.pagination.OrderColumns = append(query.pagination.OrderColumns, opt.getColumn(query))
|
||||
}
|
75
backend/v3/domain/organization_repository.go
Normal file
75
backend/v3/domain/organization_repository.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
)
|
||||
|
||||
// OrganizationRepository is the interface for the instance repository.
|
||||
type OrganizationRepository interface {
|
||||
organizationColumns
|
||||
organizationConditions
|
||||
organizationChanges
|
||||
|
||||
Get(ctx context.Context, opts ...database.QueryOption) (*Organization, error)
|
||||
List(ctx context.Context, opts ...database.QueryOption) ([]*Organization, error)
|
||||
|
||||
Create(ctx context.Context, instance *Organization) error
|
||||
Update(ctx context.Context, id OrgIdentifierCondition, instance_id string, changes ...database.Change) (int64, error)
|
||||
Delete(ctx context.Context, id OrgIdentifierCondition, instance_id string) (int64, error)
|
||||
|
||||
// Domains returns the domain sub repository for the organization.
|
||||
// If shouldLoad is true, the domains will be loaded from the database and written to the [Organization].Domains field.
|
||||
// If shouldLoad is set to true once, the Domains field will be set even if shouldLoad is false in the future.
|
||||
Domains(shouldLoad bool) OrganizationDomainRepository
|
||||
}
|
||||
|
||||
// OrgIdentifierCondition is used to help specify a single Organization,
|
||||
// it will either be used as the organization ID or organization name,
|
||||
// as organizations can be identified either using (instanceID + ID) OR (instanceID + name)
|
||||
type OrgIdentifierCondition interface {
|
||||
database.Condition
|
||||
}
|
||||
|
||||
// organizationColumns define all the columns of the instance table.
|
||||
type organizationColumns interface {
|
||||
// IDColumn returns the column for the id field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
IDColumn(qualified bool) database.Column
|
||||
// NameColumn returns the column for the name field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
NameColumn(qualified bool) database.Column
|
||||
// InstanceIDColumn returns the column for the default org id field
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
InstanceIDColumn(qualified bool) database.Column
|
||||
// StateColumn returns the column for the name field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
StateColumn(qualified bool) database.Column
|
||||
// CreatedAtColumn returns the column for the created at field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
CreatedAtColumn(qualified bool) database.Column
|
||||
// UpdatedAtColumn returns the column for the updated at field.
|
||||
// `qualified` indicates if the column should be qualified with the table name.
|
||||
UpdatedAtColumn(qualified bool) database.Column
|
||||
}
|
||||
|
||||
// organizationConditions define all the conditions for the instance table.
|
||||
type organizationConditions interface {
|
||||
// IDCondition returns an equal filter on the id field.
|
||||
IDCondition(id string) OrgIdentifierCondition
|
||||
// NameCondition returns a filter on the name field.
|
||||
NameCondition(name string) OrgIdentifierCondition
|
||||
// InstanceIDCondition returns a filter on the instance id field.
|
||||
InstanceIDCondition(instanceID string) database.Condition
|
||||
// StateCondition returns a filter on the name field.
|
||||
StateCondition(state OrgState) database.Condition
|
||||
}
|
||||
|
||||
// organizationChanges define all the changes for the instance table.
|
||||
type organizationChanges interface {
|
||||
// SetName sets the name column.
|
||||
SetName(name string) database.Change
|
||||
// SetState sets the name column.
|
||||
SetState(state OrgState) database.Change
|
||||
}
|
17
backend/v3/domain/query.go
Normal file
17
backend/v3/domain/query.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package domain
|
||||
|
||||
import "github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
|
||||
type Pagination struct {
|
||||
Limit uint32
|
||||
Offset uint32
|
||||
Ascending bool
|
||||
OrderColumns database.Columns
|
||||
}
|
||||
|
||||
// applyOnOrgsQuery implements OrgsQueryOpts.
|
||||
func (p Pagination) applyOnOrgsQuery(query *OrgsQuery) {
|
||||
query.pagination = p
|
||||
}
|
||||
|
||||
var _ OrgsQueryOpts = (*Pagination)(nil)
|
@@ -10,9 +10,10 @@ func WithCondition(condition Condition) QueryOption {
|
||||
}
|
||||
|
||||
// WithOrderBy sets the columns to order the results by.
|
||||
func WithOrderBy(orderBy ...Column) QueryOption {
|
||||
func WithOrderBy(descending bool, orderBy ...Column) QueryOption {
|
||||
return func(opts *QueryOpts) {
|
||||
opts.OrderBy = orderBy
|
||||
opts.OrderByDescending = descending
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +70,9 @@ type QueryOpts struct {
|
||||
// OrderBy is the columns to order the results by.
|
||||
// It is used to build the ORDER BY clause of the SQL statement.
|
||||
OrderBy Columns
|
||||
// OrderByDescending indicates if the results should be ordered in descending order.
|
||||
// default is ascending order.
|
||||
OrderByDescending bool
|
||||
// Limit is the maximum number of results to return.
|
||||
// It is used to build the LIMIT clause of the SQL statement.
|
||||
Limit uint32
|
||||
@@ -105,7 +109,15 @@ func (opts *QueryOpts) WriteOrderBy(builder *StatementBuilder) {
|
||||
return
|
||||
}
|
||||
builder.WriteString(" ORDER BY ")
|
||||
opts.OrderBy.Write(builder)
|
||||
for i, col := range opts.OrderBy {
|
||||
if i > 0 {
|
||||
builder.WriteString(", ")
|
||||
}
|
||||
col.Write(builder)
|
||||
if opts.OrderByDescending {
|
||||
builder.WriteString(" DESC")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (opts *QueryOpts) WriteLimit(builder *StatementBuilder) {
|
||||
|
@@ -538,7 +538,7 @@ func TestListInstance(t *testing.T) {
|
||||
// check instance values
|
||||
returnedInstances, err := instanceRepo.List(ctx,
|
||||
database.WithCondition(condition),
|
||||
database.WithOrderBy(instanceRepo.CreatedAtColumn(true)),
|
||||
database.WithOrderBy(false, instanceRepo.CreatedAtColumn(true)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
if tt.noInstanceReturned {
|
||||
|
@@ -785,7 +785,7 @@ func TestListOrganization(t *testing.T) {
|
||||
// check organization values
|
||||
returnedOrgs, err := organizationRepo.List(ctx,
|
||||
database.WithCondition(condition),
|
||||
database.WithOrderBy(organizationRepo.CreatedAtColumn(true)),
|
||||
database.WithOrderBy(false, organizationRepo.CreatedAtColumn(true)),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
if tt.noOrganizationReturned {
|
||||
|
Reference in New Issue
Block a user