mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 11:17:32 +00:00
implementation done
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
Reference in New Issue
Block a user