feat: add executions for actions v2 (#7433)

* feat: add events for execution

* feat: add events for execution and command side

* feat: add events for execution and command side

* feat: add api endpoints for set and delete executions with integration tests

* feat: add integration and unit tests and more existence checks

* feat: add integration and unit tests and more existence checks

* feat: unit tests for includes in executions

* feat: integration tests for includes in executions

* fix: linting

* fix: update internal/api/api.go

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>

* fix: update internal/command/command.go

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>

* fix: apply suggestions from code review

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>

* fix: change api return

* fix: change aggregateID with prefix of execution type and add to documentation

* fix: change body in proto for documentation and correct linting

* fix: changed existing check to single query in separate writemodel

* fix: linter changes and list endpoints for conditions in executions

* fix: remove writemodel query on exeuction set as state before is irrelevant

* fix: testing for exists write models and correction

* fix: translations for errors and event types

---------

Co-authored-by: Tim Möhlmann <tim+github@zitadel.com>
This commit is contained in:
Stefan Benz
2024-02-26 11:49:43 +01:00
committed by GitHub
parent ce7ebffa84
commit 2731099db3
39 changed files with 5893 additions and 58 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"crypto/tls"
"net/http"
"sort"
"strings"
"github.com/gorilla/mux"
@@ -38,6 +39,30 @@ type API struct {
queries *query.Queries
}
func (a *API) ListGrpcServices() []string {
serviceInfo := a.grpcServer.GetServiceInfo()
services := make([]string, len(serviceInfo))
i := 0
for servicename := range serviceInfo {
services[i] = servicename
i++
}
sort.Strings(services)
return services
}
func (a *API) ListGrpcMethods() []string {
serviceInfo := a.grpcServer.GetServiceInfo()
methods := make([]string, 0)
for servicename, service := range serviceInfo {
for _, method := range service.Methods {
methods = append(methods, "/"+servicename+"/"+method.Name)
}
}
sort.Strings(methods)
return methods
}
type healthCheck interface {
Health(ctx context.Context) error
}

View File

@@ -0,0 +1,124 @@
package execution
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
)
func (s *Server) ListExecutionFunctions(_ context.Context, _ *execution.ListExecutionFunctionsRequest) (*execution.ListExecutionFunctionsResponse, error) {
return &execution.ListExecutionFunctionsResponse{
Functions: s.ListActionFunctions(),
}, nil
}
func (s *Server) ListExecutionMethods(_ context.Context, _ *execution.ListExecutionMethodsRequest) (*execution.ListExecutionMethodsResponse, error) {
return &execution.ListExecutionMethodsResponse{
Methods: s.ListGRPCMethods(),
}, nil
}
func (s *Server) ListExecutionServices(_ context.Context, _ *execution.ListExecutionServicesRequest) (*execution.ListExecutionServicesResponse, error) {
return &execution.ListExecutionServicesResponse{
Services: s.ListGRPCServices(),
}, nil
}
func (s *Server) SetExecution(ctx context.Context, req *execution.SetExecutionRequest) (*execution.SetExecutionResponse, error) {
set := &command.SetExecution{
Targets: req.GetTargets(),
Includes: req.GetIncludes(),
}
var err error
var details *domain.ObjectDetails
switch t := req.GetCondition().GetConditionType().(type) {
case *execution.SetConditions_Request:
cond := &command.ExecutionAPICondition{
Method: t.Request.GetMethod(),
Service: t.Request.GetService(),
All: t.Request.GetAll(),
}
details, err = s.command.SetExecutionRequest(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Response:
cond := &command.ExecutionAPICondition{
Method: t.Response.GetMethod(),
Service: t.Response.GetService(),
All: t.Response.GetAll(),
}
details, err = s.command.SetExecutionResponse(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Event:
cond := &command.ExecutionEventCondition{
Event: t.Event.GetEvent(),
Group: t.Event.GetGroup(),
All: t.Event.GetAll(),
}
details, err = s.command.SetExecutionEvent(ctx, cond, set, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Function:
details, err = s.command.SetExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), set, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
}
return &execution.SetExecutionResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}
func (s *Server) DeleteExecution(ctx context.Context, req *execution.DeleteExecutionRequest) (*execution.DeleteExecutionResponse, error) {
var err error
var details *domain.ObjectDetails
switch t := req.GetCondition().GetConditionType().(type) {
case *execution.SetConditions_Request:
cond := &command.ExecutionAPICondition{
Method: t.Request.GetMethod(),
Service: t.Request.GetService(),
All: t.Request.GetAll(),
}
details, err = s.command.DeleteExecutionRequest(ctx, cond, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Response:
cond := &command.ExecutionAPICondition{
Method: t.Response.GetMethod(),
Service: t.Response.GetService(),
All: t.Response.GetAll(),
}
details, err = s.command.DeleteExecutionResponse(ctx, cond, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Event:
cond := &command.ExecutionEventCondition{
Event: t.Event.GetEvent(),
Group: t.Event.GetGroup(),
All: t.Event.GetAll(),
}
details, err = s.command.DeleteExecutionEvent(ctx, cond, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
case *execution.SetConditions_Function:
details, err = s.command.DeleteExecutionFunction(ctx, command.ExecutionFunctionCondition(t.Function), authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
}
return &execution.DeleteExecutionResponse{
Details: object.DomainToDetailsPb(details),
}, nil
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,8 +14,11 @@ var _ execution.ExecutionServiceServer = (*Server)(nil)
type Server struct {
execution.UnimplementedExecutionServiceServer
command *command.Commands
query *query.Queries
command *command.Commands
query *query.Queries
ListActionFunctions func() []string
ListGRPCMethods func() []string
ListGRPCServices func() []string
}
type Config struct{}
@@ -23,10 +26,16 @@ type Config struct{}
func CreateServer(
command *command.Commands,
query *query.Queries,
listActionFunctions func() []string,
listGRPCMethods func() []string,
listGRPCServices func() []string,
) *Server {
return &Server{
command: command,
query: query,
command: command,
query: query,
ListActionFunctions: listActionFunctions,
ListGRPCMethods: listGRPCMethods,
ListGRPCServices: listGRPCServices,
}
}

View File

@@ -0,0 +1,33 @@
//go:build integration
package execution_test
import (
"context"
"os"
"testing"
"time"
"github.com/zitadel/zitadel/internal/integration"
execution "github.com/zitadel/zitadel/pkg/grpc/execution/v3alpha"
)
var (
CTX context.Context
Tester *integration.Tester
Client execution.ExecutionServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, errCtx, cancel := integration.Contexts(5 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
Client = Tester.Client.ExecutionV3
CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx
return m.Run()
}())
}

View File

@@ -5,7 +5,6 @@ package execution_test
import (
"context"
"fmt"
"os"
"testing"
"time"
@@ -20,26 +19,6 @@ import (
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
)
var (
CTX context.Context
Tester *integration.Tester
Client execution.ExecutionServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, errCtx, cancel := integration.Contexts(5 * time.Minute)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
Client = Tester.Client.ExecutionV3
CTX, _ = Tester.WithAuthorization(ctx, integration.IAMOwner), errCtx
return m.Run()
}())
}
func TestServer_CreateTarget(t *testing.T) {
tests := []struct {
name string

View File

@@ -0,0 +1,285 @@
package command
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/repository/execution"
"github.com/zitadel/zitadel/internal/zerrors"
)
type ExecutionAPICondition struct {
Method string
Service string
All bool
}
func (e *ExecutionAPICondition) IsValid() error {
if e.Method == "" && e.Service == "" && !e.All {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-3tkej630e6", "Errors.Execution.Invalid")
}
// never set two conditions
if e.Method != "" && (e.Service != "" || e.All) ||
e.Service != "" && (e.Method != "" || e.All) ||
e.All && (e.Method != "" || e.Service != "") {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-nee5q8aszq", "Errors.Execution.Invalid")
}
return nil
}
func (e *ExecutionAPICondition) ID(executionType domain.ExecutionType) string {
if e.Method != "" {
return execution.ID(executionType, e.Method)
}
if e.Service != "" {
return execution.ID(executionType, e.Service)
}
if e.All {
return execution.IDAll(executionType)
}
return ""
}
func (e *ExecutionAPICondition) Existing(c *Commands) error {
if e.Method != "" && !c.GrpcMethodExisting(e.Method) {
return zerrors.ThrowNotFound(nil, "COMMAND-vysplsevt8", "Errors.Execution.ConditionInvalid")
}
if e.Service != "" && !c.GrpcServiceExisting(e.Service) {
return zerrors.ThrowNotFound(nil, "COMMAND-qu6dfhiioq", "Errors.Execution.ConditionInvalid")
}
return nil
}
func (c *Commands) SetExecutionRequest(ctx context.Context, cond *ExecutionAPICondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
if err := cond.Existing(c); err != nil {
return nil, err
}
if set.AggregateID == "" {
set.AggregateID = cond.ID(domain.ExecutionTypeRequest)
}
return c.setExecution(ctx, set, resourceOwner)
}
func (c *Commands) SetExecutionResponse(ctx context.Context, cond *ExecutionAPICondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
if err := cond.Existing(c); err != nil {
return nil, err
}
if set.AggregateID == "" {
set.AggregateID = cond.ID(domain.ExecutionTypeResponse)
}
return c.setExecution(ctx, set, resourceOwner)
}
type ExecutionFunctionCondition string
func (e ExecutionFunctionCondition) IsValid() error {
if e == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5folwn5jws", "Errors.Execution.Invalid")
}
return nil
}
func (e ExecutionFunctionCondition) ID() string {
return execution.ID(domain.ExecutionTypeFunction, string(e))
}
func (e ExecutionFunctionCondition) Existing(c *Commands) error {
if !c.ActionFunctionExisting(string(e)) {
return zerrors.ThrowNotFound(nil, "COMMAND-cdy39t0ksr", "Errors.Execution.ConditionInvalid")
}
return nil
}
func (c *Commands) SetExecutionFunction(ctx context.Context, cond ExecutionFunctionCondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
if err := cond.Existing(c); err != nil {
return nil, err
}
if set.AggregateID == "" {
set.AggregateID = cond.ID()
}
return c.setExecution(ctx, set, resourceOwner)
}
type ExecutionEventCondition struct {
Event string
Group string
All bool
}
func (e *ExecutionEventCondition) IsValid() error {
if e.Event == "" && e.Group == "" && !e.All {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-w5smb6v7qu", "Errors.Execution.Invalid")
}
// never set two conditions
if e.Event != "" && (e.Group != "" || e.All) ||
e.Group != "" && (e.Event != "" || e.All) ||
e.All && (e.Event != "" || e.Group != "") {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-hdm4zl1hmd", "Errors.Execution.Invalid")
}
return nil
}
func (e *ExecutionEventCondition) ID() string {
if e.Event != "" {
return execution.ID(domain.ExecutionTypeEvent, e.Event)
}
if e.Group != "" {
return execution.ID(domain.ExecutionTypeEvent, e.Group)
}
if e.All {
return execution.IDAll(domain.ExecutionTypeEvent)
}
return ""
}
func (e *ExecutionEventCondition) Existing(c *Commands) error {
if e.Event != "" && !c.EventExisting(e.Event) {
return zerrors.ThrowNotFound(nil, "COMMAND-74aaqj8fv9", "Errors.Execution.ConditionInvalid")
}
if e.Group != "" && !c.EventGroupExisting(e.Group) {
return zerrors.ThrowNotFound(nil, "COMMAND-er5oneb5lz", "Errors.Execution.ConditionInvalid")
}
return nil
}
func (c *Commands) SetExecutionEvent(ctx context.Context, cond *ExecutionEventCondition, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
if err := cond.Existing(c); err != nil {
return nil, err
}
if set.AggregateID == "" {
set.AggregateID = cond.ID()
}
return c.setExecution(ctx, set, resourceOwner)
}
type SetExecution struct {
models.ObjectRoot
Targets []string
Includes []string
}
func (e *SetExecution) IsValid() error {
if len(e.Targets) == 0 && len(e.Includes) == 0 {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-56bteot2uj", "Errors.Execution.NoTargets")
}
if len(e.Targets) > 0 && len(e.Includes) > 0 {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-5zleae34r1", "Errors.Execution.Invalid")
}
return nil
}
func (e *SetExecution) Existing(c *Commands, ctx context.Context, resourceOwner string) error {
if len(e.Targets) > 0 && !c.existsTargetsByIDs(ctx, e.Targets, resourceOwner) {
return zerrors.ThrowNotFound(nil, "COMMAND-17e8fq1ggk", "Errors.Target.NotFound")
}
if len(e.Includes) > 0 && !c.existsExecutionsByIDs(ctx, e.Includes, resourceOwner) {
return zerrors.ThrowNotFound(nil, "COMMAND-slgj0l4cdz", "Errors.Execution.IncludeNotFound")
}
return nil
}
func (c *Commands) setExecution(ctx context.Context, set *SetExecution, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if resourceOwner == "" || set.AggregateID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-gg3a6ol4om", "Errors.IDMissing")
}
if err := set.IsValid(); err != nil {
return nil, err
}
wm := NewExecutionWriteModel(set.AggregateID, resourceOwner)
// Check if targets and includes for execution are existing
if err := set.Existing(c, ctx, resourceOwner); err != nil {
return nil, err
}
if err := c.pushAppendAndReduce(ctx, wm, execution.NewSetEvent(
ctx,
ExecutionAggregateFromWriteModel(&wm.WriteModel),
set.Targets,
set.Includes,
)); err != nil {
return nil, err
}
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) DeleteExecutionRequest(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeRequest), resourceOwner)
}
func (c *Commands) DeleteExecutionResponse(ctx context.Context, cond *ExecutionAPICondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
return c.deleteExecution(ctx, cond.ID(domain.ExecutionTypeResponse), resourceOwner)
}
func (c *Commands) DeleteExecutionFunction(ctx context.Context, cond ExecutionFunctionCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
}
func (c *Commands) DeleteExecutionEvent(ctx context.Context, cond *ExecutionEventCondition, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if err := cond.IsValid(); err != nil {
return nil, err
}
return c.deleteExecution(ctx, cond.ID(), resourceOwner)
}
func (c *Commands) deleteExecution(ctx context.Context, aggID string, resourceOwner string) (_ *domain.ObjectDetails, err error) {
if resourceOwner == "" || aggID == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-cnic97c0g3", "Errors.IDMissing")
}
wm, err := c.getExecutionWriteModelByID(ctx, aggID, resourceOwner)
if err != nil {
return nil, err
}
if !wm.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-suq2upd3rt", "Errors.Execution.NotFound")
}
if err := c.pushAppendAndReduce(ctx, wm, execution.NewRemovedEvent(
ctx,
ExecutionAggregateFromWriteModel(&wm.WriteModel),
)); err != nil {
return nil, err
}
return writeModelToObjectDetails(&wm.WriteModel), nil
}
func (c *Commands) existsExecutionsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
wm := NewExecutionsExistWriteModel(ids, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, wm)
if err != nil {
return false
}
return wm.AllExists()
}
func (c *Commands) getExecutionWriteModelByID(ctx context.Context, id string, resourceOwner string) (*ExecutionWriteModel, error) {
wm := NewExecutionWriteModel(id, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, wm)
if err != nil {
return nil, err
}
return wm, nil
}

View File

@@ -0,0 +1,113 @@
package command
import (
"slices"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/execution"
)
type ExecutionWriteModel struct {
eventstore.WriteModel
Targets []string
Includes []string
}
func (e *ExecutionWriteModel) Exists() bool {
return len(e.Targets) > 0 || len(e.Includes) > 0
}
func NewExecutionWriteModel(id string, resourceOwner string) *ExecutionWriteModel {
return &ExecutionWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: id,
ResourceOwner: resourceOwner,
InstanceID: resourceOwner,
},
}
}
func (wm *ExecutionWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *execution.SetEvent:
wm.Targets = e.Targets
wm.Includes = e.Includes
case *execution.RemovedEvent:
wm.Targets = nil
wm.Includes = nil
}
}
return wm.WriteModel.Reduce()
}
func (wm *ExecutionWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(execution.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(execution.SetEventType,
execution.RemovedEventType).
Builder()
}
func ExecutionAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return &eventstore.Aggregate{
ID: wm.AggregateID,
Type: execution.AggregateType,
ResourceOwner: wm.ResourceOwner,
InstanceID: wm.InstanceID,
Version: execution.AggregateVersion,
}
}
type ExecutionsExistWriteModel struct {
eventstore.WriteModel
ids []string
existingIDs []string
}
func (e *ExecutionsExistWriteModel) AllExists() bool {
return len(e.ids) == len(e.existingIDs)
}
func NewExecutionsExistWriteModel(ids []string, resourceOwner string) *ExecutionsExistWriteModel {
return &ExecutionsExistWriteModel{
WriteModel: eventstore.WriteModel{
ResourceOwner: resourceOwner,
InstanceID: resourceOwner,
},
ids: ids,
}
}
func (wm *ExecutionsExistWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *execution.SetEvent:
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
}
case *execution.RemovedEvent:
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
if i >= 0 {
wm.existingIDs = slices.Delete(wm.existingIDs, i, i+1)
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *ExecutionsExistWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(execution.AggregateType).
AggregateIDs(wm.ids...).
EventTypes(execution.SetEventType,
execution.RemovedEventType).
Builder()
}

View File

@@ -0,0 +1,437 @@
package command
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/execution"
)
func TestCommandSide_executionsExistsWriteModel(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
ids []string
resourceOwner string
}
tests := []struct {
name string
fields fields
args args
res bool
}{
{
name: "execution, single",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution"},
},
res: true,
},
{
name: "execution, single reset",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution"},
},
res: true,
},
{
name: "execution, single before removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution"},
},
res: true,
},
{
name: "execution, single removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution", "org1"),
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution"},
},
res: false,
},
{
name: "execution, multiple",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: true,
},
{
name: "execution, multiple, first removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: false,
},
{
name: "execution, multiple, second removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: false,
},
{
name: "execution, multiple, third removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: false,
},
{
name: "execution, multiple, before removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: true,
},
{
name: "execution, multiple, all removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: false,
},
{
name: "execution, multiple, two removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution1", "org1"),
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewSetEvent(context.Background(),
execution.NewAggregate("execution3", "org1"),
[]string{"target"},
[]string{"include"},
),
),
eventFromEventPusher(
execution.NewRemovedEvent(context.Background(),
execution.NewAggregate("execution2", "org1"),
),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"execution1", "execution2", "execution3"},
},
res: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
assert.Equal(t, tt.res, c.existsExecutionsByIDs(tt.args.ctx, tt.args.ids, tt.args.resourceOwner))
})
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -52,8 +52,14 @@ func (c *Commands) AddTarget(ctx context.Context, add *AddTarget, resourceOwner
return nil, err
}
}
wm, err := c.getTargetWriteModelByID(ctx, add.AggregateID, resourceOwner)
if err != nil {
return nil, err
}
if wm.State.Exists() {
return nil, zerrors.ThrowAlreadyExists(nil, "INSTANCE-9axkz0jvzm", "Errors.Target.AlreadyExists")
}
wm := NewTargetWriteModel(add.AggregateID, resourceOwner)
pushedEvents, err := c.eventstore.Push(ctx, target.NewAddedEvent(
ctx,
TargetAggregateFromWriteModel(&wm.WriteModel),
@@ -167,6 +173,15 @@ func (c *Commands) DeleteTarget(ctx context.Context, id, resourceOwner string) (
return writeModelToObjectDetails(&existing.WriteModel), nil
}
func (c *Commands) existsTargetsByIDs(ctx context.Context, ids []string, resourceOwner string) bool {
wm := NewTargetsExistsWriteModel(ids, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, wm)
if err != nil {
return false
}
return wm.AllExists()
}
func (c *Commands) getTargetWriteModelByID(ctx context.Context, id string, resourceOwner string) (*TargetWriteModel, error) {
wm := NewTargetWriteModel(id, resourceOwner)
err := c.eventstore.FilterToQueryReducer(ctx, wm)

View File

@@ -2,11 +2,11 @@ package command
import (
"context"
"slices"
"time"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/action"
"github.com/zitadel/zitadel/internal/repository/target"
)
@@ -62,7 +62,7 @@ func (wm *TargetWriteModel) Reduce() error {
if e.InterruptOnError != nil {
wm.InterruptOnError = *e.InterruptOnError
}
case *action.RemovedEvent:
case *target.RemovedEvent:
wm.State = domain.TargetRemoved
}
}
@@ -116,6 +116,54 @@ func (wm *TargetWriteModel) NewChangedEvent(
return target.NewChangedEvent(ctx, agg, changes)
}
type TargetsExistsWriteModel struct {
eventstore.WriteModel
ids []string
existingIDs []string
}
func (e *TargetsExistsWriteModel) AllExists() bool {
return len(e.ids) == len(e.existingIDs)
}
func NewTargetsExistsWriteModel(ids []string, resourceOwner string) *TargetsExistsWriteModel {
return &TargetsExistsWriteModel{
WriteModel: eventstore.WriteModel{
ResourceOwner: resourceOwner,
InstanceID: resourceOwner,
},
ids: ids,
}
}
func (wm *TargetsExistsWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *target.AddedEvent:
if !slices.Contains(wm.existingIDs, e.Aggregate().ID) {
wm.existingIDs = append(wm.existingIDs, e.Aggregate().ID)
}
case *target.RemovedEvent:
i := slices.Index(wm.existingIDs, e.Aggregate().ID)
if i >= 0 {
wm.existingIDs = slices.Delete(wm.existingIDs, i, i+1)
}
}
}
return wm.WriteModel.Reduce()
}
func (wm *TargetsExistsWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(target.AggregateType).
AggregateIDs(wm.ids...).
EventTypes(target.AddedEventType,
target.RemovedEventType).
Builder()
}
func TargetAggregateFromWriteModel(wm *eventstore.WriteModel) *eventstore.Aggregate {
return &eventstore.Aggregate{
ID: wm.AggregateID,

View File

@@ -0,0 +1,330 @@
package command
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/target"
)
func targetAddEvent(aggID, resourceOwner string) *target.AddedEvent {
return target.NewAddedEvent(context.Background(),
target.NewAggregate(aggID, resourceOwner),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
false,
false,
)
}
func targetRemoveEvent(aggID, resourceOwner string) *target.RemovedEvent {
return target.NewRemovedEvent(context.Background(),
target.NewAggregate(aggID, resourceOwner),
"name",
)
}
func TestCommandSide_targetsExistsWriteModel(t *testing.T) {
type fields struct {
eventstore func(t *testing.T) *eventstore.Eventstore
}
type args struct {
ctx context.Context
ids []string
resourceOwner string
}
tests := []struct {
name string
fields fields
args args
res bool
}{
{
name: "target, single",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target"},
},
res: true,
},
{
name: "target, single reset",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target", "org1"),
),
eventFromEventPusher(
targetAddEvent("target", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target"},
},
res: true,
},
{
name: "target, single before removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetRemoveEvent("target", "org1"),
),
eventFromEventPusher(
targetAddEvent("target", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target"},
},
res: true,
},
{
name: "target, single removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target"},
},
res: false,
},
{
name: "target, multiple",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: true,
},
{
name: "target, multiple, first removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: false,
},
{
name: "target, multiple, second removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: false,
},
{
name: "target, multiple, third removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: false,
},
{
name: "target, multiple, before removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetRemoveEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: true,
},
{
name: "target, multiple, all removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target2", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target3", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: false,
},
{
name: "target, multiple, two removed",
fields: fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
targetAddEvent("target1", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target1", "org1"),
),
eventFromEventPusher(
targetAddEvent("target2", "org1"),
),
eventFromEventPusher(
targetAddEvent("target3", "org1"),
),
eventFromEventPusher(
targetRemoveEvent("target2", "org1"),
),
),
),
},
args: args{
ctx: context.Background(),
ids: []string{"target1", "target2", "target3"},
},
res: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore(t),
}
assert.Equal(t, tt.res, c.existsTargetsByIDs(tt.args.ctx, tt.args.ids, tt.args.resourceOwner))
})
}
}

View File

@@ -122,6 +122,7 @@ func TestCommands_AddTarget(t *testing.T) {
"unique constraint failed, error",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPushFailed(
zerrors.ThrowPreconditionFailed(nil, "id", "name already exists"),
target.NewAddedEvent(context.Background(),
@@ -151,10 +152,43 @@ func TestCommands_AddTarget(t *testing.T) {
err: zerrors.IsPreconditionFailed,
},
},
{
"already existing",
fields{
eventstore: eventstoreExpect(t,
expectFilter(
target.NewAddedEvent(context.Background(),
target.NewAggregate("id1", "org1"),
"name",
domain.TargetTypeWebhook,
"https://example.com",
time.Second,
false,
false,
),
),
),
idGenerator: mock.ExpectID(t, "id1"),
},
args{
ctx: context.Background(),
add: &AddTarget{
Name: "name",
TargetType: domain.TargetTypeWebhook,
Timeout: time.Second,
URL: "https://example.com",
},
resourceOwner: "org1",
},
res{
err: zerrors.IsErrorAlreadyExists,
},
},
{
"push ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
target.NewAddedEvent(context.Background(),
target.NewAggregate("id1", "org1"),
@@ -190,6 +224,7 @@ func TestCommands_AddTarget(t *testing.T) {
"push full ok",
fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
target.NewAddedEvent(context.Background(),
target.NewAggregate("id1", "org1"),

View File

@@ -74,6 +74,12 @@ type Commands struct {
defaultSecretGenerators *SecretGenerators
samlCertificateAndKeyGenerator func(id string) ([]byte, []byte, error)
GrpcMethodExisting func(method string) bool
GrpcServiceExisting func(method string) bool
ActionFunctionExisting func(function string) bool
EventExisting func(event string) bool
EventGroupExisting func(group string) bool
}
func StartCommands(
@@ -132,6 +138,13 @@ func StartCommands(
defaultRefreshTokenIdleLifetime: defaultRefreshTokenIdleLifetime,
defaultSecretGenerators: defaultSecretGenerators,
samlCertificateAndKeyGenerator: samlCertificateAndKeyGenerator(defaults.KeyConfig.Size),
// always true for now until we can check with an eventlist
EventExisting: func(event string) bool { return true },
// always true for now until we can check with an eventlist
EventGroupExisting: func(group string) bool { return true },
GrpcServiceExisting: func(service string) bool { return false },
GrpcMethodExisting: func(method string) bool { return false },
ActionFunctionExisting: domain.FunctionExists(),
}
repo.codeAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)

View File

@@ -0,0 +1,33 @@
package domain
type ExecutionType uint
func (s ExecutionType) Valid() bool {
return s < executionTypeStateCount
}
const (
ExecutionTypeUnspecified ExecutionType = iota
ExecutionTypeRequest
ExecutionTypeResponse
ExecutionTypeFunction
ExecutionTypeEvent
executionTypeStateCount
)
func (e ExecutionType) String() string {
switch e {
case ExecutionTypeUnspecified, executionTypeStateCount:
return ""
case ExecutionTypeRequest:
return "request"
case ExecutionTypeResponse:
return "response"
case ExecutionTypeFunction:
return "function"
case ExecutionTypeEvent:
return "event"
}
return ""
}

View File

@@ -1,6 +1,9 @@
package domain
import "strconv"
import (
"slices"
"strconv"
)
type FlowState int32
@@ -25,6 +28,15 @@ const (
flowTypeCount
)
func AllFlowTypes() []FlowType {
return []FlowType{
FlowTypeExternalAuthentication,
FlowTypeCustomiseToken,
FlowTypeInternalAuthentication,
FlowTypeCustomizeSAMLResponse,
}
}
func (s FlowType) Valid() bool {
return s > 0 && s < flowTypeCount
}
@@ -138,3 +150,20 @@ func (s TriggerType) LocalizationKey() string {
return "Action.TriggerType.Unspecified"
}
}
func AllFunctions() []string {
functions := make([]string, 0)
for _, flowType := range AllFlowTypes() {
for _, triggerType := range flowType.TriggerTypes() {
functions = append(functions, flowType.LocalizationKey()+"."+triggerType.LocalizationKey())
}
}
return functions
}
func FunctionExists() func(string) bool {
functions := AllFunctions()
return func(s string) bool {
return slices.Contains(functions, s)
}
}

View File

@@ -525,3 +525,12 @@ func (s *Tester) CreateTarget(ctx context.Context, t *testing.T) *execution.Crea
require.NoError(t, err)
return target
}
func (s *Tester) SetExecution(ctx context.Context, t *testing.T, cond *execution.SetConditions, targets []string) *execution.SetExecutionResponse {
target, err := s.Client.ExecutionV3.SetExecution(ctx, &execution.SetExecutionRequest{
Condition: cond,
Targets: targets,
})
require.NoError(t, err)
return target
}

View File

@@ -0,0 +1,31 @@
package execution
import (
"strings"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
const (
AggregateType = "execution"
AggregateVersion = "v1"
)
func NewAggregate(aggrID, instanceID string) *eventstore.Aggregate {
return &eventstore.Aggregate{
ID: aggrID,
Type: AggregateType,
ResourceOwner: instanceID,
InstanceID: instanceID,
Version: AggregateVersion,
}
}
func ID(executionType domain.ExecutionType, value string) string {
return strings.Join([]string{executionType.String(), value}, ".")
}
func IDAll(executionType domain.ExecutionType) string {
return executionType.String()
}

View File

@@ -0,0 +1,8 @@
package execution
import "github.com/zitadel/zitadel/internal/eventstore"
func init() {
eventstore.RegisterFilterEventMapper(AggregateType, SetEventType, eventstore.GenericEventMapper[SetEvent])
eventstore.RegisterFilterEventMapper(AggregateType, RemovedEventType, eventstore.GenericEventMapper[RemovedEvent])
}

View File

@@ -0,0 +1,71 @@
package execution
import (
"context"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
const (
eventTypePrefix eventstore.EventType = "execution."
SetEventType = eventTypePrefix + "set"
RemovedEventType = eventTypePrefix + "removed"
)
type SetEvent struct {
*eventstore.BaseEvent `json:"-"`
ExecutionType domain.ExecutionType `json:"executionType"`
Targets []string `json:"targets"`
Includes []string `json:"includes"`
}
func (e *SetEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *SetEvent) Payload() any {
return e
}
func (e *SetEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func NewSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
targets []string,
includes []string,
) *SetEvent {
return &SetEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx, aggregate, SetEventType,
),
Targets: targets,
Includes: includes,
}
}
type RemovedEvent struct {
*eventstore.BaseEvent `json:"-"`
}
func (e *RemovedEvent) SetBaseEvent(b *eventstore.BaseEvent) {
e.BaseEvent = b
}
func (e *RemovedEvent) Payload() any {
return e
}
func (e *RemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
func NewRemovedEvent(ctx context.Context, aggregate *eventstore.Aggregate) *RemovedEvent {
return &RemovedEvent{
eventstore.NewBaseEventForPush(ctx, aggregate, RemovedEventType),
}
}

View File

@@ -557,6 +557,12 @@ Errors:
NoTimeout: Целта няма време за изчакване
InvalidURL: Целта има невалиден URL адрес
NotFound: Целта не е намерена
Execution:
ConditionInvalid: Условието за изпълнение е невалидно
Invalid: Изпълнението е невалидно
NotFound: Изпълнението не е намерено
IncludeNotFound: Включването не е намерено
NoTargets: Няма определени цели
AggregateTypes:
action: Действие
@@ -569,8 +575,12 @@ AggregateTypes:
quota: Квота
feature: Особеност
target: Целта
execution: Екзекуция
EventTypes:
execution:
set: Комплект за изпълнение
removed: Изпълнението е изтрито
target:
added: Целта е създадена
changed: Целта е променена

View File

@@ -537,6 +537,12 @@ Errors:
NoTimeout: Cíl nemá časový limit
InvalidURL: Cíl má neplatnou adresu URL
NotFound: Cíl nenalezen
Execution:
ConditionInvalid: Podmínka provedení je neplatná
Invalid: Provedení je neplatné
NotFound: Provedení nenalezeno
IncludeNotFound: Zahrnout nenalezeno
NoTargets: Nejsou definovány žádné cíle
AggregateTypes:
action: Akce
@@ -549,8 +555,12 @@ AggregateTypes:
quota: Kvóta
feature: Funkce
target: Cíl
execution: Provedení
EventTypes:
execution:
set: Prováděcí sada
removed: Provedení smazáno
target:
added: Cíl vytvořen
changed: Cíl změněn

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: Ziel hat keinen Timeout
InvalidURL: Ziel hat eine ungültige URL
NotFound: Ziel nicht gefunden
Execution:
ConditionInvalid: Die Ausführungsbedingung ist ungültig
Invalid: Die Ausführung ist ungültig
NotFound: Ausführung nicht gefunden
IncludeNotFound: Einschließen nicht gefunden
NoTargets: Keine Ziele definiert
AggregateTypes:
action: Action
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Kontingent
feature: Feature
target: Ziel
execution: Ausführung
EventTypes:
execution:
set: Ausführung gesetzt
removed: Ausführung gelöscht
target:
added: Ziel erstellt
changed: Ziel geändert

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: Target has no timeout
InvalidURL: Target has an invalid URL
NotFound: Target not found
Execution:
ConditionInvalid: Execution condition is invalid
Invalid: Execution is invalid
NotFound: Execution not found
IncludeNotFound: Include not found
NoTargets: No targets defined
AggregateTypes:
action: Action
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Quota
feature: Feature
target: Target
execution: Execution
EventTypes:
execution:
set: Execution set
removed: Execution deleted
target:
added: Target created
changed: Target changed

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: El objetivo no tiene tiempo de espera
InvalidURL: El objetivo tiene una URL no válida
NotFound: El objetivo no encontrado
Execution:
ConditionInvalid: La condición de ejecución no es válida
Invalid: La ejecución no es válida
NotFound: Ejecución no encontrada
IncludeNotFound: Incluir no encontrado
NoTargets: No hay objetivos definidos
AggregateTypes:
action: Acción
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Cuota
feature: Característica
target: Objectivo
execution: Ejecución
EventTypes:
execution:
set: Conjunto de ejecución
removed: Ejecución eliminada
target:
added: Objetivo creado
changed: Objetivo cambiado

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: La cible n'a pas de délai d'attente
InvalidURL: La cible a une URL non valide
NotFound: La cible introuvable
Execution:
ConditionInvalid: La condition d'exécution n'est pas valide
Invalid: L'exécution est invalide
NotFound: Exécution introuvable
IncludeNotFound: Inclure introuvable
NoTargets: Aucune cible définie
AggregateTypes:
action: Action
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Contingent
feature: Fonctionnalité
target: Cible
execution: Exécution
EventTypes:
execution:
set: Ensemble d'exécution
removed: Exécution supprimée
target:
added: Cible créée
changed: Cible modifiée

View File

@@ -541,6 +541,13 @@ Errors:
NoTimeout: Il target non ha timeout
InvalidURL: La destinazione ha un URL non valido
NotFound: Obiettivo non trovato
Execution:
ConditionInvalid: La condizione di esecuzione non è valida
Invalid: L'esecuzione non è valida
NotFound: Esecuzione non trovata
IncludeNotFound: Includi non trovato
NoTargets: Nessun obiettivo definito
AggregateTypes:
action: Azione
@@ -553,8 +560,12 @@ AggregateTypes:
quota: Quota
feature: Funzionalità
target: Bersaglio
execution: Esecuzione
EventTypes:
execution:
set: Insieme di esecuzione
removed: Esecuzione cancellata
target:
added: Obiettivo creato
changed: Obiettivo cambiato

View File

@@ -529,6 +529,12 @@ Errors:
NoTimeout: ターゲットにはタイムアウトがありません
InvalidURL: ターゲットに無効な URL があります
NotFound: ターゲットが見つかりません
Execution:
ConditionInvalid: 実行条件が不正です
Invalid: 実行は無効です
NotFound: 実行が見つかりませんでした
IncludeNotFound: 見つからないものを含める
NoTargets: ターゲットが定義されていません
AggregateTypes:
action: アクション
@@ -541,8 +547,12 @@ AggregateTypes:
quota: クォータ
feature: 特徴
target: 目標
execution: 実行
EventTypes:
execution:
set: 実行セット
removed: 実行は削除されました
target:
added: ターゲットが作成されました
changed: ターゲットが変更されました

View File

@@ -539,6 +539,12 @@ Errors:
NoTimeout: Целта нема тајмаут
InvalidURL: Целта има неважечка URL-адреса
NotFound: Целта не е пронајдена
Execution:
ConditionInvalid: Условот за извршување е неважечки
Invalid: Извршувањето е неважечко
NotFound: Извршувањето не е пронајдено
IncludeNotFound: Вклучете не е пронајден
NoTargets: Не се дефинирани цели
AggregateTypes:
action: Акција
@@ -551,8 +557,12 @@ AggregateTypes:
quota: Квота
feature: Карактеристика
target: Цел
execution: Извршување
EventTypes:
execution:
set: Комплет за извршување
removed: Извршувањето е избришано
target:
added: Целта е избришана
changed: Целта е променета

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: Doel heeft geen time-out
InvalidURL: Doel heeft een ongeldige URL
NotFound: Doel niet gevonden
Execution:
ConditionInvalid: Uitvoeringsvoorwaarde is ongeldig
Invalid: Uitvoering is ongeldig
NotFound: Uitvoering niet gevonden
IncludeNotFound: Inclusief niet gevonden
NoTargets: Geen doelstellingen gedefinieerd
AggregateTypes:
action: Actie
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Quota
feature: Functie
target: Doel
execution: Executie
EventTypes:
execution:
set: Uitvoering ingesteld
removed: Uitvoering verwijderd
target:
added: Doel gemaakt
changed: Doel gewijzigd

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: Cel nie ma limitu czasu
InvalidURL: Cel ma nieprawidłowy adres URL
NotFound: Nie znaleziono celu
Execution:
ConditionInvalid: Warunek wykonania jest nieprawidłowy
Invalid: Wykonanie jest nieprawidłowe
NotFound: Nie znaleziono wykonania
IncludeNotFound: Nie znaleziono uwzględnienia
NoTargets: Nie zdefiniowano celów
AggregateTypes:
action: Działanie
@@ -552,8 +558,12 @@ AggregateTypes:
quota: Limit
feature: Funkcja
target: Cel
execution: Wykonanie
EventTypes:
execution:
set: Zestaw wykonawczy
removed: Wykonanie usunięte
target:
added: Cel został utworzony
changed: Cel zmieniony

View File

@@ -534,6 +534,12 @@ Errors:
NoTimeout: O destino não tem tempo limite
InvalidURL: O destino tem um URL inválido
NotFound: Destino não encontrado
Execution:
ConditionInvalid: A condição de execução é inválida
Invalid: A execução é inválida
NotFound: Execução não encontrada
IncludeNotFound: Incluir não encontrado
NoTargets: Nenhuma meta definida
AggregateTypes:
action: Ação
@@ -545,9 +551,13 @@ AggregateTypes:
usergrant: Concessão de usuário
quota: Cota
feature: Recurso
target: objetivo
target: Objetivo
execution: Execução
EventTypes:
execution:
set: Conjunto de execução
removed: Execução excluída
target:
added: Destino criado
changed: Destino alterada

View File

@@ -528,6 +528,12 @@ Errors:
NoTimeout: У цели нет тайм-аута
InvalidURL: Цель имеет неверный URL-адрес
NotFound: Цель не найдена
Execution:
ConditionInvalid: Недопустимое условие выполнения
Invalid: Исполнение недействительно
NotFound: Исполнение не найдено
IncludeNotFound: Включить не найдено
NoTargets: Цели не определены
AggregateTypes:
action: Действие
@@ -540,8 +546,12 @@ AggregateTypes:
quota: Квота
feature: Особенность
target: мишень
execution: Исполнение
EventTypes:
execution:
set: Набор исполнения
removed: Исполнение удалено
target:
added: Цель создана
changed: Цель изменена

View File

@@ -540,6 +540,12 @@ Errors:
NoTimeout: 目标没有超时
InvalidURL: 目标的 URL 无效
NotFound: 未找到目标
Execution:
ConditionInvalid: 执行条件无效
Invalid: 执行无效
NotFound: 未找到执行
IncludeNotFound: 包括未找到的内容
NoTargets: 没有定义目标
AggregateTypes:
action: 动作
@@ -552,8 +558,12 @@ AggregateTypes:
quota: 配额
feature: 特征
target:
execution: 执行
EventTypes:
execution:
set: 执行集
removed: 执行已删除
target:
added: 目标已创建
changed: 目标改变