mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-30 02:52:16 +00:00
feat(domain): Instance APIs with relational tables (#10953)
# Which Problems Are Solved This PR implements the endpoints listed in https://github.com/zitadel/zitadel/issues/10443 . # How the Problems Are Solved The implementation follows the same pattern as the Organization one. There are no peculiarities to this PR. The `Update`, `Delete` are implementing the `Commander` interface, while `Get` and `List` instance(s) endpoints are implementing the `Querier` interace. # Additional Context - Closes #10443 - Depends on #10445
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
/*
|
||||
* Domain Model to GRPC v2
|
||||
*/
|
||||
|
||||
func DomainInstanceListModelToGRPCResponse(instances []*domain.Instance) []*instance.Instance {
|
||||
toReturn := make([]*instance.Instance, len(instances))
|
||||
|
||||
for i, inst := range instances {
|
||||
toReturn[i] = DomainInstanceModelToGRPCResponse(inst)
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
|
||||
func DomainInstanceModelToGRPCResponse(inst *domain.Instance) *instance.Instance {
|
||||
return &instance.Instance{
|
||||
Id: inst.ID,
|
||||
ChangeDate: timestamppb.New(inst.UpdatedAt),
|
||||
CreationDate: timestamppb.New(inst.CreatedAt),
|
||||
State: instance.State_STATE_RUNNING, // TODO(IAM-Marco): Not sure what to put here
|
||||
Name: inst.Name,
|
||||
Version: build.Version(),
|
||||
Domains: domainInstanceDomainListModelToGRPCResponse(inst.Domains),
|
||||
}
|
||||
}
|
||||
|
||||
func domainInstanceDomainListModelToGRPCResponse(dms []*domain.InstanceDomain) []*instance.Domain {
|
||||
toReturn := make([]*instance.Domain, len(dms))
|
||||
for i, domain := range dms {
|
||||
isGenerated := domain.IsGenerated != nil && *domain.IsGenerated
|
||||
isPrimary := domain.IsPrimary != nil && *domain.IsPrimary
|
||||
toReturn[i] = &instance.Domain{
|
||||
InstanceId: domain.InstanceID,
|
||||
CreationDate: timestamppb.New(domain.CreatedAt),
|
||||
Domain: domain.Domain,
|
||||
Primary: isPrimary,
|
||||
Generated: isGenerated,
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
package convert
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func TestDomainInstanceModelToGRPCResponse(t *testing.T) {
|
||||
// Given
|
||||
t.Parallel()
|
||||
|
||||
now := time.Now().UTC()
|
||||
yesterday := now.AddDate(0, 0, -1)
|
||||
|
||||
inputInstance := &domain.Instance{
|
||||
ID: "instance-1",
|
||||
Name: "Instance One",
|
||||
CreatedAt: yesterday,
|
||||
UpdatedAt: now,
|
||||
Domains: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d1.example.com",
|
||||
IsPrimary: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d2.example.com",
|
||||
IsGenerated: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d3.example.com",
|
||||
IsPrimary: gu.Ptr(true),
|
||||
IsGenerated: gu.Ptr(false),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
{
|
||||
InstanceID: "instance-1",
|
||||
Domain: "d4.example.com",
|
||||
IsPrimary: gu.Ptr(false),
|
||||
IsGenerated: gu.Ptr(true),
|
||||
CreatedAt: yesterday,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedInstance := &instance.Instance{
|
||||
Id: "instance-1",
|
||||
ChangeDate: timestamppb.New(now),
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Name: "Instance One",
|
||||
Version: "",
|
||||
Domains: []*instance.Domain{
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d1.example.com",
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d2.example.com",
|
||||
Primary: false,
|
||||
Generated: true,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d3.example.com",
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
{
|
||||
InstanceId: "instance-1",
|
||||
CreationDate: timestamppb.New(yesterday),
|
||||
Domain: "d4.example.com",
|
||||
Primary: false,
|
||||
Generated: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test
|
||||
res := DomainInstanceModelToGRPCResponse(inputInstance)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, expectedInstance, res)
|
||||
}
|
||||
|
||||
func TestDomainInstanceListModelToGRPCResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
now := time.Now()
|
||||
|
||||
tt := []struct {
|
||||
testName string
|
||||
inputResult []*domain.Instance
|
||||
expectedResult []*instance.Instance
|
||||
}{
|
||||
{
|
||||
testName: "empty result",
|
||||
inputResult: []*domain.Instance{},
|
||||
expectedResult: []*instance.Instance{},
|
||||
},
|
||||
{
|
||||
testName: "single instance without domains",
|
||||
inputResult: []*domain.Instance{
|
||||
{
|
||||
ID: "instance1",
|
||||
Name: "test-instance",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "multiple instances with domains",
|
||||
inputResult: []*domain.Instance{
|
||||
{
|
||||
ID: "instance1",
|
||||
Name: "test-instance-1",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: []*domain.InstanceDomain{
|
||||
{
|
||||
InstanceID: "instance1",
|
||||
Domain: "domain1.com",
|
||||
CreatedAt: now,
|
||||
IsPrimary: gu.Ptr(true),
|
||||
IsGenerated: gu.Ptr(false),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: "instance2",
|
||||
Name: "test-instance-2",
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
Domains: nil,
|
||||
},
|
||||
},
|
||||
expectedResult: []*instance.Instance{
|
||||
{
|
||||
Id: "instance1",
|
||||
Name: "test-instance-1",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{
|
||||
{
|
||||
InstanceId: "instance1",
|
||||
Domain: "domain1.com",
|
||||
CreationDate: timestamppb.New(now),
|
||||
Primary: true,
|
||||
Generated: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "instance2",
|
||||
Name: "test-instance-2",
|
||||
CreationDate: timestamppb.New(now),
|
||||
ChangeDate: timestamppb.New(now),
|
||||
State: instance.State_STATE_RUNNING,
|
||||
Domains: []*instance.Domain{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tt {
|
||||
t.Run(tc.testName, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Test
|
||||
result := DomainInstanceListModelToGRPCResponse(tc.inputResult)
|
||||
|
||||
// Verify
|
||||
assert.Equal(t, tc.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package instancev2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/backend/v3/api/instance/v2/convert"
|
||||
"github.com/zitadel/zitadel/backend/v3/domain"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database"
|
||||
"github.com/zitadel/zitadel/backend/v3/storage/database/repository"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func DeleteInstance(ctx context.Context, request *connect.Request[instance.DeleteInstanceRequest]) (*connect.Response[instance.DeleteInstanceResponse], error) {
|
||||
instanceDeleteCmd := domain.NewDeleteInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceDeleteCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.DeleteInstanceResponse]{
|
||||
Msg: &instance.DeleteInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when OrganizationRepo.Update()
|
||||
// returns the timestamp
|
||||
DeletionDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetInstance(ctx context.Context, request *connect.Request[instance.GetInstanceRequest]) (*connect.Response[instance.GetInstanceResponse], error) {
|
||||
instanceGetCmd := domain.NewGetInstanceCommand(request.Msg.GetInstanceId())
|
||||
|
||||
err := domain.Invoke(ctx, instanceGetCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
if errors.Is(err, &database.NoRowFoundError{}) {
|
||||
return nil, zerrors.ThrowNotFound(err, "INST-QVrUwc", "instance not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.GetInstanceResponse]{
|
||||
Msg: &instance.GetInstanceResponse{
|
||||
Instance: convert.DomainInstanceModelToGRPCResponse(instanceGetCmd.Result()),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func UpdateInstance(ctx context.Context, request *connect.Request[instance.UpdateInstanceRequest]) (*connect.Response[instance.UpdateInstanceResponse], error) {
|
||||
instanceUpdateCmd := domain.NewUpdateInstanceCommand(request.Msg.GetInstanceId(), request.Msg.GetInstanceName())
|
||||
|
||||
err := domain.Invoke(ctx, instanceUpdateCmd, domain.WithInstanceRepo(repository.InstanceRepository()))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &connect.Response[instance.UpdateInstanceResponse]{
|
||||
Msg: &instance.UpdateInstanceResponse{
|
||||
// TODO(IAM-Marco): Change this with the real update date when InstanceRepo.Update()
|
||||
// returns the timestamp
|
||||
ChangeDate: timestamppb.Now(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ListInstances(ctx context.Context, request *connect.Request[instance.ListInstancesRequest]) (*connect.Response[instance.ListInstancesResponse], error) {
|
||||
instancesListCmd := domain.NewListInstancesCommand(request.Msg)
|
||||
|
||||
err := domain.Invoke(
|
||||
ctx,
|
||||
instancesListCmd,
|
||||
domain.WithInstanceRepo(repository.InstanceRepository()),
|
||||
domain.WithInstanceDomainRepo(repository.InstanceDomainRepository()),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instances := instancesListCmd.Result()
|
||||
return &connect.Response[instance.ListInstancesResponse]{
|
||||
Msg: &instance.ListInstancesResponse{
|
||||
Instances: convert.DomainInstanceListModelToGRPCResponse(instances),
|
||||
Pagination: &filter.PaginationResponse{
|
||||
// TODO(IAM-Marco): return correct value. Tracked in https://github.com/zitadel/zitadel/issues/10955
|
||||
TotalResult: uint64(len(instances)),
|
||||
AppliedLimit: uint64(request.Msg.GetPagination().GetLimit()),
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package v2
|
||||
|
||||
// this file has been commented out to pass the linter
|
||||
|
||||
// import (
|
||||
// "github.com/zitadel/zitadel/backend/v3/telemetry/logging"
|
||||
// "github.com/zitadel/zitadel/backend/v3/telemetry/tracing"
|
||||
// )
|
||||
|
||||
// var (
|
||||
// logger logging.Logger
|
||||
// tracer tracing.Tracer
|
||||
// )
|
||||
|
||||
// func SetLogger(l logging.Logger) {
|
||||
// logger = l
|
||||
// }
|
||||
|
||||
// func SetTracer(t tracing.Tracer) {
|
||||
// tracer = t
|
||||
// }
|
||||
Reference in New Issue
Block a user