feat: Instance domains (#3444)

* feat: add domain list

* feat: domain tests

* feat: add redirect url on adding instance domain

* Update internal/command/instance_domain.go

Co-authored-by: Livio Amstutz <livio.a@gmail.com>

* feat: remove unused code

* fix

Co-authored-by: Livio Amstutz <livio.a@gmail.com>
This commit is contained in:
Fabi
2022-04-14 14:19:18 +02:00
committed by GitHub
parent 820a21dce3
commit c25d853820
29 changed files with 858 additions and 145 deletions

View File

@@ -12,6 +12,7 @@ type Instance interface {
InstanceID() string
ProjectID() string
ConsoleClientID() string
ConsoleApplicationID() string
RequestedDomain() string
}
@@ -36,6 +37,10 @@ func (i *instance) ConsoleClientID() string {
return ""
}
func (i *instance) ConsoleApplicationID() string {
return ""
}
func (i *instance) RequestedDomain() string {
return i.Domain
}

View File

@@ -79,6 +79,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}
func (m *mockInstance) ConsoleApplicationID() string {
return "appID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@@ -0,0 +1,29 @@
package admin
import (
"context"
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func (s *Server) GetInstanceDomains(ctx context.Context, req *admin_pb.ListInstanceDomainsRequest) (*admin_pb.ListInstanceDomainsResponse, error) {
queries, err := ListInstanceDomainsRequestToModel(req)
if err != nil {
return nil, err
}
domains, err := s.query.SearchInstanceDomains(ctx, queries)
if err != nil {
return nil, err
}
return &admin_pb.ListInstanceDomainsResponse{
Result: instance_grpc.DomainsToPb(domains.Domains),
Details: object.ToListDetails(
domains.Count,
domains.Sequence,
domains.Timestamp,
),
}, nil
}

View File

@@ -0,0 +1,25 @@
package admin
import (
instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/query"
admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
)
func ListInstanceDomainsRequestToModel(req *admin_pb.ListInstanceDomainsRequest) (*query.InstanceDomainSearchQueries, error) {
offset, limit, asc := object.ListQueryToModel(req.Query)
queries, err := instance_grpc.DomainQueriesToModel(req.Queries)
if err != nil {
return nil, err
}
return &query.InstanceDomainSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
},
//SortingColumn: //TODO: sorting
Queries: queries,
}, nil
}

View File

@@ -0,0 +1,54 @@
package org
import (
"github.com/caos/zitadel/internal/api/grpc/object"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query"
instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
)
func DomainQueriesToModel(queries []*instance_pb.DomainSearchQuery) (_ []query.SearchQuery, err error) {
q := make([]query.SearchQuery, len(queries))
for i, query := range queries {
q[i], err = DomainQueryToModel(query)
if err != nil {
return nil, err
}
}
return q, nil
}
func DomainQueryToModel(searchQuery *instance_pb.DomainSearchQuery) (query.SearchQuery, error) {
switch q := searchQuery.Query.(type) {
case *instance_pb.DomainSearchQuery_DomainQuery:
return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.Method), q.DomainQuery.Domain)
case *instance_pb.DomainSearchQuery_GeneratedQuery:
return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedQuery.Generated)
case *instance_pb.DomainSearchQuery_PrimaryQuery:
return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary)
default:
return nil, errors.ThrowInvalidArgument(nil, "ORG-Ags42", "List.Query.Invalid")
}
}
func DomainsToPb(domains []*query.InstanceDomain) []*instance_pb.Domain {
d := make([]*instance_pb.Domain, len(domains))
for i, domain := range domains {
d[i] = DomainToPb(domain)
}
return d
}
func DomainToPb(d *query.InstanceDomain) *instance_pb.Domain {
return &instance_pb.Domain{
Domain: d.Domain,
Primary: d.IsPrimary,
Generated: d.IsGenerated,
Details: object.ToViewDetailsPb(
d.Sequence,
d.CreationDate,
d.ChangeDate,
d.InstanceID,
),
}
}

View File

@@ -173,6 +173,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string {
return "localhost"
}

View File

@@ -257,6 +257,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@@ -356,7 +356,7 @@ func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*
),
AddOIDCAppCommand(console, nil),
SetIAMConsoleID(instanceAgg, &console.ClientID),
SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.Zitadel.consoleAppID),
)
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
@@ -387,11 +387,11 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
}
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
func SetIAMConsoleID(a *instance.Aggregate, clientID *string) preparation.Validation {
func SetIAMConsoleID(a *instance.Aggregate, clientID, appID *string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID),
instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID, appID),
}, nil
}, nil
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
"github.com/caos/zitadel/internal/repository/project"
)
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
@@ -30,6 +31,24 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
}, nil
}
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil {
return nil, err
}
events, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return &domain.ObjectDetails{
Sequence: events[len(events)-1].Sequence(),
EventDate: events[len(events)-1].CreationDate(),
ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
}, nil
}
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.removeInstanceDomain(instanceAgg, instanceDomain)
@@ -61,7 +80,46 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
if domainWriteModel.State == domain.InstanceDomainStateActive {
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
}
return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
if err != nil {
return nil, err
}
redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
ctx,
ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
appWriteModel.AppID,
[]project.OIDCConfigChanges{
project.ChangeRedirectURIs(redirectUrls),
project.ChangePostLogoutRedirectURIs(logoutUrls),
},
)
if err != nil {
return nil, err
}
return []eventstore.Command{
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
consoleChangeEvent,
}, nil
}, nil
}
}
func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
if err != nil {
return nil, err
}
if !domainWriteModel.State.Exists() {
return nil, errors.ThrowNotFound(nil, "INSTANCE-9nkWf", "Errors.Instance.Domain.NotFound")
}
return []eventstore.Command{instance.NewDomainPrimarySetEvent(ctx, &a.Aggregate, instanceDomain)}, nil
}, nil
}
}

View File

@@ -3,8 +3,11 @@ package command
import (
"context"
"testing"
"time"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
@@ -77,6 +80,43 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
eventstore: eventstoreExpect(
t,
expectFilter(),
expectFilter(
eventFromEventPusherWithInstanceID(
"INSTANCE",
project.NewApplicationAddedEvent(context.Background(),
&project.NewAggregate("project1", "org1").Aggregate,
"consoleApplicationID",
"app",
),
),
eventFromEventPusherWithInstanceID(
"INSTANCE",
project.NewOIDCConfigAddedEvent(context.Background(),
&project.NewAggregate("projectID", "org1").Aggregate,
domain.OIDCVersionV1,
"consoleApplicationID",
"client1@project",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("a"),
},
[]string{"https://test.ch"},
[]domain.OIDCResponseType{domain.OIDCResponseTypeCode},
[]domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
domain.OIDCApplicationTypeWeb,
domain.OIDCAuthMethodTypePost,
[]string{"https://test.ch/logout"},
true,
domain.OIDCTokenTypeBearer,
true,
true,
true,
time.Second*1,
[]string{"https://sub.test.ch"}),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
@@ -86,11 +126,121 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
"domain.ch",
false,
)),
eventFromEventPusherWithInstanceID(
"INSTANCE",
newOIDCAppChangedEventInstanceDomain(context.Background(), "consoleApplicationID", "projectID", "org1"),
),
},
uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddInstanceDomainUniqueConstraint("domain.ch")),
),
),
},
args: args{
ctx: authz.WithInstance(context.Background(), new(mockInstance)),
domain: "domain.ch",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain)
if tt.res.err == nil {
assert.NoError(t, err)
}
if tt.res.err != nil && !tt.res.err(err) {
t.Errorf("got wrong err: %v ", err)
}
if tt.res.err == nil {
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_SetPrimaryInstanceDomain(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
}
type args struct {
ctx context.Context
domain string
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
name: "invalid domain, error",
fields: fields{
eventstore: eventstoreExpect(
t,
),
},
args: args{
ctx: context.Background(),
domain: "",
},
res: res{
err: caos_errs.IsErrorInvalidArgument,
},
},
{
name: "domain not exists, precondition error",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
domain: "domain.ch",
},
res: res{
err: caos_errs.IsNotFound,
},
},
{
name: "set primary domain, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewDomainAddedEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
false,
),
),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"INSTANCE",
instance.NewDomainPrimarySetEvent(context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"domain.ch",
)),
},
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
domain: "domain.ch",
@@ -107,7 +257,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain)
got, err := r.SetPrimaryInstanceDomain(tt.args.ctx, tt.args.domain)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -251,3 +401,16 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
})
}
}
func newOIDCAppChangedEventInstanceDomain(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
changes := []project.OIDCConfigChanges{
project.ChangeRedirectURIs([]string{"https://test.ch", "domain.ch/ui/console/auth/callback"}),
project.ChangePostLogoutRedirectURIs([]string{"https://test.ch/logout", "domain.ch/ui/console/signedout"}),
}
event, _ := project.NewOIDCConfigChangedEvent(ctx,
&project.NewAggregate(projectID, resourceOwner).Aggregate,
appID,
changes,
)
return event
}

View File

@@ -256,3 +256,25 @@ func (v *testVerifier) CheckOrgFeatures(ctx context.Context, orgID string, requi
}
return nil
}
type mockInstance struct{}
func (m *mockInstance) InstanceID() string {
return "INSTANCE"
}
func (m *mockInstance) ProjectID() string {
return "projectID"
}
func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}
func (m *mockInstance) ConsoleApplicationID() string {
return "consoleApplicationID"
}
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}

View File

@@ -309,7 +309,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err
}
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
logging.New().OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
}

View File

@@ -18,6 +18,10 @@ func (f InstanceDomainState) Valid() bool {
return f >= 0 && f < instanceDomainStateCount
}
func (f InstanceDomainState) Exists() bool {
return f == InstanceDomainStateActive
}
func NewGeneratedInstanceDomain(instanceName, iamDomain string) string {
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain)
}

View File

@@ -43,6 +43,10 @@ var (
name: projection.InstanceColumnConsoleID,
table: instanceTable,
}
InstanceColumnConsoleAppID = Column{
name: projection.InstanceColumnConsoleAppID,
table: instanceTable,
}
InstanceColumnSetupStarted = Column{
name: projection.InstanceColumnSetUpStarted,
table: instanceTable,
@@ -65,6 +69,7 @@ type Instance struct {
GlobalOrgID string
IAMProjectID string
ConsoleID string
ConsoleAppID string
DefaultLanguage language.Tag
SetupStarted domain.Step
SetupDone domain.Step
@@ -83,6 +88,10 @@ func (i *Instance) ConsoleClientID() string {
return i.ConsoleID
}
func (i *Instance) ConsoleApplicationID() string {
return i.ConsoleAppID
}
func (i *Instance) RequestedDomain() string {
return i.Host
}
@@ -142,6 +151,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(),
@@ -157,6 +167,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
&instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang,

View File

@@ -19,6 +19,7 @@ type InstanceDomain struct {
Domain string
InstanceID string
IsGenerated bool
IsPrimary bool
}
type InstanceDomains struct {
@@ -47,8 +48,12 @@ func NewInstanceDomainInstanceIDSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(InstanceDomainInstanceIDCol, value, TextEquals)
}
func NewInstanceDomainGeneratedSearchQuery(verified bool) (SearchQuery, error) {
return NewBoolQuery(InstanceDomainIsGeneratedCol, verified)
func NewInstanceDomainGeneratedSearchQuery(generated bool) (SearchQuery, error) {
return NewBoolQuery(InstanceDomainIsGeneratedCol, generated)
}
func NewInstanceDomainPrimarySearchQuery(primary bool) (SearchQuery, error) {
return NewBoolQuery(InstanceDomainIsPrimaryCol, primary)
}
func (q *Queries) SearchInstanceDomains(ctx context.Context, queries *InstanceDomainSearchQueries) (domains *InstanceDomains, err error) {
@@ -81,6 +86,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
InstanceDomainDomainCol.identifier(),
InstanceDomainInstanceIDCol.identifier(),
InstanceDomainIsGeneratedCol.identifier(),
InstanceDomainIsPrimaryCol.identifier(),
countColumn.identifier(),
).From(instanceDomainsTable.identifier()).PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*InstanceDomains, error) {
@@ -95,6 +101,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
&domain.Domain,
&domain.InstanceID,
&domain.IsGenerated,
&domain.IsPrimary,
&count,
)
if err != nil {
@@ -145,4 +152,8 @@ var (
name: projection.InstanceDomainIsGeneratedCol,
table: instanceDomainsTable,
}
InstanceDomainIsPrimaryCol = Column{
name: projection.InstanceDomainIsPrimaryCol,
table: instanceDomainsTable,
}
)

View File

@@ -31,6 +31,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
nil,
@@ -50,6 +51,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
[]string{
@@ -59,6 +61,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain",
"instance_id",
"is_generated",
"is_primary",
"count",
},
[][]driver.Value{
@@ -69,6 +72,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch",
"inst-id",
true,
true,
},
},
),
@@ -85,6 +89,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch",
InstanceID: "inst-id",
IsGenerated: true,
IsPrimary: true,
},
},
},
@@ -100,6 +105,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
[]string{
@@ -109,6 +115,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain",
"instance_id",
"is_generated",
"is_primary",
"count",
},
[][]driver.Value{
@@ -119,6 +126,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch",
"inst-id",
true,
true,
},
{
testNow,
@@ -127,6 +135,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.com",
"inst-id",
false,
false,
},
},
),
@@ -143,6 +152,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch",
InstanceID: "inst-id",
IsGenerated: true,
IsPrimary: true,
},
{
CreationDate: testNow,
@@ -151,6 +161,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.com",
InstanceID: "inst-id",
IsGenerated: false,
IsPrimary: false,
},
},
},
@@ -166,6 +177,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
sql.ErrConnDone,

View File

@@ -39,6 +39,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+
@@ -68,6 +69,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+
@@ -79,6 +81,7 @@ func Test_InstancePrepares(t *testing.T) {
"global_org_id",
"iam_project_id",
"console_client_id",
"console_app_id",
"setup_started",
"setup_done",
"default_language",
@@ -90,6 +93,7 @@ func Test_InstancePrepares(t *testing.T) {
"global-org-id",
"project-id",
"client-id",
"app-id",
domain.Step2,
domain.Step1,
"en",
@@ -103,6 +107,7 @@ func Test_InstancePrepares(t *testing.T) {
GlobalOrgID: "global-org-id",
IAMProjectID: "project-id",
ConsoleID: "client-id",
ConsoleAppID: "app-id",
SetupStarted: domain.Step2,
SetupDone: domain.Step1,
DefaultLanguage: language.English,
@@ -121,6 +126,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+

View File

@@ -18,6 +18,7 @@ const (
InstanceColumnGlobalOrgID = "global_org_id"
InstanceColumnProjectID = "iam_project_id"
InstanceColumnConsoleID = "console_client_id"
InstanceColumnConsoleAppID = "console_app_id"
InstanceColumnSequence = "sequence"
InstanceColumnSetUpStarted = "setup_started"
InstanceColumnSetUpDone = "setup_done"
@@ -129,6 +130,7 @@ func (p *InstanceProjection) reduceConsoleSet(event eventstore.Event) (*handler.
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnConsoleID, e.ClientID),
handler.NewCol(InstanceColumnConsoleAppID, e.AppID),
},
), nil
}

View File

@@ -19,6 +19,7 @@ const (
InstanceDomainSequenceCol = "sequence"
InstanceDomainDomainCol = "domain"
InstanceDomainIsGeneratedCol = "is_generated"
InstanceDomainIsPrimaryCol = "is_primary"
)
type InstanceDomainProjection struct {
@@ -37,6 +38,7 @@ func NewInstanceDomainProjection(ctx context.Context, config crdb.StatementHandl
crdb.NewColumn(InstanceDomainSequenceCol, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceDomainDomainCol, crdb.ColumnTypeText),
crdb.NewColumn(InstanceDomainIsGeneratedCol, crdb.ColumnTypeBool),
crdb.NewColumn(InstanceDomainIsPrimaryCol, crdb.ColumnTypeBool),
},
crdb.NewPrimaryKey(InstanceDomainInstanceIDCol, InstanceDomainDomainCol),
),
@@ -54,6 +56,10 @@ func (p *InstanceDomainProjection) reducers() []handler.AggregateReducer {
Event: instance.InstanceDomainAddedEventType,
Reduce: p.reduceDomainAdded,
},
{
Event: instance.InstanceDomainAddedEventType,
Reduce: p.reduceDomainPrimarySet,
},
{
Event: instance.InstanceDomainRemovedEventType,
Reduce: p.reduceDomainRemoved,
@@ -77,10 +83,43 @@ func (p *InstanceDomainProjection) reduceDomainAdded(event eventstore.Event) (*h
handler.NewCol(InstanceDomainDomainCol, e.Domain),
handler.NewCol(InstanceDomainInstanceIDCol, e.Aggregate().ID),
handler.NewCol(InstanceDomainIsGeneratedCol, e.Generated),
handler.NewCol(InstanceDomainIsPrimaryCol, false),
},
), nil
}
func (p *InstanceDomainProjection) reduceDomainPrimarySet(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.DomainPrimarySetEvent)
if !ok {
return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-f8nlw", "reduce.wrong.event.type %s", instance.InstanceDomainPrimarySetEventType)
}
return crdb.NewMultiStatement(
e,
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
handler.NewCol(InstanceDomainIsPrimaryCol, false),
},
[]handler.Condition{
handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCond(InstanceDomainIsPrimaryCol, true),
},
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
handler.NewCol(InstanceDomainIsPrimaryCol, true),
},
[]handler.Condition{
handler.NewCond(InstanceDomainDomainCol, e.Domain),
handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().ID),
},
),
), nil
}
func (p *InstanceDomainProjection) reduceDomainRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.DomainRemovedEvent)
if !ok {

View File

@@ -38,7 +38,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated) VALUES ($1, $2, $3, $4, $5, $6)",
expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated, is_primary) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
@@ -46,6 +46,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
"domain.new",
"agg-id",
true,
false,
},
},
},

View File

@@ -11,10 +11,11 @@ import (
)
const (
UniqueInstanceDomain = "instance_domain"
domainEventPrefix = instanceEventTypePrefix + "domain."
InstanceDomainAddedEventType = domainEventPrefix + "added"
InstanceDomainRemovedEventType = domainEventPrefix + "removed"
UniqueInstanceDomain = "instance_domain"
domainEventPrefix = instanceEventTypePrefix + "domain."
InstanceDomainAddedEventType = domainEventPrefix + "added"
InstanceDomainPrimarySetEventType = domainEventPrefix + "primary.set"
InstanceDomainRemovedEventType = domainEventPrefix + "removed"
)
func NewAddInstanceDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint {
@@ -69,6 +70,43 @@ func DomainAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
return orgDomainAdded, nil
}
type DomainPrimarySetEvent struct {
eventstore.BaseEvent `json:"-"`
Domain string `json:"domain,omitempty"`
}
func (e *DomainPrimarySetEvent) Data() interface{} {
return e
}
func (e *DomainPrimarySetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func NewDomainPrimarySetEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainPrimarySetEvent {
return &DomainPrimarySetEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,
aggregate,
InstanceDomainPrimarySetEventType,
),
Domain: domain,
}
}
func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) {
orgDomainAdded := &DomainAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, orgDomainAdded)
if err != nil {
return nil, errors.ThrowInternal(err, "INSTANCE-29jöF", "unable to unmarshal instance domain added")
}
return orgDomainAdded, nil
}
type DomainRemovedEvent struct {
eventstore.BaseEvent `json:"-"`

View File

@@ -60,6 +60,7 @@ type ConsoleSetEvent struct {
eventstore.BaseEvent `json:"-"`
ClientID string `json:"clientId"`
AppID string `json:"appId"`
}
func (e *ConsoleSetEvent) Data() interface{} {
@@ -73,7 +74,8 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
func NewIAMConsoleSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
clientID *string,
clientID,
appID *string,
) *ConsoleSetEvent {
return &ConsoleSetEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -82,6 +84,7 @@ func NewIAMConsoleSetEvent(
ConsoleSetEventType,
),
ClientID: *clientID,
AppID: *appID,
}
}

View File

@@ -87,6 +87,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper).
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
RegisterFilterEventMapper(InstanceDomainAddedEventType, DomainAddedEventMapper).
RegisterFilterEventMapper(InstanceDomainPrimarySetEventType, DomainPrimarySetEventMapper).
RegisterFilterEventMapper(InstanceDomainRemovedEventType, DomainRemovedEventMapper).
RegisterFilterEventMapper(InstanceAddedEventType, InstanceAddedEventMapper).
RegisterFilterEventMapper(InstanceChangedEventType, InstanceChangedEventMapper).