feat: add http as sms provider (#8540)

# Which Problems Are Solved

Send SMS messages as a HTTP call to a relay, for own logic on handling
different SMS providers.

# How the Problems Are Solved

Add HTTP as SMS provider type and handling of webhook messages in the
notification handlers.

# Additional Changes

Clean up old Twilio events, which were supposed to handle the general
SMS providers with deactivate, activate and remove.

# Additional Context

Partially closes #8270

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz 2024-09-06 15:11:36 +02:00 committed by GitHub
parent d2e0ac07f1
commit 5bdf1a4547
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 2536 additions and 593 deletions

View File

@ -34,23 +34,23 @@ func (s *Server) GetSMSProvider(ctx context.Context, req *admin_pb.GetSMSProvide
} }
func (s *Server) AddSMSProviderTwilio(ctx context.Context, req *admin_pb.AddSMSProviderTwilioRequest) (*admin_pb.AddSMSProviderTwilioResponse, error) { func (s *Server) AddSMSProviderTwilio(ctx context.Context, req *admin_pb.AddSMSProviderTwilioRequest) (*admin_pb.AddSMSProviderTwilioResponse, error) {
id, result, err := s.command.AddSMSConfigTwilio(ctx, authz.GetInstance(ctx).InstanceID(), AddSMSConfigTwilioToConfig(req)) smsConfig := addSMSConfigTwilioToConfig(ctx, req)
if err != nil { if err := s.command.AddSMSConfigTwilio(ctx, smsConfig); err != nil {
return nil, err return nil, err
} }
return &admin_pb.AddSMSProviderTwilioResponse{ return &admin_pb.AddSMSProviderTwilioResponse{
Details: object.DomainToAddDetailsPb(result), Details: object.DomainToAddDetailsPb(smsConfig.Details),
Id: id, Id: smsConfig.ID,
}, nil }, nil
} }
func (s *Server) UpdateSMSProviderTwilio(ctx context.Context, req *admin_pb.UpdateSMSProviderTwilioRequest) (*admin_pb.UpdateSMSProviderTwilioResponse, error) { func (s *Server) UpdateSMSProviderTwilio(ctx context.Context, req *admin_pb.UpdateSMSProviderTwilioRequest) (*admin_pb.UpdateSMSProviderTwilioResponse, error) {
result, err := s.command.ChangeSMSConfigTwilio(ctx, authz.GetInstance(ctx).InstanceID(), req.Id, UpdateSMSConfigTwilioToConfig(req)) smsConfig := updateSMSConfigTwilioToConfig(ctx, req)
if err != nil { if err := s.command.ChangeSMSConfigTwilio(ctx, smsConfig); err != nil {
return nil, err return nil, err
} }
return &admin_pb.UpdateSMSProviderTwilioResponse{ return &admin_pb.UpdateSMSProviderTwilioResponse{
Details: object.DomainToChangeDetailsPb(result), Details: object.DomainToChangeDetailsPb(smsConfig.Details),
}, nil }, nil
} }
@ -65,6 +65,27 @@ func (s *Server) UpdateSMSProviderTwilioToken(ctx context.Context, req *admin_pb
}, nil }, nil
} }
func (s *Server) AddSMSProviderHTTP(ctx context.Context, req *admin_pb.AddSMSProviderHTTPRequest) (*admin_pb.AddSMSProviderHTTPResponse, error) {
smsConfig := addSMSConfigHTTPToConfig(ctx, req)
if err := s.command.AddSMSConfigHTTP(ctx, smsConfig); err != nil {
return nil, err
}
return &admin_pb.AddSMSProviderHTTPResponse{
Details: object.DomainToAddDetailsPb(smsConfig.Details),
Id: smsConfig.ID,
}, nil
}
func (s *Server) UpdateSMSProviderHTTP(ctx context.Context, req *admin_pb.UpdateSMSProviderHTTPRequest) (*admin_pb.UpdateSMSProviderHTTPResponse, error) {
smsConfig := updateSMSConfigHTTPToConfig(ctx, req)
if err := s.command.ChangeSMSConfigHTTP(ctx, smsConfig); err != nil {
return nil, err
}
return &admin_pb.UpdateSMSProviderHTTPResponse{
Details: object.DomainToChangeDetailsPb(smsConfig.Details),
}, nil
}
func (s *Server) ActivateSMSProvider(ctx context.Context, req *admin_pb.ActivateSMSProviderRequest) (*admin_pb.ActivateSMSProviderResponse, error) { func (s *Server) ActivateSMSProvider(ctx context.Context, req *admin_pb.ActivateSMSProviderRequest) (*admin_pb.ActivateSMSProviderResponse, error) {
result, err := s.command.ActivateSMSConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id) result, err := s.command.ActivateSMSConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil { if err != nil {

View File

@ -1,9 +1,14 @@
package admin package admin
import ( import (
"context"
"github.com/muhlemmer/gu"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
@ -32,6 +37,7 @@ func SMSConfigToProviderPb(config *query.SMSConfig) *settings_pb.SMSProvider {
return &settings_pb.SMSProvider{ return &settings_pb.SMSProvider{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner), Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID, Id: config.ID,
Description: config.Description,
State: smsStateToPb(config.State), State: smsStateToPb(config.State),
Config: SMSConfigToPb(config), Config: SMSConfigToPb(config),
} }
@ -41,9 +47,20 @@ func SMSConfigToPb(config *query.SMSConfig) settings_pb.SMSConfig {
if config.TwilioConfig != nil { if config.TwilioConfig != nil {
return TwilioConfigToPb(config.TwilioConfig) return TwilioConfigToPb(config.TwilioConfig)
} }
if config.HTTPConfig != nil {
return HTTPConfigToPb(config.HTTPConfig)
}
return nil return nil
} }
func HTTPConfigToPb(http *query.HTTP) *settings_pb.SMSProvider_Http {
return &settings_pb.SMSProvider_Http{
Http: &settings_pb.HTTPConfig{
Endpoint: http.Endpoint,
},
}
}
func TwilioConfigToPb(twilio *query.Twilio) *settings_pb.SMSProvider_Twilio { func TwilioConfigToPb(twilio *query.Twilio) *settings_pb.SMSProvider_Twilio {
return &settings_pb.SMSProvider_Twilio{ return &settings_pb.SMSProvider_Twilio{
Twilio: &settings_pb.TwilioConfig{ Twilio: &settings_pb.TwilioConfig{
@ -64,17 +81,39 @@ func smsStateToPb(state domain.SMSConfigState) settings_pb.SMSProviderConfigStat
} }
} }
func AddSMSConfigTwilioToConfig(req *admin_pb.AddSMSProviderTwilioRequest) *twilio.Config { func addSMSConfigTwilioToConfig(ctx context.Context, req *admin_pb.AddSMSProviderTwilioRequest) *command.AddTwilioConfig {
return &twilio.Config{ return &command.AddTwilioConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
SID: req.Sid, SID: req.Sid,
SenderNumber: req.SenderNumber, SenderNumber: req.SenderNumber,
Token: req.Token, Token: req.Token,
} }
} }
func UpdateSMSConfigTwilioToConfig(req *admin_pb.UpdateSMSProviderTwilioRequest) *twilio.Config { func updateSMSConfigTwilioToConfig(ctx context.Context, req *admin_pb.UpdateSMSProviderTwilioRequest) *command.ChangeTwilioConfig {
return &twilio.Config{ return &command.ChangeTwilioConfig{
SID: req.Sid, ResourceOwner: authz.GetInstance(ctx).InstanceID(),
SenderNumber: req.SenderNumber, ID: req.Id,
Description: gu.Ptr(req.Description),
SID: gu.Ptr(req.Sid),
SenderNumber: gu.Ptr(req.SenderNumber),
}
}
func addSMSConfigHTTPToConfig(ctx context.Context, req *admin_pb.AddSMSProviderHTTPRequest) *command.AddSMSHTTP {
return &command.AddSMSHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.GetDescription(),
Endpoint: req.GetEndpoint(),
}
}
func updateSMSConfigHTTPToConfig(ctx context.Context, req *admin_pb.UpdateSMSProviderHTTPRequest) *command.ChangeSMSHTTP {
return &command.ChangeSMSHTTP{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
ID: req.Id,
Description: gu.Ptr(req.Description),
Endpoint: gu.Ptr(req.Endpoint),
} }
} }

View File

@ -5,203 +5,328 @@ import (
"github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
func (c *Commands) AddSMSConfigTwilio(ctx context.Context, instanceID string, config *twilio.Config) (string, *domain.ObjectDetails, error) { type AddTwilioConfig struct {
id, err := c.idGenerator.Next() Details *domain.ObjectDetails
if err != nil { ResourceOwner string
return "", nil, err ID string
Description string
SID string
Token string
SenderNumber string
}
func (c *Commands) AddSMSConfigTwilio(ctx context.Context, config *AddTwilioConfig) (err error) {
if config.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-ZLrZhKSKq0", "Errors.ResourceOwnerMissing")
} }
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if config.ID == "" {
config.ID, err = c.idGenerator.Next()
if err != nil { if err != nil {
return "", nil, err return err
}
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, config.ResourceOwner, config.ID)
if err != nil {
return err
} }
var token *crypto.CryptoValue var token *crypto.CryptoValue
if config.Token != "" { if config.Token != "" {
token, err = crypto.Encrypt([]byte(config.Token), c.smsEncryption) token, err = crypto.Encrypt([]byte(config.Token), c.smsEncryption)
if err != nil { if err != nil {
return "", nil, err return err
} }
} }
err = c.pushAppendAndReduce(ctx,
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel) smsConfigWriteModel,
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTwilioAddedEvent( instance.NewSMSConfigTwilioAddedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id, config.ID,
config.Description,
config.SID, config.SID,
config.SenderNumber, config.SenderNumber,
token)) token,
),
)
if err != nil { if err != nil {
return "", nil, err return err
} }
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...) config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
if err != nil { return nil
return "", nil, err
}
return id, writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) ChangeSMSConfigTwilio(ctx context.Context, instanceID, id string, config *twilio.Config) (*domain.ObjectDetails, error) { type ChangeTwilioConfig struct {
if id == "" { Details *domain.ObjectDetails
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-e9jwf", "Errors.IDMissing") ResourceOwner string
ID string
Description *string
SID *string
Token *string
SenderNumber *string
}
func (c *Commands) ChangeSMSConfigTwilio(ctx context.Context, config *ChangeTwilioConfig) (err error) {
if config.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-RHXryJwmFG", "Errors.ResourceOwnerMissing")
} }
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if config.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-gMr93iNhTR", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, config.ResourceOwner, config.ID)
if err != nil { if err != nil {
return nil, err return err
} }
if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.Twilio == nil { if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.Twilio == nil {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-2m9fw", "Errors.SMSConfig.NotFound") return zerrors.ThrowNotFound(nil, "COMMAND-MUY0IFAf8O", "Errors.SMSConfig.NotFound")
} }
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel) changedEvent, hasChanged, err := smsConfigWriteModel.NewTwilioChangedEvent(
changedEvent, hasChanged, err := smsConfigWriteModel.NewChangedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id, config.ID,
config.Description,
config.SID, config.SID,
config.SenderNumber) config.SenderNumber)
if err != nil { if err != nil {
return nil, err return err
} }
if !hasChanged { if !hasChanged {
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-jf9wk", "Errors.NoChangesFound") config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
} }
pushedEvents, err := c.eventstore.Push(ctx, changedEvent) err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
changedEvent,
)
if err != nil { if err != nil {
return nil, err return err
} }
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...) config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
if err != nil { return nil
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) ChangeSMSConfigTwilioToken(ctx context.Context, instanceID, id, token string) (*domain.ObjectDetails, error) { func (c *Commands) ChangeSMSConfigTwilioToken(ctx context.Context, resourceOwner, id, token string) (*domain.ObjectDetails, error) {
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-sLLA1HnMzj", "Errors.ResourceOwnerMissing")
}
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-PeNaqbC0r0", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, resourceOwner, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.Twilio == nil { if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.Twilio == nil {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-fj9wf", "Errors.SMSConfig.NotFound") return nil, zerrors.ThrowNotFound(nil, "COMMAND-ij3NhEHATp", "Errors.SMSConfig.NotFound")
} }
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel)
newtoken, err := crypto.Encrypt([]byte(token), c.smsEncryption) newtoken, err := crypto.Encrypt([]byte(token), c.smsEncryption)
if err != nil { if err != nil {
return nil, err return nil, err
} }
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTokenChangedEvent( err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigTokenChangedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id, id,
newtoken)) newtoken,
if err != nil { ),
return nil, err )
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) ActivateSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { type AddSMSHTTP struct {
if id == "" { Details *domain.ObjectDetails
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-dn93n", "Errors.IDMissing") ResourceOwner string
ID string
Description string
Endpoint string
}
func (c *Commands) AddSMSConfigHTTP(ctx context.Context, config *AddSMSHTTP) (err error) {
if config.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-huy99qWjX4", "Errors.ResourceOwnerMissing")
} }
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if config.ID == "" {
config.ID, err = c.idGenerator.Next()
if err != nil {
return err
}
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, config.ResourceOwner, config.ID)
if err != nil {
return err
}
err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigHTTPAddedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
config.ID,
config.Description,
config.Endpoint,
),
)
if err != nil {
return err
}
config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
}
type ChangeSMSHTTP struct {
Details *domain.ObjectDetails
ResourceOwner string
ID string
Description *string
Endpoint *string
}
func (c *Commands) ChangeSMSConfigHTTP(ctx context.Context, config *ChangeSMSHTTP) (err error) {
if config.ResourceOwner == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-M622CFQnwK", "Errors.ResourceOwnerMissing")
}
if config.ID == "" {
return zerrors.ThrowInvalidArgument(nil, "COMMAND-phyb2e4Kll", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, config.ResourceOwner, config.ID)
if err != nil {
return err
}
if !smsConfigWriteModel.State.Exists() || smsConfigWriteModel.HTTP == nil {
return zerrors.ThrowNotFound(nil, "COMMAND-6NW4I5Kqzj", "Errors.SMSConfig.NotFound")
}
changedEvent, hasChanged, err := smsConfigWriteModel.NewHTTPChangedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
config.ID,
config.Description,
config.Endpoint)
if err != nil {
return err
}
if !hasChanged {
config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
}
err = c.pushAppendAndReduce(ctx, smsConfigWriteModel, changedEvent)
if err != nil {
return err
}
config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
}
func (c *Commands) ActivateSMSConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-EFgoOg997V", "Errors.ResourceOwnerMissing")
}
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-jJ6TVqzvjp", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, resourceOwner, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !smsConfigWriteModel.State.Exists() { if !smsConfigWriteModel.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-sn9we", "Errors.SMSConfig.NotFound") return nil, zerrors.ThrowNotFound(nil, "COMMAND-9ULtp9PH5E", "Errors.SMSConfig.NotFound")
} }
if smsConfigWriteModel.State == domain.SMSConfigStateActive { if smsConfigWriteModel.State == domain.SMSConfigStateActive {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-sn9we", "Errors.SMSConfig.AlreadyActive") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-B25GFeIvRi", "Errors.SMSConfig.AlreadyActive")
} }
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel) err = c.pushAppendAndReduce(ctx, smsConfigWriteModel,
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTwilioActivatedEvent( instance.NewSMSConfigActivatedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id)) id,
if err != nil { ),
return nil, err )
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) DeactivateSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { func (c *Commands) DeactivateSMSConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
if id == "" { if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-frkwf", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-V9NWOZj8Gi", "Errors.ResourceOwnerMissing")
} }
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-xs1ah1v1CL", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, resourceOwner, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !smsConfigWriteModel.State.Exists() { if !smsConfigWriteModel.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-s39Kg", "Errors.SMSConfig.NotFound") return nil, zerrors.ThrowNotFound(nil, "COMMAND-La91dGNhbM", "Errors.SMSConfig.NotFound")
} }
if smsConfigWriteModel.State == domain.SMSConfigStateInactive { if smsConfigWriteModel.State == domain.SMSConfigStateInactive {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-dm9e3", "Errors.SMSConfig.AlreadyDeactivated") return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-OSZAEkYvk7", "Errors.SMSConfig.AlreadyDeactivated")
} }
err = c.pushAppendAndReduce(ctx,
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel) smsConfigWriteModel,
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigDeactivatedEvent( instance.NewSMSConfigDeactivatedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id)) id,
if err != nil { ),
return nil, err )
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) RemoveSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) { func (c *Commands) RemoveSMSConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
if id == "" { if resourceOwner == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-3j9fs", "Errors.IDMissing") return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-cw0NSJsn1v", "Errors.ResourceOwnerMissing")
} }
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id) if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Qrz7lvdC4c", "Errors.IDMissing")
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, resourceOwner, id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !smsConfigWriteModel.State.Exists() { if !smsConfigWriteModel.State.Exists() {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-sn9we", "Errors.SMSConfig.NotFound") return nil, zerrors.ThrowNotFound(nil, "COMMAND-povEVHPCkV", "Errors.SMSConfig.NotFound")
} }
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel) err = c.pushAppendAndReduce(ctx,
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigRemovedEvent( smsConfigWriteModel,
instance.NewSMSConfigRemovedEvent(
ctx, ctx,
iamAgg, InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id)) id,
if err != nil { ),
return nil, err )
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
} }
func (c *Commands) getSMSConfig(ctx context.Context, instanceID, id string) (_ *IAMSMSConfigWriteModel, err error) { func (c *Commands) getSMSConfig(ctx context.Context, instanceID, id string) (_ *IAMSMSConfigWriteModel, err error) {
writeModel := NewIAMSMSConfigWriteModel(instanceID, id) writeModel := NewIAMSMSConfigWriteModel(instanceID, id)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel) err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return writeModel, nil return writeModel, nil
} }

View File

@ -13,7 +13,9 @@ type IAMSMSConfigWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
ID string ID string
Description string
Twilio *TwilioConfig Twilio *TwilioConfig
HTTP *HTTPConfig
State domain.SMSConfigState State domain.SMSConfigState
} }
@ -23,6 +25,10 @@ type TwilioConfig struct {
SenderNumber string SenderNumber string
} }
type HTTPConfig struct {
Endpoint string
}
func NewIAMSMSConfigWriteModel(instanceID, id string) *IAMSMSConfigWriteModel { func NewIAMSMSConfigWriteModel(instanceID, id string) *IAMSMSConfigWriteModel {
return &IAMSMSConfigWriteModel{ return &IAMSMSConfigWriteModel{
WriteModel: eventstore.WriteModel{ WriteModel: eventstore.WriteModel{
@ -46,11 +52,15 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
Token: e.Token, Token: e.Token,
SenderNumber: e.SenderNumber, SenderNumber: e.SenderNumber,
} }
wm.Description = e.Description
wm.State = domain.SMSConfigStateInactive wm.State = domain.SMSConfigStateInactive
case *instance.SMSConfigTwilioChangedEvent: case *instance.SMSConfigTwilioChangedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
continue continue
} }
if e.Description != nil {
wm.Description = *e.Description
}
if e.SID != nil { if e.SID != nil {
wm.Twilio.SID = *e.SID wm.Twilio.SID = *e.SID
} }
@ -62,6 +72,42 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
continue continue
} }
wm.Twilio.Token = e.Token wm.Twilio.Token = e.Token
case *instance.SMSConfigHTTPAddedEvent:
if wm.ID != e.ID {
continue
}
wm.HTTP = &HTTPConfig{
Endpoint: e.Endpoint,
}
wm.Description = e.Description
wm.State = domain.SMSConfigStateInactive
case *instance.SMSConfigHTTPChangedEvent:
if wm.ID != e.ID {
continue
}
if e.Description != nil {
wm.Description = *e.Description
}
if e.Endpoint != nil {
wm.HTTP.Endpoint = *e.Endpoint
}
case *instance.SMSConfigTwilioActivatedEvent:
if wm.ID != e.ID {
continue
}
wm.State = domain.SMSConfigStateActive
case *instance.SMSConfigTwilioDeactivatedEvent:
if wm.ID != e.ID {
continue
}
wm.State = domain.SMSConfigStateInactive
case *instance.SMSConfigTwilioRemovedEvent:
if wm.ID != e.ID {
continue
}
wm.Twilio = nil
wm.HTTP = nil
wm.State = domain.SMSConfigStateRemoved
case *instance.SMSConfigActivatedEvent: case *instance.SMSConfigActivatedEvent:
if wm.ID != e.ID { if wm.ID != e.ID {
continue continue
@ -77,6 +123,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
continue continue
} }
wm.Twilio = nil wm.Twilio = nil
wm.HTTP = nil
wm.State = domain.SMSConfigStateRemoved wm.State = domain.SMSConfigStateRemoved
} }
} }
@ -92,21 +139,33 @@ func (wm *IAMSMSConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.SMSConfigTwilioAddedEventType, instance.SMSConfigTwilioAddedEventType,
instance.SMSConfigTwilioChangedEventType, instance.SMSConfigTwilioChangedEventType,
instance.SMSConfigTwilioTokenChangedEventType, instance.SMSConfigTwilioTokenChangedEventType,
instance.SMSConfigHTTPAddedEventType,
instance.SMSConfigHTTPChangedEventType,
instance.SMSConfigTwilioActivatedEventType,
instance.SMSConfigTwilioDeactivatedEventType,
instance.SMSConfigTwilioRemovedEventType,
instance.SMSConfigActivatedEventType, instance.SMSConfigActivatedEventType,
instance.SMSConfigDeactivatedEventType, instance.SMSConfigDeactivatedEventType,
instance.SMSConfigRemovedEventType). instance.SMSConfigRemovedEventType).
Builder() Builder()
} }
func (wm *IAMSMSConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id, sid, senderNumber string) (*instance.SMSConfigTwilioChangedEvent, bool, error) { func (wm *IAMSMSConfigWriteModel) NewTwilioChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id string, description, sid, senderNumber *string) (*instance.SMSConfigTwilioChangedEvent, bool, error) {
changes := make([]instance.SMSConfigTwilioChanges, 0) changes := make([]instance.SMSConfigTwilioChanges, 0)
var err error var err error
if wm.Twilio.SID != sid { if wm.Twilio == nil {
changes = append(changes, instance.ChangeSMSConfigTwilioSID(sid)) return nil, false, nil
} }
if wm.Twilio.SenderNumber != senderNumber {
changes = append(changes, instance.ChangeSMSConfigTwilioSenderNumber(senderNumber)) if description != nil && wm.Description != *description {
changes = append(changes, instance.ChangeSMSConfigTwilioDescription(*description))
}
if sid != nil && wm.Twilio.SID != *sid {
changes = append(changes, instance.ChangeSMSConfigTwilioSID(*sid))
}
if senderNumber != nil && wm.Twilio.SenderNumber != *senderNumber {
changes = append(changes, instance.ChangeSMSConfigTwilioSenderNumber(*senderNumber))
} }
if len(changes) == 0 { if len(changes) == 0 {
@ -118,3 +177,28 @@ func (wm *IAMSMSConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate
} }
return changeEvent, true, nil return changeEvent, true, nil
} }
func (wm *IAMSMSConfigWriteModel) NewHTTPChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, id string, description, endpoint *string) (*instance.SMSConfigHTTPChangedEvent, bool, error) {
changes := make([]instance.SMSConfigHTTPChanges, 0)
var err error
if wm.HTTP == nil {
return nil, false, nil
}
if description != nil && wm.Description != *description {
changes = append(changes, instance.ChangeSMSConfigHTTPDescription(*description))
}
if endpoint != nil && wm.HTTP.Endpoint != *endpoint {
changes = append(changes, instance.ChangeSMSConfigHTTPEndpoint(*endpoint))
}
if len(changes) == 0 {
return nil, false, nil
}
changeEvent, err := instance.NewSMSConfigHTTPChangedEvent(ctx, aggregate, id, changes)
if err != nil {
return nil, false, err
}
return changeEvent, true, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -953,6 +953,49 @@ func TestCommandSide_ActivateSMTPConfig(t *testing.T) {
}, },
}, },
}, },
{
name: "activate smtp config, already active, ok",
fields: fields{
eventstore: eventstoreExpect(
t,
expectFilter(
eventFromEventPusher(
instance.NewSMTPConfigAddedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
"test",
true,
"from",
"name",
"",
"host:587",
"user",
&crypto.CryptoValue{},
),
),
),
expectPush(
instance.NewSMTPConfigActivatedEvent(
context.Background(),
&instance.NewAggregate("INSTANCE").Aggregate,
"ID",
),
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
id: "ID",
instanceID: "INSTANCE",
activatedId: "",
},
res: res{
want: &domain.ObjectDetails{
ResourceOwner: "INSTANCE",
},
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {

View File

@ -5,8 +5,8 @@ import (
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/notification/channels/sms"
"github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/handlers" "github.com/zitadel/zitadel/internal/notification/handlers"
"github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/notification/senders"
@ -78,20 +78,20 @@ func (c *channels) Email(ctx context.Context) (*senders.Chain, *smtp.Config, err
return chain, smtpCfg, err return chain, smtpCfg, err
} }
func (c *channels) SMS(ctx context.Context) (*senders.Chain, *twilio.Config, error) { func (c *channels) SMS(ctx context.Context) (*senders.Chain, *sms.Config, error) {
twilioCfg, err := c.q.GetTwilioConfig(ctx) smsCfg, err := c.q.GetActiveSMSConfig(ctx)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
chain, err := senders.SMSChannels( chain, err := senders.SMSChannels(
ctx, ctx,
twilioCfg, smsCfg,
c.q.GetFileSystemProvider, c.q.GetFileSystemProvider,
c.q.GetLogProvider, c.q.GetLogProvider,
c.counters.success.sms, c.counters.success.sms,
c.counters.failed.sms, c.counters.failed.sms,
) )
return chain, twilioCfg, err return chain, smsCfg, err
} }
func (c *channels) Webhook(ctx context.Context, cfg webhook.Config) (*senders.Chain, error) { func (c *channels) Webhook(ctx context.Context, cfg webhook.Config) (*senders.Chain, error) {

View File

@ -0,0 +1,17 @@
package sms
import (
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
)
type Config struct {
ProviderConfig *Provider
TwilioConfig *twilio.Config
WebhookConfig *webhook.Config
}
type Provider struct {
ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
}

View File

@ -0,0 +1,52 @@
package handlers
import (
"context"
"net/http"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/notification/channels/sms"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/zerrors"
)
// GetActiveSMSConfig reads the active iam sms provider config
func (n *NotificationQueries) GetActiveSMSConfig(ctx context.Context) (*sms.Config, error) {
config, err := n.SMSProviderConfigActive(ctx, authz.GetInstance(ctx).InstanceID())
if err != nil {
return nil, err
}
provider := &sms.Provider{
ID: config.ID,
Description: config.Description,
}
if config.TwilioConfig != nil {
token, err := crypto.DecryptString(config.TwilioConfig.Token, n.SMSTokenCrypto)
if err != nil {
return nil, err
}
return &sms.Config{
ProviderConfig: provider,
TwilioConfig: &twilio.Config{
SID: config.TwilioConfig.SID,
Token: token,
SenderNumber: config.TwilioConfig.SenderNumber,
},
}, nil
}
if config.HTTPConfig != nil {
return &sms.Config{
ProviderConfig: provider,
WebhookConfig: &webhook.Config{
CallURL: config.HTTPConfig.Endpoint,
Method: http.MethodPost,
Headers: nil,
},
}, nil
}
return nil, zerrors.ThrowNotFound(nil, "HANDLER-8nfow", "Errors.SMS.Twilio.NotFound")
}

View File

@ -1,35 +0,0 @@
package handlers
import (
"context"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
)
// GetTwilioConfig reads the iam Twilio provider config
func (n *NotificationQueries) GetTwilioConfig(ctx context.Context) (*twilio.Config, error) {
active, err := query.NewSMSProviderStateQuery(domain.SMSConfigStateActive)
if err != nil {
return nil, err
}
config, err := n.SMSProviderConfig(ctx, active)
if err != nil {
return nil, err
}
if config.TwilioConfig == nil {
return nil, zerrors.ThrowNotFound(nil, "HANDLER-8nfow", "Errors.SMS.Twilio.NotFound")
}
token, err := crypto.DecryptString(config.TwilioConfig.Token, n.SMSTokenCrypto)
if err != nil {
return nil, err
}
return &twilio.Config{
SID: config.TwilioConfig.SID,
Token: token,
SenderNumber: config.TwilioConfig.SenderNumber,
}, nil
}

View File

@ -161,24 +161,19 @@ func (mr *MockQueriesMockRecorder) NotificationProviderByIDAndType(arg0, arg1, a
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationProviderByIDAndType", reflect.TypeOf((*MockQueries)(nil).NotificationProviderByIDAndType), arg0, arg1, arg2) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NotificationProviderByIDAndType", reflect.TypeOf((*MockQueries)(nil).NotificationProviderByIDAndType), arg0, arg1, arg2)
} }
// SMSProviderConfig mocks base method. // SMSProviderConfigActive mocks base method.
func (m *MockQueries) SMSProviderConfig(arg0 context.Context, arg1 ...query.SearchQuery) (*query.SMSConfig, error) { func (m *MockQueries) SMSProviderConfigActive(arg0 context.Context, arg1 string) (*query.SMSConfig, error) {
m.ctrl.T.Helper() m.ctrl.T.Helper()
varargs := []any{arg0} ret := m.ctrl.Call(m, "SMSProviderConfigActive", arg0, arg1)
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SMSProviderConfig", varargs...)
ret0, _ := ret[0].(*query.SMSConfig) ret0, _ := ret[0].(*query.SMSConfig)
ret1, _ := ret[1].(error) ret1, _ := ret[1].(error)
return ret0, ret1 return ret0, ret1
} }
// SMSProviderConfig indicates an expected call of SMSProviderConfig. // SMSProviderConfigActive indicates an expected call of SMSProviderConfigActive.
func (mr *MockQueriesMockRecorder) SMSProviderConfig(arg0 any, arg1 ...any) *gomock.Call { func (mr *MockQueriesMockRecorder) SMSProviderConfigActive(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper() mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfigActive", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfigActive), arg0, arg1)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfig", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfig), varargs...)
} }
// SMTPConfigActive mocks base method. // SMTPConfigActive mocks base method.

View File

@ -21,7 +21,7 @@ type Queries interface {
NotificationPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (*query.NotificationPolicy, error) NotificationPolicyByOrg(ctx context.Context, shouldTriggerBulk bool, orgID string, withOwnerRemoved bool) (*query.NotificationPolicy, error)
SearchMilestones(ctx context.Context, instanceIDs []string, queries *query.MilestonesSearchQueries) (*query.Milestones, error) SearchMilestones(ctx context.Context, instanceIDs []string, queries *query.MilestonesSearchQueries) (*query.Milestones, error)
NotificationProviderByIDAndType(ctx context.Context, aggID string, providerType domain.NotificationProviderType) (*query.DebugNotificationProvider, error) NotificationProviderByIDAndType(ctx context.Context, aggID string, providerType domain.NotificationProviderType) (*query.DebugNotificationProvider, error)
SMSProviderConfig(ctx context.Context, queries ...query.SearchQuery) (*query.SMSConfig, error) SMSProviderConfigActive(ctx context.Context, resourceOwner string) (config *query.SMSConfig, err error)
SMTPConfigActive(ctx context.Context, resourceOwner string) (*query.SMTPConfig, error) SMTPConfigActive(ctx context.Context, resourceOwner string) (*query.SMTPConfig, error)
GetDefaultLanguage(ctx context.Context) language.Tag GetDefaultLanguage(ctx context.Context) language.Tag
GetInstanceRestrictions(ctx context.Context) (restrictions query.Restrictions, err error) GetInstanceRestrictions(ctx context.Context) (restrictions query.Restrictions, err error)

View File

@ -283,7 +283,7 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
} }
notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e) notify := types.SendEmail(ctx, u.channels, string(template.Template), translator, notifyUser, colors, e)
if e.NotificationType == domain.NotificationTypeSms { if e.NotificationType == domain.NotificationTypeSms {
notify = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e) notify = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e)
} }
err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID) err = notify.SendPasswordCode(ctx, notifyUser, code, e.URLTemplate, e.AuthRequestID)
if err != nil { if err != nil {
@ -373,7 +373,7 @@ func (u *userNotifier) reduceOTPSMS(
if err != nil { if err != nil {
return nil, err return nil, err
} }
notify := types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, event) notify := types.SendSMS(ctx, u.channels, translator, notifyUser, colors, event)
err = notify.SendOTPSMSCode(ctx, plainCode, expiry) err = notify.SendOTPSMSCode(ctx, plainCode, expiry)
if err != nil { if err != nil {
return nil, err return nil, err
@ -709,7 +709,7 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
if err != nil { if err != nil {
return err return err
} }
err = types.SendSMSTwilio(ctx, u.channels, translator, notifyUser, colors, e). err = types.SendSMS(ctx, u.channels, translator, notifyUser, colors, e).
SendPhoneVerificationCode(ctx, code) SendPhoneVerificationCode(ctx, code)
if err != nil { if err != nil {
return err return err

View File

@ -16,8 +16,8 @@ import (
"github.com/zitadel/zitadel/internal/eventstore/repository" "github.com/zitadel/zitadel/internal/eventstore/repository"
es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock" es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/mock"
channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock" channel_mock "github.com/zitadel/zitadel/internal/notification/channels/mock"
"github.com/zitadel/zitadel/internal/notification/channels/sms"
"github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/handlers/mock" "github.com/zitadel/zitadel/internal/notification/handlers/mock"
"github.com/zitadel/zitadel/internal/notification/messages" "github.com/zitadel/zitadel/internal/notification/messages"
@ -1463,7 +1463,7 @@ func (c *channels) Email(context.Context) (*senders.Chain, *smtp.Config, error)
return &c.Chain, nil, nil return &c.Chain, nil, nil
} }
func (c *channels) SMS(context.Context) (*senders.Chain, *twilio.Config, error) { func (c *channels) SMS(context.Context) (*senders.Chain, *sms.Config, error) {
return &c.Chain, nil, nil return &c.Chain, nil, nil
} }

View File

@ -3,36 +3,60 @@ package senders
import ( import (
"context" "context"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/notification/channels" "github.com/zitadel/zitadel/internal/notification/channels"
"github.com/zitadel/zitadel/internal/notification/channels/fs" "github.com/zitadel/zitadel/internal/notification/channels/fs"
"github.com/zitadel/zitadel/internal/notification/channels/instrumenting" "github.com/zitadel/zitadel/internal/notification/channels/instrumenting"
"github.com/zitadel/zitadel/internal/notification/channels/log" "github.com/zitadel/zitadel/internal/notification/channels/log"
"github.com/zitadel/zitadel/internal/notification/channels/sms"
"github.com/zitadel/zitadel/internal/notification/channels/twilio" "github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
) )
const twilioSpanName = "twilio.NotificationChannel" const twilioSpanName = "twilio.NotificationChannel"
func SMSChannels( func SMSChannels(
ctx context.Context, ctx context.Context,
twilioConfig *twilio.Config, smsConfig *sms.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error), getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error), getLogProvider func(ctx context.Context) (*log.Config, error),
successMetricName, successMetricName,
failureMetricName string, failureMetricName string,
) (chain *Chain, err error) { ) (chain *Chain, err error) {
channels := make([]channels.NotificationChannel, 0, 3) channels := make([]channels.NotificationChannel, 0, 3)
if twilioConfig != nil { if smsConfig.TwilioConfig != nil {
channels = append( channels = append(
channels, channels,
instrumenting.Wrap( instrumenting.Wrap(
ctx, ctx,
twilio.InitChannel(*twilioConfig), twilio.InitChannel(*smsConfig.TwilioConfig),
twilioSpanName, twilioSpanName,
successMetricName, successMetricName,
failureMetricName, failureMetricName,
), ),
) )
} }
if smsConfig.WebhookConfig != nil {
webhookChannel, err := webhook.InitChannel(ctx, *smsConfig.WebhookConfig)
logging.WithFields(
"instance", authz.GetInstance(ctx).InstanceID(),
"callurl", smsConfig.WebhookConfig.CallURL,
).OnError(err).Debug("initializing JSON channel failed")
if err == nil {
channels = append(
channels,
instrumenting.Wrap(
ctx,
webhookChannel,
webhookSpanName,
successMetricName,
failureMetricName,
),
)
}
}
channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...) channels = append(channels, debugChannels(ctx, getFileSystemProvider, getLogProvider)...)
return ChainChannels(channels...), nil return ChainChannels(channels...), nil
} }

View File

@ -15,23 +15,23 @@ const (
) )
type TemplateData struct { type TemplateData struct {
Title string Title string `json:"title,omitempty"`
PreHeader string PreHeader string `json:"preHeader,omitempty"`
Subject string Subject string `json:"subject,omitempty"`
Greeting string Greeting string `json:"greeting,omitempty"`
Text string Text string `json:"text,omitempty"`
URL string URL string `json:"url,omitempty"`
ButtonText string ButtonText string `json:"buttonText,omitempty"`
PrimaryColor string PrimaryColor string `json:"primaryColor,omitempty"`
BackgroundColor string BackgroundColor string `json:"backgroundColor,omitempty"`
FontColor string FontColor string `json:"fontColor,omitempty"`
LogoURL string LogoURL string `json:"logoUrl,omitempty"`
FontURL string FontURL string `json:"fontUrl,omitempty"`
FontFaceFamily string FontFaceFamily string `json:"fontFaceFamily,omitempty"`
FontFamily string FontFamily string `json:"fontFamily,omitempty"`
IncludeFooter bool IncludeFooter bool `json:"includeFooter,omitempty"`
FooterText string FooterText string `json:"footerText,omitempty"`
} }
func (data *TemplateData) Translate(translator *i18n.Translator, msgType string, args map[string]interface{}, langs ...string) { func (data *TemplateData) Translate(translator *i18n.Translator, msgType string, args map[string]interface{}, langs ...string) {

View File

@ -7,8 +7,8 @@ import (
"github.com/zitadel/zitadel/internal/database" "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/i18n" "github.com/zitadel/zitadel/internal/i18n"
"github.com/zitadel/zitadel/internal/notification/channels/sms"
"github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/notification/channels/smtp"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook" "github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/senders" "github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/templates" "github.com/zitadel/zitadel/internal/notification/templates"
@ -24,7 +24,7 @@ type Notify func(
type ChannelChains interface { type ChannelChains interface {
Email(context.Context) (*senders.Chain, *smtp.Config, error) Email(context.Context) (*senders.Chain, *smtp.Config, error)
SMS(context.Context) (*senders.Chain, *twilio.Config, error) SMS(context.Context) (*senders.Chain, *sms.Config, error)
Webhook(context.Context, webhook.Config) (*senders.Chain, error) Webhook(context.Context, webhook.Config) (*senders.Chain, error)
} }
@ -79,7 +79,7 @@ func sanitizeArgsForHTML(args map[string]any) {
} }
} }
func SendSMSTwilio( func SendSMS(
ctx context.Context, ctx context.Context,
channels ChannelChains, channels ChannelChains,
translator *i18n.Translator, translator *i18n.Translator,
@ -99,7 +99,8 @@ func SendSMSTwilio(
ctx, ctx,
channels, channels,
user, user,
data.Text, data,
args,
allowUnverifiedNotificationChannel, allowUnverifiedNotificationChannel,
triggeringEvent, triggeringEvent,
) )

View File

@ -2,40 +2,78 @@ package types
import ( import (
"context" "context"
"strings"
"github.com/zitadel/logging" "github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/notification/messages" "github.com/zitadel/zitadel/internal/notification/messages"
"github.com/zitadel/zitadel/internal/notification/templates"
"github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors" "github.com/zitadel/zitadel/internal/zerrors"
) )
type serializableData struct {
ContextInfo map[string]interface{} `json:"contextInfo,omitempty"`
TemplateData templates.TemplateData `json:"templateData,omitempty"`
Args map[string]interface{} `json:"args,omitempty"`
}
func generateSms( func generateSms(
ctx context.Context, ctx context.Context,
channels ChannelChains, channels ChannelChains,
user *query.NotifyUser, user *query.NotifyUser,
content string, data templates.TemplateData,
args map[string]interface{},
lastPhone bool, lastPhone bool,
triggeringEvent eventstore.Event, triggeringEvent eventstore.Event,
) error { ) error {
number := "" smsChannels, config, err := channels.SMS(ctx)
smsChannels, twilioConfig, err := channels.SMS(ctx)
logging.OnError(err).Error("could not create sms channel") logging.OnError(err).Error("could not create sms channel")
if smsChannels == nil || smsChannels.Len() == 0 { if smsChannels == nil || smsChannels.Len() == 0 {
return zerrors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent") return zerrors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
} }
recipient := user.VerifiedPhone
if lastPhone {
recipient = user.LastPhone
}
if config.TwilioConfig != nil {
number := ""
if err == nil { if err == nil {
number = twilioConfig.SenderNumber number = config.TwilioConfig.SenderNumber
} }
message := &messages.SMS{ message := &messages.SMS{
SenderPhoneNumber: number, SenderPhoneNumber: number,
RecipientPhoneNumber: user.VerifiedPhone, RecipientPhoneNumber: recipient,
Content: content, Content: data.Text,
TriggeringEvent: triggeringEvent, TriggeringEvent: triggeringEvent,
} }
if lastPhone {
message.RecipientPhoneNumber = user.LastPhone
}
return smsChannels.HandleMessage(message) return smsChannels.HandleMessage(message)
}
if config.WebhookConfig != nil {
caseArgs := make(map[string]interface{}, len(args))
for k, v := range args {
caseArgs[strings.ToLower(string(k[0]))+k[1:]] = v
}
contextInfo := map[string]interface{}{
"recipientPhoneNumber": recipient,
"eventType": triggeringEvent.Type(),
"provider": config.ProviderConfig,
}
message := &messages.JSON{
Serializable: &serializableData{
TemplateData: data,
Args: caseArgs,
ContextInfo: contextInfo,
},
TriggeringEvent: triggeringEvent,
}
webhookChannels, err := channels.Webhook(ctx, *config.WebhookConfig)
if err != nil {
return err
}
return webhookChannels.HandleMessage(message)
}
return zerrors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
} }

View File

@ -8,12 +8,12 @@ import (
old_handler "github.com/zitadel/zitadel/internal/eventstore/handler" old_handler "github.com/zitadel/zitadel/internal/eventstore/handler"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2" "github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/repository/instance" "github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
) )
const ( const (
SMSConfigProjectionTable = "projections.sms_configs2" SMSConfigProjectionTable = "projections.sms_configs3"
SMSTwilioTable = SMSConfigProjectionTable + "_" + smsTwilioTableSuffix SMSTwilioTable = SMSConfigProjectionTable + "_" + smsTwilioTableSuffix
SMSHTTPTable = SMSConfigProjectionTable + "_" + smsHTTPTableSuffix
SMSColumnID = "id" SMSColumnID = "id"
SMSColumnAggregateID = "aggregate_id" SMSColumnAggregateID = "aggregate_id"
@ -23,13 +23,19 @@ const (
SMSColumnState = "state" SMSColumnState = "state"
SMSColumnResourceOwner = "resource_owner" SMSColumnResourceOwner = "resource_owner"
SMSColumnInstanceID = "instance_id" SMSColumnInstanceID = "instance_id"
SMSColumnDescription = "description"
smsTwilioTableSuffix = "twilio" smsTwilioTableSuffix = "twilio"
SMSTwilioConfigColumnSMSID = "sms_id" SMSTwilioColumnSMSID = "sms_id"
SMSTwilioColumnInstanceID = "instance_id" SMSTwilioColumnInstanceID = "instance_id"
SMSTwilioConfigColumnSID = "sid" SMSTwilioColumnSID = "sid"
SMSTwilioConfigColumnSenderNumber = "sender_number" SMSTwilioColumnSenderNumber = "sender_number"
SMSTwilioConfigColumnToken = "token" SMSTwilioColumnToken = "token"
smsHTTPTableSuffix = "http"
SMSHTTPColumnSMSID = "sms_id"
SMSHTTPColumnInstanceID = "instance_id"
SMSHTTPColumnEndpoint = "endpoint"
) )
type smsConfigProjection struct{} type smsConfigProjection struct{}
@ -53,20 +59,30 @@ func (*smsConfigProjection) Init() *old_handler.Check {
handler.NewColumn(SMSColumnState, handler.ColumnTypeEnum), handler.NewColumn(SMSColumnState, handler.ColumnTypeEnum),
handler.NewColumn(SMSColumnResourceOwner, handler.ColumnTypeText), handler.NewColumn(SMSColumnResourceOwner, handler.ColumnTypeText),
handler.NewColumn(SMSColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(SMSColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSColumnDescription, handler.ColumnTypeText),
}, },
handler.NewPrimaryKey(SMSColumnInstanceID, SMSColumnID), handler.NewPrimaryKey(SMSColumnInstanceID, SMSColumnID),
), ),
handler.NewSuffixedTable([]*handler.InitColumn{ handler.NewSuffixedTable([]*handler.InitColumn{
handler.NewColumn(SMSTwilioConfigColumnSMSID, handler.ColumnTypeText), handler.NewColumn(SMSTwilioColumnSMSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioColumnInstanceID, handler.ColumnTypeText), handler.NewColumn(SMSTwilioColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnSID, handler.ColumnTypeText), handler.NewColumn(SMSTwilioColumnSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnSenderNumber, handler.ColumnTypeText), handler.NewColumn(SMSTwilioColumnSenderNumber, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnToken, handler.ColumnTypeJSONB), handler.NewColumn(SMSTwilioColumnToken, handler.ColumnTypeJSONB),
}, },
handler.NewPrimaryKey(SMSTwilioColumnInstanceID, SMSTwilioConfigColumnSMSID), handler.NewPrimaryKey(SMSTwilioColumnInstanceID, SMSTwilioColumnSMSID),
smsTwilioTableSuffix, smsTwilioTableSuffix,
handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()), handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()),
), ),
handler.NewSuffixedTable([]*handler.InitColumn{
handler.NewColumn(SMSHTTPColumnSMSID, handler.ColumnTypeText),
handler.NewColumn(SMSHTTPColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSHTTPColumnEndpoint, handler.ColumnTypeText),
},
handler.NewPrimaryKey(SMSHTTPColumnInstanceID, SMSHTTPColumnSMSID),
smsHTTPTableSuffix,
handler.WithForeignKey(handler.NewForeignKeyOfPublicKeys()),
),
) )
} }
@ -87,6 +103,26 @@ func (p *smsConfigProjection) Reducers() []handler.AggregateReducer {
Event: instance.SMSConfigTwilioTokenChangedEventType, Event: instance.SMSConfigTwilioTokenChangedEventType,
Reduce: p.reduceSMSConfigTwilioTokenChanged, Reduce: p.reduceSMSConfigTwilioTokenChanged,
}, },
{
Event: instance.SMSConfigHTTPAddedEventType,
Reduce: p.reduceSMSConfigHTTPAdded,
},
{
Event: instance.SMSConfigHTTPChangedEventType,
Reduce: p.reduceSMSConfigHTTPChanged,
},
{
Event: instance.SMSConfigTwilioActivatedEventType,
Reduce: p.reduceSMSConfigTwilioActivated,
},
{
Event: instance.SMSConfigTwilioDeactivatedEventType,
Reduce: p.reduceSMSConfigTwilioDeactivated,
},
{
Event: instance.SMSConfigTwilioRemovedEventType,
Reduce: p.reduceSMSConfigTwilioRemoved,
},
{ {
Event: instance.SMSConfigActivatedEventType, Event: instance.SMSConfigActivatedEventType,
Reduce: p.reduceSMSConfigActivated, Reduce: p.reduceSMSConfigActivated,
@ -109,9 +145,9 @@ func (p *smsConfigProjection) Reducers() []handler.AggregateReducer {
} }
func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigTwilioAddedEvent) e, err := assertEvent[*instance.SMSConfigTwilioAddedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-s8efs", "reduce.wrong.event.type %s", instance.SMSConfigTwilioAddedEventType) return nil, err
} }
return handler.NewMultiStatement( return handler.NewMultiStatement(
@ -126,15 +162,16 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event)
handler.NewCol(SMSColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMSColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive), handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnSequence, e.Sequence()), handler.NewCol(SMSColumnSequence, e.Sequence()),
handler.NewCol(SMSColumnDescription, e.Description),
}, },
), ),
handler.AddCreateStatement( handler.AddCreateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(SMSTwilioConfigColumnSMSID, e.ID), handler.NewCol(SMSTwilioColumnSMSID, e.ID),
handler.NewCol(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID), handler.NewCol(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSTwilioConfigColumnSID, e.SID), handler.NewCol(SMSTwilioColumnSID, e.SID),
handler.NewCol(SMSTwilioConfigColumnToken, e.Token), handler.NewCol(SMSTwilioColumnToken, e.Token),
handler.NewCol(SMSTwilioConfigColumnSenderNumber, e.SenderNumber), handler.NewCol(SMSTwilioColumnSenderNumber, e.SenderNumber),
}, },
handler.WithTableSuffix(smsTwilioTableSuffix), handler.WithTableSuffix(smsTwilioTableSuffix),
), ),
@ -142,57 +179,64 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event)
} }
func (p *smsConfigProjection) reduceSMSConfigTwilioChanged(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigTwilioChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigTwilioChangedEvent) e, err := assertEvent[*instance.SMSConfigTwilioChangedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fi99F", "reduce.wrong.event.type %s", instance.SMSConfigTwilioChangedEventType) return nil, err
}
columns := make([]handler.Column, 0)
if e.SID != nil {
columns = append(columns, handler.NewCol(SMSTwilioConfigColumnSID, *e.SID))
}
if e.SenderNumber != nil {
columns = append(columns, handler.NewCol(SMSTwilioConfigColumnSenderNumber, *e.SenderNumber))
} }
return handler.NewMultiStatement( stmts := make([]func(eventstore.Event) handler.Exec, 0, 3)
e, columns := []handler.Column{
handler.AddUpdateStatement(
columns,
[]handler.Condition{
handler.NewCond(SMSTwilioConfigColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(smsTwilioTableSuffix),
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSColumnChangeDate, e.CreationDate()), handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()), handler.NewCol(SMSColumnSequence, e.Sequence()),
}, }
if e.Description != nil {
columns = append(columns, handler.NewCol(SMSColumnDescription, *e.Description))
}
if len(columns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement(
columns,
[]handler.Condition{ []handler.Condition{
handler.NewCond(SMSColumnID, e.ID), handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
}, },
), ))
), nil }
twilioColumns := make([]handler.Column, 0)
if e.SID != nil {
twilioColumns = append(twilioColumns, handler.NewCol(SMSTwilioColumnSID, *e.SID))
}
if e.SenderNumber != nil {
twilioColumns = append(twilioColumns, handler.NewCol(SMSTwilioColumnSenderNumber, *e.SenderNumber))
}
if len(twilioColumns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement(
twilioColumns,
[]handler.Condition{
handler.NewCond(SMSTwilioColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(smsTwilioTableSuffix),
))
}
return handler.NewMultiStatement(e, stmts...), nil
} }
func (p *smsConfigProjection) reduceSMSConfigTwilioTokenChanged(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigTwilioTokenChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigTwilioTokenChangedEvent) e, err := assertEvent[*instance.SMSConfigTwilioTokenChangedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fi99F", "reduce.wrong.event.type %s", instance.SMSConfigTwilioTokenChangedEventType) return nil, err
}
columns := make([]handler.Column, 0)
if e.Token != nil {
columns = append(columns, handler.NewCol(SMSTwilioConfigColumnToken, e.Token))
} }
return handler.NewMultiStatement( return handler.NewMultiStatement(
e, e,
handler.AddUpdateStatement( handler.AddUpdateStatement(
columns, []handler.Column{
handler.NewCol(SMSTwilioColumnToken, e.Token),
},
[]handler.Condition{ []handler.Condition{
handler.NewCond(SMSTwilioConfigColumnSMSID, e.ID), handler.NewCond(SMSTwilioColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
}, },
handler.WithTableSuffix(smsTwilioTableSuffix), handler.WithTableSuffix(smsTwilioTableSuffix),
@ -210,13 +254,99 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioTokenChanged(event eventstore
), nil ), nil
} }
func (p *smsConfigProjection) reduceSMSConfigActivated(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigHTTPAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigActivatedEvent) e, err := assertEvent[*instance.SMSConfigHTTPAddedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fj9Ef", "reduce.wrong.event.type %s", instance.SMSConfigActivatedEventType) return nil, err
} }
return handler.NewUpdateStatement(
return handler.NewMultiStatement(
e, e,
handler.AddCreateStatement(
[]handler.Column{
handler.NewCol(SMSColumnID, e.ID),
handler.NewCol(SMSColumnAggregateID, e.Aggregate().ID),
handler.NewCol(SMSColumnCreationDate, e.CreationDate()),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnResourceOwner, e.Aggregate().ResourceOwner),
handler.NewCol(SMSColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnSequence, e.Sequence()),
handler.NewCol(SMSColumnDescription, e.Description),
},
),
handler.AddCreateStatement(
[]handler.Column{
handler.NewCol(SMSHTTPColumnSMSID, e.ID),
handler.NewCol(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSHTTPColumnEndpoint, e.Endpoint),
},
handler.WithTableSuffix(smsHTTPTableSuffix),
),
), nil
}
func (p *smsConfigProjection) reduceSMSConfigHTTPChanged(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigHTTPChangedEvent](event)
if err != nil {
return nil, err
}
stmts := make([]func(eventstore.Event) handler.Exec, 0, 3)
columns := []handler.Column{
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
}
if e.Description != nil {
columns = append(columns, handler.NewCol(SMSColumnDescription, *e.Description))
}
if len(columns) > 0 {
stmts = append(stmts, handler.AddUpdateStatement(
columns,
[]handler.Condition{
handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
))
}
if e.Endpoint != nil {
stmts = append(stmts, handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSHTTPColumnEndpoint, *e.Endpoint),
},
[]handler.Condition{
handler.NewCond(SMSHTTPColumnSMSID, e.ID),
handler.NewCond(SMSHTTPColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(smsHTTPTableSuffix),
))
}
return handler.NewMultiStatement(e, stmts...), nil
}
func (p *smsConfigProjection) reduceSMSConfigTwilioActivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigTwilioActivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.Not(handler.NewCond(SMSColumnID, e.ID)),
handler.NewCond(SMSColumnState, domain.SMSConfigStateActive),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
),
handler.AddUpdateStatement(
[]handler.Column{ []handler.Column{
handler.NewCol(SMSColumnState, domain.SMSConfigStateActive), handler.NewCol(SMSColumnState, domain.SMSConfigStateActive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()), handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
@ -226,14 +356,85 @@ func (p *smsConfigProjection) reduceSMSConfigActivated(event eventstore.Event) (
handler.NewCond(SMSColumnID, e.ID), handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID), handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
}, },
),
), nil
}
func (p *smsConfigProjection) reduceSMSConfigTwilioDeactivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigTwilioDeactivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewUpdateStatement(
e,
[]handler.Column{
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
), nil
}
func (p *smsConfigProjection) reduceSMSConfigTwilioRemoved(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigTwilioRemovedEvent](event)
if err != nil {
return nil, err
}
return handler.NewDeleteStatement(
e,
[]handler.Condition{
handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
), nil
}
func (p *smsConfigProjection) reduceSMSConfigActivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigActivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.Not(handler.NewCond(SMSColumnID, e.ID)),
handler.NewCond(SMSColumnState, domain.SMSConfigStateActive),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSColumnState, domain.SMSConfigStateActive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(SMSColumnID, e.ID),
handler.NewCond(SMSColumnInstanceID, e.Aggregate().InstanceID),
},
),
), nil ), nil
} }
func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigDeactivatedEvent) e, err := assertEvent[*instance.SMSConfigDeactivatedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-dj9Js", "reduce.wrong.event.type %s", instance.SMSConfigDeactivatedEventType) return nil, err
} }
return handler.NewUpdateStatement( return handler.NewUpdateStatement(
e, e,
[]handler.Column{ []handler.Column{
@ -249,10 +450,11 @@ func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event)
} }
func (p *smsConfigProjection) reduceSMSConfigRemoved(event eventstore.Event) (*handler.Statement, error) { func (p *smsConfigProjection) reduceSMSConfigRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigRemovedEvent) e, err := assertEvent[*instance.SMSConfigRemovedEvent](event)
if !ok { if err != nil {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-s9JJf", "reduce.wrong.event.type %s", instance.SMSConfigRemovedEventType) return nil, err
} }
return handler.NewDeleteStatement( return handler.NewDeleteStatement(
e, e,
[]handler.Condition{ []handler.Condition{

View File

@ -37,9 +37,10 @@ func TestSMSProjection_reduces(t *testing.T) {
"keyId": "key-id", "keyId": "key-id",
"crypted": "Y3J5cHRlZA==" "crypted": "Y3J5cHRlZA=="
}, },
"senderNumber": "sender-number" "senderNumber": "sender-number",
"description": "description"
}`), }`),
), instance.SMSConfigTwilioAddedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigTwilioAddedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioAdded, reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioAdded,
want: wantReduce{ want: wantReduce{
@ -48,7 +49,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "INSERT INTO projections.sms_configs2 (id, aggregate_id, creation_date, change_date, resource_owner, instance_id, state, sequence) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)", expectedStmt: "INSERT INTO projections.sms_configs3 (id, aggregate_id, creation_date, change_date, resource_owner, instance_id, state, sequence, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"id", "id",
"agg-id", "agg-id",
@ -58,10 +59,11 @@ func TestSMSProjection_reduces(t *testing.T) {
"instance-id", "instance-id",
domain.SMSConfigStateInactive, domain.SMSConfigStateInactive,
uint64(15), uint64(15),
"description",
}, },
}, },
{ {
expectedStmt: "INSERT INTO projections.sms_configs2_twilio (sms_id, instance_id, sid, token, sender_number) VALUES ($1, $2, $3, $4, $5)", expectedStmt: "INSERT INTO projections.sms_configs3_twilio (sms_id, instance_id, sid, token, sender_number) VALUES ($1, $2, $3, $4, $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"id", "id",
"instance-id", "instance-id",
@ -89,9 +91,10 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id", "id": "id",
"sid": "sid", "sid": "sid",
"senderNumber": "sender-number" "senderNumber": "sender-number",
"description": "description"
}`), }`),
), instance.SMSConfigTwilioChangedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigTwilioChangedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioChanged, reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioChanged,
want: wantReduce{ want: wantReduce{
@ -100,7 +103,17 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.sms_configs2_twilio SET (sid, sender_number) = ($1, $2) WHERE (sms_id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"description",
"id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3_twilio SET (sid, sender_number) = ($1, $2) WHERE (sms_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"sid", "sid",
"sender-number", "sender-number",
@ -108,11 +121,75 @@ func TestSMSProjection_reduces(t *testing.T) {
"instance-id", "instance-id",
}, },
}, },
},
},
},
},
{ {
expectedStmt: "UPDATE projections.sms_configs2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", name: "instance reduceSMSConfigTwilioChanged, only description",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigTwilioChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"description": "description"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
"description",
"id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigTwilioChanged, only sid",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigTwilioChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"sid": "sid"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3_twilio SET sid = $1 WHERE (sms_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"sid",
"id", "id",
"instance-id", "instance-id",
}, },
@ -137,7 +214,7 @@ func TestSMSProjection_reduces(t *testing.T) {
"crypted": "Y3J5cHRlZA==" "crypted": "Y3J5cHRlZA=="
} }
}`), }`),
), instance.SMSConfigTwilioTokenChangedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigTwilioTokenChangedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioTokenChanged, reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioTokenChanged,
want: wantReduce{ want: wantReduce{
@ -146,7 +223,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.sms_configs2_twilio SET token = $1 WHERE (sms_id = $2) AND (instance_id = $3)", expectedStmt: "UPDATE projections.sms_configs3_twilio SET token = $1 WHERE (sms_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
&crypto.CryptoValue{ &crypto.CryptoValue{
CryptoType: crypto.TypeEncryption, CryptoType: crypto.TypeEncryption,
@ -159,7 +236,7 @@ func TestSMSProjection_reduces(t *testing.T) {
}, },
}, },
{ {
expectedStmt: "UPDATE projections.sms_configs2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)", expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
anyArg{}, anyArg{},
uint64(15), uint64(15),
@ -171,6 +248,270 @@ func TestSMSProjection_reduces(t *testing.T) {
}, },
}, },
}, },
{
name: "instance reduceSMSHTTPAdded",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigHTTPAddedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"description": "description",
"endpoint": "endpoint"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPAddedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigHTTPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.sms_configs3 (id, aggregate_id, creation_date, change_date, resource_owner, instance_id, state, sequence, description) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
expectedArgs: []interface{}{
"id",
"agg-id",
anyArg{},
anyArg{},
"ro-id",
"instance-id",
domain.SMSConfigStateInactive,
uint64(15),
"description",
},
},
{
expectedStmt: "INSERT INTO projections.sms_configs3_http (sms_id, instance_id, endpoint) VALUES ($1, $2, $3)",
expectedArgs: []interface{}{
"id",
"instance-id",
"endpoint",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigHTTPChanged",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigHTTPChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"endpoint": "endpoint",
"description": "description"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigHTTPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"description",
"id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3_http SET endpoint = $1 WHERE (sms_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"endpoint",
"id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigHTTPChanged, only description",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigHTTPChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"description": "description"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigHTTPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence, description) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"description",
"id",
"instance-id",
},
},
},
},
},
}, {
name: "instance reduceSMSConfigHTTPChanged, only endpoint",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigHTTPChangedEventType,
instance.AggregateType,
[]byte(`{
"id": "id",
"endpoint": "endpoint"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigHTTPChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigHTTPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
anyArg{},
uint64(15),
"id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3_http SET endpoint = $1 WHERE (sms_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"endpoint",
"id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigTwilioActivated",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigTwilioActivatedEventType,
instance.AggregateType,
[]byte(`{
"id": "id"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioActivatedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioActivated,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedArgs: []interface{}{
domain.SMSConfigStateInactive,
anyArg{},
uint64(15),
"id",
domain.SMSConfigStateActive,
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
domain.SMSConfigStateActive,
anyArg{},
uint64(15),
"id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigTwilioDeactivated",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigTwilioDeactivatedEventType,
instance.AggregateType,
[]byte(`{
"id": "id"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioDeactivatedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioDeactivated,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
domain.SMSConfigStateInactive,
anyArg{},
uint64(15),
"id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceSMSConfigTwilioRemoved",
args: args{
event: getEvent(
testEvent(
instance.SMSConfigTwilioRemovedEventType,
instance.AggregateType,
[]byte(`{
"id": "id"
}`),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioRemovedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioRemoved,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.sms_configs3 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{
"id",
"instance-id",
},
},
},
},
},
},
{ {
name: "instance reduceSMSConfigActivated", name: "instance reduceSMSConfigActivated",
args: args{ args: args{
@ -181,7 +522,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id" "id": "id"
}`), }`),
), instance.SMSConfigActivatedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigActivatedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigActivated, reduce: (&smsConfigProjection{}).reduceSMSConfigActivated,
want: wantReduce{ want: wantReduce{
@ -190,7 +531,18 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.sms_configs2 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (NOT (id = $4)) AND (state = $5) AND (instance_id = $6)",
expectedArgs: []interface{}{
domain.SMSConfigStateInactive,
anyArg{},
uint64(15),
"id",
domain.SMSConfigStateActive,
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.SMSConfigStateActive, domain.SMSConfigStateActive,
anyArg{}, anyArg{},
@ -213,7 +565,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id" "id": "id"
}`), }`),
), instance.SMSConfigDeactivatedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigDeactivatedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigDeactivated, reduce: (&smsConfigProjection{}).reduceSMSConfigDeactivated,
want: wantReduce{ want: wantReduce{
@ -222,7 +574,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "UPDATE projections.sms_configs2 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)", expectedStmt: "UPDATE projections.sms_configs3 SET (state, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
domain.SMSConfigStateInactive, domain.SMSConfigStateInactive,
anyArg{}, anyArg{},
@ -245,7 +597,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{ []byte(`{
"id": "id" "id": "id"
}`), }`),
), instance.SMSConfigRemovedEventMapper), ), eventstore.GenericEventMapper[instance.SMSConfigRemovedEvent]),
}, },
reduce: (&smsConfigProjection{}).reduceSMSConfigRemoved, reduce: (&smsConfigProjection{}).reduceSMSConfigRemoved,
want: wantReduce{ want: wantReduce{
@ -254,7 +606,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.sms_configs2 WHERE (id = $1) AND (instance_id = $2)", expectedStmt: "DELETE FROM projections.sms_configs3 WHERE (id = $1) AND (instance_id = $2)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"id", "id",
"instance-id", "instance-id",
@ -281,7 +633,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{ executer: &testExecuter{
executions: []execution{ executions: []execution{
{ {
expectedStmt: "DELETE FROM projections.sms_configs2 WHERE (instance_id = $1)", expectedStmt: "DELETE FROM projections.sms_configs3 WHERE (instance_id = $1)",
expectedArgs: []interface{}{ expectedArgs: []interface{}{
"agg-id", "agg-id",
}, },

View File

@ -30,8 +30,10 @@ type SMSConfig struct {
ResourceOwner string ResourceOwner string
State domain.SMSConfigState State domain.SMSConfigState
Sequence uint64 Sequence uint64
Description string
TwilioConfig *Twilio TwilioConfig *Twilio
HTTPConfig *HTTP
} }
type Twilio struct { type Twilio struct {
@ -40,6 +42,10 @@ type Twilio struct {
SenderNumber string SenderNumber string
} }
type HTTP struct {
Endpoint string
}
type SMSConfigsSearchQueries struct { type SMSConfigsSearchQueries struct {
SearchRequest SearchRequest
Queries []SearchQuery Queries []SearchQuery
@ -58,60 +64,79 @@ var (
name: projection.SMSConfigProjectionTable, name: projection.SMSConfigProjectionTable,
instanceIDCol: projection.SMSColumnInstanceID, instanceIDCol: projection.SMSColumnInstanceID,
} }
SMSConfigColumnID = Column{ SMSColumnID = Column{
name: projection.SMSColumnID, name: projection.SMSColumnID,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnAggregateID = Column{ SMSColumnAggregateID = Column{
name: projection.SMSColumnAggregateID, name: projection.SMSColumnAggregateID,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnCreationDate = Column{ SMSColumnCreationDate = Column{
name: projection.SMSColumnCreationDate, name: projection.SMSColumnCreationDate,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnChangeDate = Column{ SMSColumnChangeDate = Column{
name: projection.SMSColumnChangeDate, name: projection.SMSColumnChangeDate,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnResourceOwner = Column{ SMSColumnResourceOwner = Column{
name: projection.SMSColumnResourceOwner, name: projection.SMSColumnResourceOwner,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnInstanceID = Column{ SMSColumnInstanceID = Column{
name: projection.SMSColumnInstanceID, name: projection.SMSColumnInstanceID,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnState = Column{ SMSColumnState = Column{
name: projection.SMSColumnState, name: projection.SMSColumnState,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSConfigColumnSequence = Column{ SMSColumnSequence = Column{
name: projection.SMSColumnSequence, name: projection.SMSColumnSequence,
table: smsConfigsTable, table: smsConfigsTable,
} }
SMSColumnDescription = Column{
name: projection.SMSColumnDescription,
table: smsConfigsTable,
}
) )
var ( var (
smsTwilioConfigsTable = table{ smsTwilioTable = table{
name: projection.SMSTwilioTable, name: projection.SMSTwilioTable,
instanceIDCol: projection.SMSTwilioColumnInstanceID, instanceIDCol: projection.SMSTwilioColumnInstanceID,
} }
SMSTwilioConfigColumnSMSID = Column{ SMSTwilioColumnSMSID = Column{
name: projection.SMSTwilioConfigColumnSMSID, name: projection.SMSTwilioColumnSMSID,
table: smsTwilioConfigsTable, table: smsTwilioTable,
} }
SMSTwilioConfigColumnSID = Column{ SMSTwilioColumnSID = Column{
name: projection.SMSTwilioConfigColumnSID, name: projection.SMSTwilioColumnSID,
table: smsTwilioConfigsTable, table: smsTwilioTable,
} }
SMSTwilioConfigColumnToken = Column{ SMSTwilioColumnToken = Column{
name: projection.SMSTwilioConfigColumnToken, name: projection.SMSTwilioColumnToken,
table: smsTwilioConfigsTable, table: smsTwilioTable,
} }
SMSTwilioConfigColumnSenderNumber = Column{ SMSTwilioColumnSenderNumber = Column{
name: projection.SMSTwilioConfigColumnSenderNumber, name: projection.SMSTwilioColumnSenderNumber,
table: smsTwilioConfigsTable, table: smsTwilioTable,
}
)
var (
smsHTTPTable = table{
name: projection.SMSHTTPTable,
instanceIDCol: projection.SMSHTTPColumnInstanceID,
}
SMSHTTPColumnSMSID = Column{
name: projection.SMSHTTPColumnSMSID,
table: smsHTTPTable,
}
SMSHTTPColumnEndpoint = Column{
name: projection.SMSHTTPColumnEndpoint,
table: smsHTTPTable,
} }
) )
@ -122,8 +147,8 @@ func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config
query, scan := prepareSMSConfigQuery(ctx, q.client) query, scan := prepareSMSConfigQuery(ctx, q.client)
stmt, args, err := query.Where( stmt, args, err := query.Where(
sq.Eq{ sq.Eq{
SMSConfigColumnID.identifier(): id, SMSColumnID.identifier(): id,
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), SMSColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}, },
).ToSql() ).ToSql()
if err != nil { if err != nil {
@ -137,17 +162,15 @@ func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config
return config, err return config, err
} }
func (q *Queries) SMSProviderConfig(ctx context.Context, queries ...SearchQuery) (config *SMSConfig, err error) { func (q *Queries) SMSProviderConfigActive(ctx context.Context, instanceID string) (config *SMSConfig, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
query, scan := prepareSMSConfigQuery(ctx, q.client) query, scan := prepareSMSConfigQuery(ctx, q.client)
for _, searchQuery := range queries {
query = searchQuery.toQuery(query)
}
stmt, args, err := query.Where( stmt, args, err := query.Where(
sq.Eq{ sq.Eq{
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), SMSColumnInstanceID.identifier(): instanceID,
SMSColumnState.identifier(): domain.SMSConfigStateActive,
}, },
).ToSql() ).ToSql()
if err != nil { if err != nil {
@ -168,7 +191,7 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
query, scan := prepareSMSConfigsQuery(ctx, q.client) query, scan := prepareSMSConfigsQuery(ctx, q.client)
stmt, args, err := queries.toQuery(query). stmt, args, err := queries.toQuery(query).
Where(sq.Eq{ Where(sq.Eq{
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(), SMSColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}).ToSql() }).ToSql()
if err != nil { if err != nil {
return nil, zerrors.ThrowInvalidArgument(err, "QUERY-sn9Jf", "Errors.Query.InvalidRequest") return nil, zerrors.ThrowInvalidArgument(err, "QUERY-sn9Jf", "Errors.Query.InvalidRequest")
@ -186,30 +209,36 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
} }
func NewSMSProviderStateQuery(state domain.SMSConfigState) (SearchQuery, error) { func NewSMSProviderStateQuery(state domain.SMSConfigState) (SearchQuery, error) {
return NewNumberQuery(SMSConfigColumnState, state, NumberEquals) return NewNumberQuery(SMSColumnState, state, NumberEquals)
} }
func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*SMSConfig, error)) { func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*SMSConfig, error)) {
return sq.Select( return sq.Select(
SMSConfigColumnID.identifier(), SMSColumnID.identifier(),
SMSConfigColumnAggregateID.identifier(), SMSColumnAggregateID.identifier(),
SMSConfigColumnCreationDate.identifier(), SMSColumnCreationDate.identifier(),
SMSConfigColumnChangeDate.identifier(), SMSColumnChangeDate.identifier(),
SMSConfigColumnResourceOwner.identifier(), SMSColumnResourceOwner.identifier(),
SMSConfigColumnState.identifier(), SMSColumnState.identifier(),
SMSConfigColumnSequence.identifier(), SMSColumnSequence.identifier(),
SMSColumnDescription.identifier(),
SMSTwilioConfigColumnSMSID.identifier(), SMSTwilioColumnSMSID.identifier(),
SMSTwilioConfigColumnSID.identifier(), SMSTwilioColumnSID.identifier(),
SMSTwilioConfigColumnToken.identifier(), SMSTwilioColumnToken.identifier(),
SMSTwilioConfigColumnSenderNumber.identifier(), SMSTwilioColumnSenderNumber.identifier(),
SMSHTTPColumnSMSID.identifier(),
SMSHTTPColumnEndpoint.identifier(),
).From(smsConfigsTable.identifier()). ).From(smsConfigsTable.identifier()).
LeftJoin(join(SMSTwilioConfigColumnSMSID, SMSConfigColumnID) + db.Timetravel(call.Took(ctx))). LeftJoin(join(SMSTwilioColumnSMSID, SMSColumnID)).
LeftJoin(join(SMSHTTPColumnSMSID, SMSColumnID) + db.Timetravel(call.Took(ctx))).
PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*SMSConfig, error) { PlaceholderFormat(sq.Dollar), func(row *sql.Row) (*SMSConfig, error) {
config := new(SMSConfig) config := new(SMSConfig)
var ( var (
twilioConfig = sqlTwilioConfig{} twilioConfig = sqlTwilioConfig{}
httpConfig = sqlHTTPConfig{}
) )
err := row.Scan( err := row.Scan(
@ -220,11 +249,15 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
&config.ResourceOwner, &config.ResourceOwner,
&config.State, &config.State,
&config.Sequence, &config.Sequence,
&config.Description,
&twilioConfig.smsID, &twilioConfig.smsID,
&twilioConfig.sid, &twilioConfig.sid,
&twilioConfig.token, &twilioConfig.token,
&twilioConfig.senderNumber, &twilioConfig.senderNumber,
&httpConfig.smsID,
&httpConfig.endpoint,
) )
if err != nil { if err != nil {
@ -235,6 +268,7 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
} }
twilioConfig.set(config) twilioConfig.set(config)
httpConfig.set(config)
return config, nil return config, nil
} }
@ -242,21 +276,27 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*SMSConfigs, error)) { func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Rows) (*SMSConfigs, error)) {
return sq.Select( return sq.Select(
SMSConfigColumnID.identifier(), SMSColumnID.identifier(),
SMSConfigColumnAggregateID.identifier(), SMSColumnAggregateID.identifier(),
SMSConfigColumnCreationDate.identifier(), SMSColumnCreationDate.identifier(),
SMSConfigColumnChangeDate.identifier(), SMSColumnChangeDate.identifier(),
SMSConfigColumnResourceOwner.identifier(), SMSColumnResourceOwner.identifier(),
SMSConfigColumnState.identifier(), SMSColumnState.identifier(),
SMSConfigColumnSequence.identifier(), SMSColumnSequence.identifier(),
SMSColumnDescription.identifier(),
SMSTwilioColumnSMSID.identifier(),
SMSTwilioColumnSID.identifier(),
SMSTwilioColumnToken.identifier(),
SMSTwilioColumnSenderNumber.identifier(),
SMSHTTPColumnSMSID.identifier(),
SMSHTTPColumnEndpoint.identifier(),
SMSTwilioConfigColumnSMSID.identifier(),
SMSTwilioConfigColumnSID.identifier(),
SMSTwilioConfigColumnToken.identifier(),
SMSTwilioConfigColumnSenderNumber.identifier(),
countColumn.identifier(), countColumn.identifier(),
).From(smsConfigsTable.identifier()). ).From(smsConfigsTable.identifier()).
LeftJoin(join(SMSTwilioConfigColumnSMSID, SMSConfigColumnID) + db.Timetravel(call.Took(ctx))). LeftJoin(join(SMSTwilioColumnSMSID, SMSColumnID)).
LeftJoin(join(SMSHTTPColumnSMSID, SMSColumnID) + db.Timetravel(call.Took(ctx))).
PlaceholderFormat(sq.Dollar), func(row *sql.Rows) (*SMSConfigs, error) { PlaceholderFormat(sq.Dollar), func(row *sql.Rows) (*SMSConfigs, error) {
configs := &SMSConfigs{Configs: []*SMSConfig{}} configs := &SMSConfigs{Configs: []*SMSConfig{}}
@ -264,6 +304,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
config := new(SMSConfig) config := new(SMSConfig)
var ( var (
twilioConfig = sqlTwilioConfig{} twilioConfig = sqlTwilioConfig{}
httpConfig = sqlHTTPConfig{}
) )
err := row.Scan( err := row.Scan(
@ -274,11 +315,16 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
&config.ResourceOwner, &config.ResourceOwner,
&config.State, &config.State,
&config.Sequence, &config.Sequence,
&config.Description,
&twilioConfig.smsID, &twilioConfig.smsID,
&twilioConfig.sid, &twilioConfig.sid,
&twilioConfig.token, &twilioConfig.token,
&twilioConfig.senderNumber, &twilioConfig.senderNumber,
&httpConfig.smsID,
&httpConfig.endpoint,
&configs.Count, &configs.Count,
) )
@ -287,6 +333,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
} }
twilioConfig.set(config) twilioConfig.set(config)
httpConfig.set(config)
configs.Configs = append(configs.Configs, config) configs.Configs = append(configs.Configs, config)
} }
@ -312,3 +359,17 @@ func (c sqlTwilioConfig) set(smsConfig *SMSConfig) {
SenderNumber: c.senderNumber.String, SenderNumber: c.senderNumber.String,
} }
} }
type sqlHTTPConfig struct {
smsID sql.NullString
endpoint sql.NullString
}
func (c sqlHTTPConfig) set(smsConfig *SMSConfig) {
if !c.smsID.Valid {
return
}
smsConfig.HTTPConfig = &HTTP{
Endpoint: c.endpoint.String,
}
}

View File

@ -14,38 +14,50 @@ import (
) )
var ( var (
expectedSMSConfigQuery = regexp.QuoteMeta(`SELECT projections.sms_configs2.id,` + expectedSMSConfigQuery = regexp.QuoteMeta(`SELECT projections.sms_configs3.id,` +
` projections.sms_configs2.aggregate_id,` + ` projections.sms_configs3.aggregate_id,` +
` projections.sms_configs2.creation_date,` + ` projections.sms_configs3.creation_date,` +
` projections.sms_configs2.change_date,` + ` projections.sms_configs3.change_date,` +
` projections.sms_configs2.resource_owner,` + ` projections.sms_configs3.resource_owner,` +
` projections.sms_configs2.state,` + ` projections.sms_configs3.state,` +
` projections.sms_configs2.sequence,` + ` projections.sms_configs3.sequence,` +
` projections.sms_configs3.description,` +
// twilio config // twilio config
` projections.sms_configs2_twilio.sms_id,` + ` projections.sms_configs3_twilio.sms_id,` +
` projections.sms_configs2_twilio.sid,` + ` projections.sms_configs3_twilio.sid,` +
` projections.sms_configs2_twilio.token,` + ` projections.sms_configs3_twilio.token,` +
` projections.sms_configs2_twilio.sender_number` + ` projections.sms_configs3_twilio.sender_number,` +
` FROM projections.sms_configs2` +
` LEFT JOIN projections.sms_configs2_twilio ON projections.sms_configs2.id = projections.sms_configs2_twilio.sms_id AND projections.sms_configs2.instance_id = projections.sms_configs2_twilio.instance_id` + // http config
` projections.sms_configs3_http.sms_id,` +
` projections.sms_configs3_http.endpoint` +
` FROM projections.sms_configs3` +
` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` +
` LEFT JOIN projections.sms_configs3_http ON projections.sms_configs3.id = projections.sms_configs3_http.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_http.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
expectedSMSConfigsQuery = regexp.QuoteMeta(`SELECT projections.sms_configs2.id,` + expectedSMSConfigsQuery = regexp.QuoteMeta(`SELECT projections.sms_configs3.id,` +
` projections.sms_configs2.aggregate_id,` + ` projections.sms_configs3.aggregate_id,` +
` projections.sms_configs2.creation_date,` + ` projections.sms_configs3.creation_date,` +
` projections.sms_configs2.change_date,` + ` projections.sms_configs3.change_date,` +
` projections.sms_configs2.resource_owner,` + ` projections.sms_configs3.resource_owner,` +
` projections.sms_configs2.state,` + ` projections.sms_configs3.state,` +
` projections.sms_configs2.sequence,` + ` projections.sms_configs3.sequence,` +
` projections.sms_configs3.description,` +
// twilio config // twilio config
` projections.sms_configs2_twilio.sms_id,` + ` projections.sms_configs3_twilio.sms_id,` +
` projections.sms_configs2_twilio.sid,` + ` projections.sms_configs3_twilio.sid,` +
` projections.sms_configs2_twilio.token,` + ` projections.sms_configs3_twilio.token,` +
` projections.sms_configs2_twilio.sender_number,` + ` projections.sms_configs3_twilio.sender_number,` +
// http config
` projections.sms_configs3_http.sms_id,` +
` projections.sms_configs3_http.endpoint,` +
` COUNT(*) OVER ()` + ` COUNT(*) OVER ()` +
` FROM projections.sms_configs2` + ` FROM projections.sms_configs3` +
` LEFT JOIN projections.sms_configs2_twilio ON projections.sms_configs2.id = projections.sms_configs2_twilio.sms_id AND projections.sms_configs2.instance_id = projections.sms_configs2_twilio.instance_id` + ` LEFT JOIN projections.sms_configs3_twilio ON projections.sms_configs3.id = projections.sms_configs3_twilio.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_twilio.instance_id` +
` LEFT JOIN projections.sms_configs3_http ON projections.sms_configs3.id = projections.sms_configs3_http.sms_id AND projections.sms_configs3.instance_id = projections.sms_configs3_http.instance_id` +
` AS OF SYSTEM TIME '-1 ms'`) ` AS OF SYSTEM TIME '-1 ms'`)
smsConfigCols = []string{ smsConfigCols = []string{
@ -56,16 +68,20 @@ var (
"resource_owner", "resource_owner",
"state", "state",
"sequence", "sequence",
"description",
// twilio config // twilio config
"sms_id", "sms_id",
"sid", "sid",
"token", "token",
"sender-number", "sender-number",
// http config
"sms_id",
"endpoint",
} }
smsConfigsCols = append(smsConfigCols, "count") smsConfigsCols = append(smsConfigCols, "count")
) )
func Test_SMSConfigssPrepare(t *testing.T) { func Test_SMSConfigsPrepare(t *testing.T) {
type want struct { type want struct {
sqlExpectations sqlExpectation sqlExpectations sqlExpectation
err checkErr err checkErr
@ -104,11 +120,15 @@ func Test_SMSConfigssPrepare(t *testing.T) {
"ro", "ro",
domain.SMSConfigStateInactive, domain.SMSConfigStateInactive,
uint64(20211109), uint64(20211109),
"description",
// twilio config // twilio config
"sms-id", "sms-id",
"sid", "sid",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
"sender-number", "sender-number",
// http config
nil,
nil,
}, },
}, },
), ),
@ -126,6 +146,7 @@ func Test_SMSConfigssPrepare(t *testing.T) {
ResourceOwner: "ro", ResourceOwner: "ro",
State: domain.SMSConfigStateInactive, State: domain.SMSConfigStateInactive,
Sequence: 20211109, Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{ TwilioConfig: &Twilio{
SID: "sid", SID: "sid",
Token: &crypto.CryptoValue{}, Token: &crypto.CryptoValue{},
@ -135,6 +156,56 @@ func Test_SMSConfigssPrepare(t *testing.T) {
}, },
}, },
}, },
{
name: "prepareSMSQuery http config",
prepare: prepareSMSConfigsQuery,
want: want{
sqlExpectations: mockQueries(
expectedSMSConfigsQuery,
smsConfigsCols,
[][]driver.Value{
{
"sms-id",
"agg-id",
testNow,
testNow,
"ro",
domain.SMSConfigStateInactive,
uint64(20211109),
"description",
// twilio config
nil,
nil,
nil,
nil,
// http config
"sms-id",
"endpoint",
},
},
),
},
object: &SMSConfigs{
SearchResponse: SearchResponse{
Count: 1,
},
Configs: []*SMSConfig{
{
ID: "sms-id",
AggregateID: "agg-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
Sequence: 20211109,
Description: "description",
HTTPConfig: &HTTP{
Endpoint: "endpoint",
},
},
},
},
},
{ {
name: "prepareSMSConfigsQuery multiple result", name: "prepareSMSConfigsQuery multiple result",
prepare: prepareSMSConfigsQuery, prepare: prepareSMSConfigsQuery,
@ -149,13 +220,17 @@ func Test_SMSConfigssPrepare(t *testing.T) {
testNow, testNow,
testNow, testNow,
"ro", "ro",
domain.SMSConfigStateInactive, domain.SMSConfigStateActive,
uint64(20211109), uint64(20211109),
"description",
// twilio config // twilio config
"sms-id", "sms-id",
"sid", "sid",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
"sender-number", "sender-number",
// http config
nil,
nil,
}, },
{ {
"sms-id2", "sms-id2",
@ -165,18 +240,40 @@ func Test_SMSConfigssPrepare(t *testing.T) {
"ro", "ro",
domain.SMSConfigStateInactive, domain.SMSConfigStateInactive,
uint64(20211109), uint64(20211109),
"description",
// twilio config // twilio config
"sms-id2", "sms-id2",
"sid2", "sid2",
&crypto.CryptoValue{}, &crypto.CryptoValue{},
"sender-number2", "sender-number2",
// http config
nil,
nil,
},
{
"sms-id3",
"agg-id",
testNow,
testNow,
"ro",
domain.SMSConfigStateInactive,
uint64(20211109),
"description",
// twilio config
nil,
nil,
nil,
nil,
// http config
"sms-id3",
"endpoint3",
}, },
}, },
), ),
}, },
object: &SMSConfigs{ object: &SMSConfigs{
SearchResponse: SearchResponse{ SearchResponse: SearchResponse{
Count: 2, Count: 3,
}, },
Configs: []*SMSConfig{ Configs: []*SMSConfig{
{ {
@ -185,8 +282,9 @@ func Test_SMSConfigssPrepare(t *testing.T) {
CreationDate: testNow, CreationDate: testNow,
ChangeDate: testNow, ChangeDate: testNow,
ResourceOwner: "ro", ResourceOwner: "ro",
State: domain.SMSConfigStateInactive, State: domain.SMSConfigStateActive,
Sequence: 20211109, Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{ TwilioConfig: &Twilio{
SID: "sid", SID: "sid",
Token: &crypto.CryptoValue{}, Token: &crypto.CryptoValue{},
@ -201,12 +299,26 @@ func Test_SMSConfigssPrepare(t *testing.T) {
ResourceOwner: "ro", ResourceOwner: "ro",
State: domain.SMSConfigStateInactive, State: domain.SMSConfigStateInactive,
Sequence: 20211109, Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{ TwilioConfig: &Twilio{
SID: "sid2", SID: "sid2",
Token: &crypto.CryptoValue{}, Token: &crypto.CryptoValue{},
SenderNumber: "sender-number2", SenderNumber: "sender-number2",
}, },
}, },
{
ID: "sms-id3",
AggregateID: "agg-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
Sequence: 20211109,
Description: "description",
HTTPConfig: &HTTP{
Endpoint: "endpoint3",
},
},
}, },
}, },
}, },
@ -265,7 +377,50 @@ func Test_SMSConfigPrepare(t *testing.T) {
object: (*SMSConfig)(nil), object: (*SMSConfig)(nil),
}, },
{ {
name: "prepareSMSConfigQuery found", name: "prepareSMSConfigQuery, twilio, found",
prepare: prepareSMSConfigQuery,
want: want{
sqlExpectations: mockQuery(
expectedSMSConfigQuery,
smsConfigCols,
[]driver.Value{
"sms-id",
"agg-id",
testNow,
testNow,
"ro",
domain.SMSConfigStateActive,
uint64(20211109),
"description",
// twilio config
"sms-id",
"sid",
&crypto.CryptoValue{},
"sender-number",
// http config
nil,
nil,
},
),
},
object: &SMSConfig{
ID: "sms-id",
AggregateID: "agg-id",
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.SMSConfigStateActive,
Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{
SID: "sid",
SenderNumber: "sender-number",
Token: &crypto.CryptoValue{},
},
},
},
{
name: "prepareSMSConfigQuery, http, found",
prepare: prepareSMSConfigQuery, prepare: prepareSMSConfigQuery,
want: want{ want: want{
sqlExpectations: mockQuery( sqlExpectations: mockQuery(
@ -279,11 +434,15 @@ func Test_SMSConfigPrepare(t *testing.T) {
"ro", "ro",
domain.SMSConfigStateInactive, domain.SMSConfigStateInactive,
uint64(20211109), uint64(20211109),
"description",
// twilio config // twilio config
nil,
nil,
nil,
nil,
// http config
"sms-id", "sms-id",
"sid", "endpoint",
&crypto.CryptoValue{},
"sender-number",
}, },
), ),
}, },
@ -295,10 +454,9 @@ func Test_SMSConfigPrepare(t *testing.T) {
ResourceOwner: "ro", ResourceOwner: "ro",
State: domain.SMSConfigStateInactive, State: domain.SMSConfigStateInactive,
Sequence: 20211109, Sequence: 20211109,
TwilioConfig: &Twilio{ Description: "description",
SID: "sid", HTTPConfig: &HTTP{
SenderNumber: "sender-number", Endpoint: "endpoint",
Token: &crypto.CryptoValue{},
}, },
}, },
}, },

View File

@ -18,12 +18,17 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, SMTPConfigDeactivatedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, SMTPConfigDeactivatedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, SMTPConfigPasswordChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, SMTPConfigPasswordChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, SMTPConfigRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, SMTPConfigRemovedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, SMSConfigTwilioAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, eventstore.GenericEventMapper[SMSConfigTwilioAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioChangedEventType, SMSConfigTwilioChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioTokenChangedEventType, SMSConfigTwilioTokenChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioTokenChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioTokenChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigActivatedEventType, SMSConfigActivatedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigHTTPAddedEventType, eventstore.GenericEventMapper[SMSConfigHTTPAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigDeactivatedEventType, SMSConfigDeactivatedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigHTTPChangedEventType, eventstore.GenericEventMapper[SMSConfigHTTPChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigRemovedEventType, SMSConfigRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioActivatedEventType, eventstore.GenericEventMapper[SMSConfigTwilioActivatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioDeactivatedEventType, eventstore.GenericEventMapper[SMSConfigTwilioDeactivatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioRemovedEventType, eventstore.GenericEventMapper[SMSConfigTwilioRemovedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigActivatedEventType, eventstore.GenericEventMapper[SMSConfigActivatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigDeactivatedEventType, eventstore.GenericEventMapper[SMSConfigDeactivatedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigRemovedEventType, eventstore.GenericEventMapper[SMSConfigRemovedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileAddedEventType, DebugNotificationProviderFileAddedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileAddedEventType, DebugNotificationProviderFileAddedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileChangedEventType, DebugNotificationProviderFileChangedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileChangedEventType, DebugNotificationProviderFileChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileRemovedEventType, DebugNotificationProviderFileRemovedEventMapper) eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileRemovedEventType, DebugNotificationProviderFileRemovedEventMapper)

View File

@ -11,18 +11,25 @@ import (
const ( const (
smsConfigPrefix = "sms.config" smsConfigPrefix = "sms.config"
smsConfigTwilioPrefix = "twilio." smsConfigTwilioPrefix = "twilio."
smsConfigHTTPPrefix = "http."
SMSConfigTwilioAddedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "added" SMSConfigTwilioAddedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "added"
SMSConfigTwilioChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "changed" SMSConfigTwilioChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "changed"
SMSConfigHTTPAddedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigHTTPPrefix + "added"
SMSConfigHTTPChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigHTTPPrefix + "changed"
SMSConfigTwilioTokenChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "token.changed" SMSConfigTwilioTokenChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "token.changed"
SMSConfigActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "activated" SMSConfigTwilioActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "activated"
SMSConfigDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "deactivated" SMSConfigTwilioDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "deactivated"
SMSConfigRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "removed" SMSConfigTwilioRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "removed"
SMSConfigActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + "activated"
SMSConfigDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + "deactivated"
SMSConfigRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + "removed"
) )
type SMSConfigTwilioAddedEvent struct { type SMSConfigTwilioAddedEvent struct {
eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
SID string `json:"sid,omitempty"` SID string `json:"sid,omitempty"`
Token *crypto.CryptoValue `json:"token,omitempty"` Token *crypto.CryptoValue `json:"token,omitempty"`
SenderNumber string `json:"senderNumber,omitempty"` SenderNumber string `json:"senderNumber,omitempty"`
@ -32,23 +39,29 @@ func NewSMSConfigTwilioAddedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id, id,
description string,
sid, sid,
senderNumber string, senderNumber string,
token *crypto.CryptoValue, token *crypto.CryptoValue,
) *SMSConfigTwilioAddedEvent { ) *SMSConfigTwilioAddedEvent {
return &SMSConfigTwilioAddedEvent{ return &SMSConfigTwilioAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigTwilioAddedEventType, SMSConfigTwilioAddedEventType,
), ),
ID: id, ID: id,
Description: description,
SID: sid, SID: sid,
Token: token, Token: token,
SenderNumber: senderNumber, SenderNumber: senderNumber,
} }
} }
func (e *SMSConfigTwilioAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioAddedEvent) Payload() interface{} { func (e *SMSConfigTwilioAddedEvent) Payload() interface{} {
return e return e
} }
@ -57,22 +70,11 @@ func (e *SMSConfigTwilioAddedEvent) UniqueConstraints() []*eventstore.UniqueCons
return nil return nil
} }
func SMSConfigTwilioAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smsConfigAdded := &SMSConfigTwilioAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smsConfigAdded)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-smwiR", "unable to unmarshal sms config twilio added")
}
return smsConfigAdded, nil
}
type SMSConfigTwilioChangedEvent struct { type SMSConfigTwilioChangedEvent struct {
eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Description *string `json:"description,omitempty"`
SID *string `json:"sid,omitempty"` SID *string `json:"sid,omitempty"`
SenderNumber *string `json:"senderNumber,omitempty"` SenderNumber *string `json:"senderNumber,omitempty"`
} }
@ -87,7 +89,7 @@ func NewSMSConfigTwilioChangedEvent(
return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-smn8e", "Errors.NoChangesFound") return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-smn8e", "Errors.NoChangesFound")
} }
changeEvent := &SMSConfigTwilioChangedEvent{ changeEvent := &SMSConfigTwilioChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigTwilioChangedEventType, SMSConfigTwilioChangedEventType,
@ -108,12 +110,22 @@ func ChangeSMSConfigTwilioSID(sid string) func(event *SMSConfigTwilioChangedEven
} }
} }
func ChangeSMSConfigTwilioDescription(description string) func(event *SMSConfigTwilioChangedEvent) {
return func(e *SMSConfigTwilioChangedEvent) {
e.Description = &description
}
}
func ChangeSMSConfigTwilioSenderNumber(senderNumber string) func(event *SMSConfigTwilioChangedEvent) { func ChangeSMSConfigTwilioSenderNumber(senderNumber string) func(event *SMSConfigTwilioChangedEvent) {
return func(e *SMSConfigTwilioChangedEvent) { return func(e *SMSConfigTwilioChangedEvent) {
e.SenderNumber = &senderNumber e.SenderNumber = &senderNumber
} }
} }
func (e *SMSConfigTwilioChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioChangedEvent) Payload() interface{} { func (e *SMSConfigTwilioChangedEvent) Payload() interface{} {
return e return e
} }
@ -122,20 +134,8 @@ func (e *SMSConfigTwilioChangedEvent) UniqueConstraints() []*eventstore.UniqueCo
return nil return nil
} }
func SMSConfigTwilioChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smsConfigChanged := &SMSConfigTwilioChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smsConfigChanged)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-smwiR", "unable to unmarshal sms config twilio added")
}
return smsConfigChanged, nil
}
type SMSConfigTwilioTokenChangedEvent struct { type SMSConfigTwilioTokenChangedEvent struct {
eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
Token *crypto.CryptoValue `json:"token,omitempty"` Token *crypto.CryptoValue `json:"token,omitempty"`
@ -148,7 +148,7 @@ func NewSMSConfigTokenChangedEvent(
token *crypto.CryptoValue, token *crypto.CryptoValue,
) *SMSConfigTwilioTokenChangedEvent { ) *SMSConfigTwilioTokenChangedEvent {
return &SMSConfigTwilioTokenChangedEvent{ return &SMSConfigTwilioTokenChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigTwilioTokenChangedEventType, SMSConfigTwilioTokenChangedEventType,
@ -158,6 +158,10 @@ func NewSMSConfigTokenChangedEvent(
} }
} }
func (e *SMSConfigTwilioTokenChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioTokenChangedEvent) Payload() interface{} { func (e *SMSConfigTwilioTokenChangedEvent) Payload() interface{} {
return e return e
} }
@ -166,30 +170,130 @@ func (e *SMSConfigTwilioTokenChangedEvent) UniqueConstraints() []*eventstore.Uni
return nil return nil
} }
func SMSConfigTwilioTokenChangedEventMapper(event eventstore.Event) (eventstore.Event, error) { type SMSConfigHTTPAddedEvent struct {
smtpConfigTokenChagned := &SMSConfigTwilioTokenChangedEvent{ *eventstore.BaseEvent `json:"-"`
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smtpConfigTokenChagned)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-fi9Wf", "unable to unmarshal sms config token changed")
}
return smtpConfigTokenChagned, nil ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
} }
type SMSConfigActivatedEvent struct { func NewSMSConfigHTTPAddedEvent(
eventstore.BaseEvent `json:"-"` ctx context.Context,
aggregate *eventstore.Aggregate,
id,
description,
endpoint string,
) *SMSConfigHTTPAddedEvent {
return &SMSConfigHTTPAddedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigHTTPAddedEventType,
),
ID: id,
Description: description,
Endpoint: endpoint,
}
}
func (e *SMSConfigHTTPAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigHTTPAddedEvent) Payload() interface{} {
return e
}
func (e *SMSConfigHTTPAddedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
type SMSConfigHTTPChangedEvent struct {
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
Description *string `json:"description,omitempty"`
Endpoint *string `json:"endpoint,omitempty"`
}
func NewSMSConfigHTTPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
changes []SMSConfigHTTPChanges,
) (*SMSConfigHTTPChangedEvent, error) {
if len(changes) == 0 {
return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-smn8e", "Errors.NoChangesFound")
}
changeEvent := &SMSConfigHTTPChangedEvent{
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigHTTPChangedEventType,
),
ID: id,
}
for _, change := range changes {
change(changeEvent)
}
return changeEvent, nil
}
type SMSConfigHTTPChanges func(event *SMSConfigHTTPChangedEvent)
func ChangeSMSConfigHTTPDescription(description string) func(event *SMSConfigHTTPChangedEvent) {
return func(e *SMSConfigHTTPChangedEvent) {
e.Description = &description
}
}
func ChangeSMSConfigHTTPEndpoint(endpoint string) func(event *SMSConfigHTTPChangedEvent) {
return func(e *SMSConfigHTTPChangedEvent) {
e.Endpoint = &endpoint
}
}
func (e *SMSConfigHTTPChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigHTTPChangedEvent) Payload() interface{} {
return e
}
func (e *SMSConfigHTTPChangedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
type SMSConfigTwilioActivatedEvent struct {
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
} }
func NewSMSConfigTwilioActivatedEvent( func (e *SMSConfigTwilioActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioActivatedEvent) Payload() interface{} {
return e
}
func (e *SMSConfigTwilioActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
}
type SMSConfigActivatedEvent struct {
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
func NewSMSConfigActivatedEvent(
ctx context.Context, ctx context.Context,
aggregate *eventstore.Aggregate, aggregate *eventstore.Aggregate,
id string, id string,
) *SMSConfigActivatedEvent { ) *SMSConfigActivatedEvent {
return &SMSConfigActivatedEvent{ return &SMSConfigActivatedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigActivatedEventType, SMSConfigActivatedEventType,
@ -198,6 +302,10 @@ func NewSMSConfigTwilioActivatedEvent(
} }
} }
func (e *SMSConfigActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigActivatedEvent) Payload() interface{} { func (e *SMSConfigActivatedEvent) Payload() interface{} {
return e return e
} }
@ -206,20 +314,25 @@ func (e *SMSConfigActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstr
return nil return nil
} }
func SMSConfigActivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { type SMSConfigTwilioDeactivatedEvent struct {
smsConfigActivated := &SMSConfigActivatedEvent{ *eventstore.BaseEvent `json:"-"`
BaseEvent: *eventstore.BaseEventFromRepo(event), ID string `json:"id,omitempty"`
} }
err := event.Unmarshal(smsConfigActivated)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-dn92f", "unable to unmarshal sms config twilio activated changed")
}
return smsConfigActivated, nil func (e *SMSConfigTwilioDeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioDeactivatedEvent) Payload() interface{} {
return e
}
func (e *SMSConfigTwilioDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
} }
type SMSConfigDeactivatedEvent struct { type SMSConfigDeactivatedEvent struct {
eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
} }
@ -229,7 +342,7 @@ func NewSMSConfigDeactivatedEvent(
id string, id string,
) *SMSConfigDeactivatedEvent { ) *SMSConfigDeactivatedEvent {
return &SMSConfigDeactivatedEvent{ return &SMSConfigDeactivatedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigDeactivatedEventType, SMSConfigDeactivatedEventType,
@ -238,6 +351,10 @@ func NewSMSConfigDeactivatedEvent(
} }
} }
func (e *SMSConfigDeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigDeactivatedEvent) Payload() interface{} { func (e *SMSConfigDeactivatedEvent) Payload() interface{} {
return e return e
} }
@ -246,20 +363,25 @@ func (e *SMSConfigDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueCons
return nil return nil
} }
func SMSConfigDeactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) { type SMSConfigTwilioRemovedEvent struct {
smsConfigDeactivated := &SMSConfigDeactivatedEvent{ *eventstore.BaseEvent `json:"-"`
BaseEvent: *eventstore.BaseEventFromRepo(event), ID string `json:"id,omitempty"`
} }
err := event.Unmarshal(smsConfigDeactivated)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-dn92f", "unable to unmarshal sms config twilio deactivated changed")
}
return smsConfigDeactivated, nil func (e *SMSConfigTwilioRemovedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioRemovedEvent) Payload() interface{} {
return e
}
func (e *SMSConfigTwilioRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil
} }
type SMSConfigRemovedEvent struct { type SMSConfigRemovedEvent struct {
eventstore.BaseEvent `json:"-"` *eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
} }
@ -269,7 +391,7 @@ func NewSMSConfigRemovedEvent(
id string, id string,
) *SMSConfigRemovedEvent { ) *SMSConfigRemovedEvent {
return &SMSConfigRemovedEvent{ return &SMSConfigRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush( BaseEvent: eventstore.NewBaseEventForPush(
ctx, ctx,
aggregate, aggregate,
SMSConfigRemovedEventType, SMSConfigRemovedEventType,
@ -278,6 +400,10 @@ func NewSMSConfigRemovedEvent(
} }
} }
func (e *SMSConfigRemovedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigRemovedEvent) Payload() interface{} { func (e *SMSConfigRemovedEvent) Payload() interface{} {
return e return e
} }
@ -285,15 +411,3 @@ func (e *SMSConfigRemovedEvent) Payload() interface{} {
func (e *SMSConfigRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint { func (e *SMSConfigRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
return nil return nil
} }
func SMSConfigRemovedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smsConfigRemoved := &SMSConfigRemovedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smsConfigRemoved)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-99iNF", "unable to unmarshal sms config removed")
}
return smsConfigRemoved, nil
}

View File

@ -690,6 +690,40 @@ service AdminService {
}; };
} }
rpc AddSMSProviderHTTP(AddSMSProviderHTTPRequest) returns (AddSMSProviderHTTPResponse) {
option (google.api.http) = {
post: "/sms/http";
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.write";
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMS Provider";
summary: "Add HTTP SMS Provider";
description: "Configure a new SMS provider of the type HTTP. A provider has to be activated to be able to send notifications."
};
}
rpc UpdateSMSProviderHTTP(UpdateSMSProviderHTTPRequest) returns (UpdateSMSProviderHTTPResponse) {
option (google.api.http) = {
put: "/sms/http/{id}";
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.write";
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "SMS Provider";
summary: "Update HTTP SMS Provider";
description: "Change the configuration of an SMS provider of the type HTTP. A provider has to be activated to be able to send notifications."
};
}
rpc ActivateSMSProvider(ActivateSMSProviderRequest) returns (ActivateSMSProviderResponse) { rpc ActivateSMSProvider(ActivateSMSProviderRequest) returns (ActivateSMSProviderResponse) {
option (google.api.http) = { option (google.api.http) = {
post: "/sms/{id}/_activate"; post: "/sms/{id}/_activate";
@ -4507,6 +4541,14 @@ message AddSMSProviderTwilioRequest {
max_length: 200; max_length: 200;
} }
]; ];
string description = 4 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"provider description\"";
min_length: 0;
max_length: 200;
}
];
} }
message AddSMSProviderTwilioResponse { message AddSMSProviderTwilioResponse {
@ -4534,6 +4576,14 @@ message UpdateSMSProviderTwilioRequest {
max_length: 200; max_length: 200;
} }
]; ];
string description = 4 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"provider description\"";
min_length: 0;
max_length: 200;
}
];
} }
message UpdateSMSProviderTwilioResponse { message UpdateSMSProviderTwilioResponse {
@ -4549,6 +4599,56 @@ message UpdateSMSProviderTwilioTokenResponse {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
} }
message AddSMSProviderHTTPRequest {
string endpoint = 1 [
(validate.rules).string = {min_len: 1, max_len: 2048},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"http://relay.example.com/provider\"";
min_length: 1;
max_length: 2048;
}
];
string description = 2 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"provider description\"";
min_length: 0;
max_length: 200;
}
];
}
message AddSMSProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
}
message UpdateSMSProviderHTTPRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string endpoint = 2 [
(validate.rules).string = {min_len: 1, max_len: 2048},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"http://relay.example.com/provider\"";
min_length: 1;
max_length: 2048;
}
];
string description = 3 [
(validate.rules).string = {min_len: 0, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"provider description\"";
min_length: 0;
max_length: 200;
}
];
}
message UpdateSMSProviderHTTPResponse {
zitadel.v1.ObjectDetails details = 1;
}
message ActivateSMSProviderRequest { message ActivateSMSProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
} }

View File

@ -7,7 +7,7 @@ import "protoc-gen-openapiv2/options/annotations.proto";
package zitadel.settings.v1; package zitadel.settings.v1;
option go_package ="github.com/zitadel/zitadel/pkg/grpc/settings"; option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings";
message SecretGenerator { message SecretGenerator {
SecretGeneratorType generator_type = 1; SecretGeneratorType generator_type = 1;
@ -99,9 +99,11 @@ message SMSProvider {
zitadel.v1.ObjectDetails details = 1; zitadel.v1.ObjectDetails details = 1;
string id = 2; string id = 2;
SMSProviderConfigState state = 3; SMSProviderConfigState state = 3;
string description = 6;
oneof config { oneof config {
TwilioConfig twilio = 4; TwilioConfig twilio = 4;
HTTPConfig http = 5;
} }
} }
@ -110,6 +112,10 @@ message TwilioConfig {
string sender_number = 2; string sender_number = 2;
} }
message HTTPConfig {
string endpoint = 1;
}
enum SMSProviderConfigState { enum SMSProviderConfigState {
SMS_PROVIDER_CONFIG_STATE_UNSPECIFIED = 0; SMS_PROVIDER_CONFIG_STATE_UNSPECIFIED = 0;
SMS_PROVIDER_CONFIG_ACTIVE = 1; SMS_PROVIDER_CONFIG_ACTIVE = 1;