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) {
id, result, err := s.command.AddSMSConfigTwilio(ctx, authz.GetInstance(ctx).InstanceID(), AddSMSConfigTwilioToConfig(req))
if err != nil {
smsConfig := addSMSConfigTwilioToConfig(ctx, req)
if err := s.command.AddSMSConfigTwilio(ctx, smsConfig); err != nil {
return nil, err
}
return &admin_pb.AddSMSProviderTwilioResponse{
Details: object.DomainToAddDetailsPb(result),
Id: id,
Details: object.DomainToAddDetailsPb(smsConfig.Details),
Id: smsConfig.ID,
}, nil
}
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))
if err != nil {
smsConfig := updateSMSConfigTwilioToConfig(ctx, req)
if err := s.command.ChangeSMSConfigTwilio(ctx, smsConfig); err != nil {
return nil, err
}
return &admin_pb.UpdateSMSProviderTwilioResponse{
Details: object.DomainToChangeDetailsPb(result),
Details: object.DomainToChangeDetailsPb(smsConfig.Details),
}, nil
}
@ -65,6 +65,27 @@ func (s *Server) UpdateSMSProviderTwilioToken(ctx context.Context, req *admin_pb
}, 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) {
result, err := s.command.ActivateSMSConfig(ctx, authz.GetInstance(ctx).InstanceID(), req.Id)
if err != nil {

View File

@ -1,9 +1,14 @@
package admin
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/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/query"
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings"
@ -30,10 +35,11 @@ func SMSConfigsToPb(configs []*query.SMSConfig) []*settings_pb.SMSProvider {
func SMSConfigToProviderPb(config *query.SMSConfig) *settings_pb.SMSProvider {
return &settings_pb.SMSProvider{
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
State: smsStateToPb(config.State),
Config: SMSConfigToPb(config),
Details: object.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.ResourceOwner),
Id: config.ID,
Description: config.Description,
State: smsStateToPb(config.State),
Config: SMSConfigToPb(config),
}
}
@ -41,9 +47,20 @@ func SMSConfigToPb(config *query.SMSConfig) settings_pb.SMSConfig {
if config.TwilioConfig != nil {
return TwilioConfigToPb(config.TwilioConfig)
}
if config.HTTPConfig != nil {
return HTTPConfigToPb(config.HTTPConfig)
}
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 {
return &settings_pb.SMSProvider_Twilio{
Twilio: &settings_pb.TwilioConfig{
@ -64,17 +81,39 @@ func smsStateToPb(state domain.SMSConfigState) settings_pb.SMSProviderConfigStat
}
}
func AddSMSConfigTwilioToConfig(req *admin_pb.AddSMSProviderTwilioRequest) *twilio.Config {
return &twilio.Config{
SID: req.Sid,
SenderNumber: req.SenderNumber,
Token: req.Token,
func addSMSConfigTwilioToConfig(ctx context.Context, req *admin_pb.AddSMSProviderTwilioRequest) *command.AddTwilioConfig {
return &command.AddTwilioConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
Description: req.Description,
SID: req.Sid,
SenderNumber: req.SenderNumber,
Token: req.Token,
}
}
func UpdateSMSConfigTwilioToConfig(req *admin_pb.UpdateSMSProviderTwilioRequest) *twilio.Config {
return &twilio.Config{
SID: req.Sid,
SenderNumber: req.SenderNumber,
func updateSMSConfigTwilioToConfig(ctx context.Context, req *admin_pb.UpdateSMSProviderTwilioRequest) *command.ChangeTwilioConfig {
return &command.ChangeTwilioConfig{
ResourceOwner: authz.GetInstance(ctx).InstanceID(),
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/domain"
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
func (c *Commands) AddSMSConfigTwilio(ctx context.Context, instanceID string, config *twilio.Config) (string, *domain.ObjectDetails, error) {
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
type AddTwilioConfig struct {
Details *domain.ObjectDetails
ResourceOwner string
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 {
return err
}
}
smsConfigWriteModel, err := c.getSMSConfig(ctx, config.ResourceOwner, config.ID)
if err != nil {
return "", nil, err
return err
}
var token *crypto.CryptoValue
if config.Token != "" {
token, err = crypto.Encrypt([]byte(config.Token), c.smsEncryption)
if err != nil {
return "", nil, err
return err
}
}
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTwilioAddedEvent(
ctx,
iamAgg,
id,
config.SID,
config.SenderNumber,
token))
err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigTwilioAddedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
config.ID,
config.Description,
config.SID,
config.SenderNumber,
token,
),
)
if err != nil {
return "", nil, err
return err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil {
return "", nil, err
}
return id, writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
}
func (c *Commands) ChangeSMSConfigTwilio(ctx context.Context, instanceID, id string, config *twilio.Config) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-e9jwf", "Errors.IDMissing")
type ChangeTwilioConfig struct {
Details *domain.ObjectDetails
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 {
return nil, err
return err
}
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.NewChangedEvent(
changedEvent, hasChanged, err := smsConfigWriteModel.NewTwilioChangedEvent(
ctx,
iamAgg,
id,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
config.ID,
config.Description,
config.SID,
config.SenderNumber)
if err != nil {
return nil, err
return err
}
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 {
return nil, err
return err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
config.Details = writeModelToObjectDetails(&smsConfigWriteModel.WriteModel)
return nil
}
func (c *Commands) ChangeSMSConfigTwilioToken(ctx context.Context, instanceID, id, token string) (*domain.ObjectDetails, error) {
smsConfigWriteModel, err := c.getSMSConfig(ctx, instanceID, id)
func (c *Commands) ChangeSMSConfigTwilioToken(ctx context.Context, resourceOwner, id, token string) (*domain.ObjectDetails, error) {
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 {
return nil, err
}
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)
if err != nil {
return nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTokenChangedEvent(
ctx,
iamAgg,
id,
newtoken))
if err != nil {
return nil, err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigTokenChangedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id,
newtoken,
),
)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
}
func (c *Commands) ActivateSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-dn93n", "Errors.IDMissing")
type AddSMSHTTP struct {
Details *domain.ObjectDetails
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 {
return nil, err
}
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 {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-sn9we", "Errors.SMSConfig.AlreadyActive")
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-B25GFeIvRi", "Errors.SMSConfig.AlreadyActive")
}
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigTwilioActivatedEvent(
ctx,
iamAgg,
id))
if err != nil {
return nil, err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
err = c.pushAppendAndReduce(ctx, smsConfigWriteModel,
instance.NewSMSConfigActivatedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id,
),
)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
}
func (c *Commands) DeactivateSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-frkwf", "Errors.IDMissing")
func (c *Commands) DeactivateSMSConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
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 {
return nil, err
}
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 {
return nil, zerrors.ThrowNotFound(nil, "COMMAND-dm9e3", "Errors.SMSConfig.AlreadyDeactivated")
return nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-OSZAEkYvk7", "Errors.SMSConfig.AlreadyDeactivated")
}
iamAgg := InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigDeactivatedEvent(
ctx,
iamAgg,
id))
if err != nil {
return nil, err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigDeactivatedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id,
),
)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
}
func (c *Commands) RemoveSMSConfig(ctx context.Context, instanceID, id string) (*domain.ObjectDetails, error) {
if id == "" {
return nil, zerrors.ThrowInvalidArgument(nil, "SMS-3j9fs", "Errors.IDMissing")
func (c *Commands) RemoveSMSConfig(ctx context.Context, resourceOwner, id string) (*domain.ObjectDetails, error) {
if resourceOwner == "" {
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 {
return nil, err
}
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)
pushedEvents, err := c.eventstore.Push(ctx, instance.NewSMSConfigRemovedEvent(
ctx,
iamAgg,
id))
if err != nil {
return nil, err
}
err = AppendAndReduce(smsConfigWriteModel, pushedEvents...)
err = c.pushAppendAndReduce(ctx,
smsConfigWriteModel,
instance.NewSMSConfigRemovedEvent(
ctx,
InstanceAggregateFromWriteModel(&smsConfigWriteModel.WriteModel),
id,
),
)
if err != nil {
return nil, err
}
return writeModelToObjectDetails(&smsConfigWriteModel.WriteModel), nil
}
func (c *Commands) getSMSConfig(ctx context.Context, instanceID, id string) (_ *IAMSMSConfigWriteModel, err error) {
writeModel := NewIAMSMSConfigWriteModel(instanceID, id)
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
if err != nil {
return nil, err
}
return writeModel, nil
}

View File

@ -12,9 +12,11 @@ import (
type IAMSMSConfigWriteModel struct {
eventstore.WriteModel
ID string
Twilio *TwilioConfig
State domain.SMSConfigState
ID string
Description string
Twilio *TwilioConfig
HTTP *HTTPConfig
State domain.SMSConfigState
}
type TwilioConfig struct {
@ -23,6 +25,10 @@ type TwilioConfig struct {
SenderNumber string
}
type HTTPConfig struct {
Endpoint string
}
func NewIAMSMSConfigWriteModel(instanceID, id string) *IAMSMSConfigWriteModel {
return &IAMSMSConfigWriteModel{
WriteModel: eventstore.WriteModel{
@ -46,11 +52,15 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
Token: e.Token,
SenderNumber: e.SenderNumber,
}
wm.Description = e.Description
wm.State = domain.SMSConfigStateInactive
case *instance.SMSConfigTwilioChangedEvent:
if wm.ID != e.ID {
continue
}
if e.Description != nil {
wm.Description = *e.Description
}
if e.SID != nil {
wm.Twilio.SID = *e.SID
}
@ -62,6 +72,42 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
continue
}
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:
if wm.ID != e.ID {
continue
@ -77,6 +123,7 @@ func (wm *IAMSMSConfigWriteModel) Reduce() error {
continue
}
wm.Twilio = nil
wm.HTTP = nil
wm.State = domain.SMSConfigStateRemoved
}
}
@ -92,21 +139,33 @@ func (wm *IAMSMSConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.SMSConfigTwilioAddedEventType,
instance.SMSConfigTwilioChangedEventType,
instance.SMSConfigTwilioTokenChangedEventType,
instance.SMSConfigHTTPAddedEventType,
instance.SMSConfigHTTPChangedEventType,
instance.SMSConfigTwilioActivatedEventType,
instance.SMSConfigTwilioDeactivatedEventType,
instance.SMSConfigTwilioRemovedEventType,
instance.SMSConfigActivatedEventType,
instance.SMSConfigDeactivatedEventType,
instance.SMSConfigRemovedEventType).
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)
var err error
if wm.Twilio.SID != sid {
changes = append(changes, instance.ChangeSMSConfigTwilioSID(sid))
if wm.Twilio == nil {
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 {
@ -118,3 +177,28 @@ func (wm *IAMSMSConfigWriteModel) NewChangedEvent(ctx context.Context, aggregate
}
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 {
t.Run(tt.name, func(t *testing.T) {

View File

@ -5,8 +5,8 @@ import (
"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/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/handlers"
"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
}
func (c *channels) SMS(ctx context.Context) (*senders.Chain, *twilio.Config, error) {
twilioCfg, err := c.q.GetTwilioConfig(ctx)
func (c *channels) SMS(ctx context.Context) (*senders.Chain, *sms.Config, error) {
smsCfg, err := c.q.GetActiveSMSConfig(ctx)
if err != nil {
return nil, nil, err
}
chain, err := senders.SMSChannels(
ctx,
twilioCfg,
smsCfg,
c.q.GetFileSystemProvider,
c.q.GetLogProvider,
c.counters.success.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) {

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)
}
// SMSProviderConfig mocks base method.
func (m *MockQueries) SMSProviderConfig(arg0 context.Context, arg1 ...query.SearchQuery) (*query.SMSConfig, error) {
// SMSProviderConfigActive mocks base method.
func (m *MockQueries) SMSProviderConfigActive(arg0 context.Context, arg1 string) (*query.SMSConfig, error) {
m.ctrl.T.Helper()
varargs := []any{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SMSProviderConfig", varargs...)
ret := m.ctrl.Call(m, "SMSProviderConfigActive", arg0, arg1)
ret0, _ := ret[0].(*query.SMSConfig)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// SMSProviderConfig indicates an expected call of SMSProviderConfig.
func (mr *MockQueriesMockRecorder) SMSProviderConfig(arg0 any, arg1 ...any) *gomock.Call {
// SMSProviderConfigActive indicates an expected call of SMSProviderConfigActive.
func (mr *MockQueriesMockRecorder) SMSProviderConfigActive(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfig", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfig), varargs...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SMSProviderConfigActive", reflect.TypeOf((*MockQueries)(nil).SMSProviderConfigActive), arg0, arg1)
}
// 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)
SearchMilestones(ctx context.Context, instanceIDs []string, queries *query.MilestonesSearchQueries) (*query.Milestones, 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)
GetDefaultLanguage(ctx context.Context) language.Tag
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)
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)
if err != nil {
@ -373,7 +373,7 @@ func (u *userNotifier) reduceOTPSMS(
if err != nil {
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)
if err != nil {
return nil, err
@ -709,7 +709,7 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
if err != nil {
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)
if err != nil {
return err

View File

@ -16,8 +16,8 @@ import (
"github.com/zitadel/zitadel/internal/eventstore/repository"
es_repo_mock "github.com/zitadel/zitadel/internal/eventstore/repository/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/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/handlers/mock"
"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
}
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
}

View File

@ -3,36 +3,60 @@ package senders
import (
"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/fs"
"github.com/zitadel/zitadel/internal/notification/channels/instrumenting"
"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/webhook"
)
const twilioSpanName = "twilio.NotificationChannel"
func SMSChannels(
ctx context.Context,
twilioConfig *twilio.Config,
smsConfig *sms.Config,
getFileSystemProvider func(ctx context.Context) (*fs.Config, error),
getLogProvider func(ctx context.Context) (*log.Config, error),
successMetricName,
failureMetricName string,
) (chain *Chain, err error) {
channels := make([]channels.NotificationChannel, 0, 3)
if twilioConfig != nil {
if smsConfig.TwilioConfig != nil {
channels = append(
channels,
instrumenting.Wrap(
ctx,
twilio.InitChannel(*twilioConfig),
twilio.InitChannel(*smsConfig.TwilioConfig),
twilioSpanName,
successMetricName,
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)...)
return ChainChannels(channels...), nil
}

View File

@ -15,23 +15,23 @@ const (
)
type TemplateData struct {
Title string
PreHeader string
Subject string
Greeting string
Text string
URL string
ButtonText string
PrimaryColor string
BackgroundColor string
FontColor string
LogoURL string
FontURL string
FontFaceFamily string
FontFamily string
Title string `json:"title,omitempty"`
PreHeader string `json:"preHeader,omitempty"`
Subject string `json:"subject,omitempty"`
Greeting string `json:"greeting,omitempty"`
Text string `json:"text,omitempty"`
URL string `json:"url,omitempty"`
ButtonText string `json:"buttonText,omitempty"`
PrimaryColor string `json:"primaryColor,omitempty"`
BackgroundColor string `json:"backgroundColor,omitempty"`
FontColor string `json:"fontColor,omitempty"`
LogoURL string `json:"logoUrl,omitempty"`
FontURL string `json:"fontUrl,omitempty"`
FontFaceFamily string `json:"fontFaceFamily,omitempty"`
FontFamily string `json:"fontFamily,omitempty"`
IncludeFooter bool
FooterText string
IncludeFooter bool `json:"includeFooter,omitempty"`
FooterText string `json:"footerText,omitempty"`
}
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/eventstore"
"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/twilio"
"github.com/zitadel/zitadel/internal/notification/channels/webhook"
"github.com/zitadel/zitadel/internal/notification/senders"
"github.com/zitadel/zitadel/internal/notification/templates"
@ -24,7 +24,7 @@ type Notify func(
type ChannelChains interface {
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)
}
@ -79,7 +79,7 @@ func sanitizeArgsForHTML(args map[string]any) {
}
}
func SendSMSTwilio(
func SendSMS(
ctx context.Context,
channels ChannelChains,
translator *i18n.Translator,
@ -99,7 +99,8 @@ func SendSMSTwilio(
ctx,
channels,
user,
data.Text,
data,
args,
allowUnverifiedNotificationChannel,
triggeringEvent,
)

View File

@ -2,40 +2,78 @@ package types
import (
"context"
"strings"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore"
"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/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(
ctx context.Context,
channels ChannelChains,
user *query.NotifyUser,
content string,
data templates.TemplateData,
args map[string]interface{},
lastPhone bool,
triggeringEvent eventstore.Event,
) error {
number := ""
smsChannels, twilioConfig, err := channels.SMS(ctx)
smsChannels, config, err := channels.SMS(ctx)
logging.OnError(err).Error("could not create sms channel")
if smsChannels == nil || smsChannels.Len() == 0 {
return zerrors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
}
if err == nil {
number = twilioConfig.SenderNumber
}
message := &messages.SMS{
SenderPhoneNumber: number,
RecipientPhoneNumber: user.VerifiedPhone,
Content: content,
TriggeringEvent: triggeringEvent,
}
recipient := user.VerifiedPhone
if lastPhone {
message.RecipientPhoneNumber = user.LastPhone
recipient = user.LastPhone
}
return smsChannels.HandleMessage(message)
if config.TwilioConfig != nil {
number := ""
if err == nil {
number = config.TwilioConfig.SenderNumber
}
message := &messages.SMS{
SenderPhoneNumber: number,
RecipientPhoneNumber: recipient,
Content: data.Text,
TriggeringEvent: triggeringEvent,
}
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"
"github.com/zitadel/zitadel/internal/eventstore/handler/v2"
"github.com/zitadel/zitadel/internal/repository/instance"
"github.com/zitadel/zitadel/internal/zerrors"
)
const (
SMSConfigProjectionTable = "projections.sms_configs2"
SMSConfigProjectionTable = "projections.sms_configs3"
SMSTwilioTable = SMSConfigProjectionTable + "_" + smsTwilioTableSuffix
SMSHTTPTable = SMSConfigProjectionTable + "_" + smsHTTPTableSuffix
SMSColumnID = "id"
SMSColumnAggregateID = "aggregate_id"
@ -23,13 +23,19 @@ const (
SMSColumnState = "state"
SMSColumnResourceOwner = "resource_owner"
SMSColumnInstanceID = "instance_id"
SMSColumnDescription = "description"
smsTwilioTableSuffix = "twilio"
SMSTwilioConfigColumnSMSID = "sms_id"
SMSTwilioColumnInstanceID = "instance_id"
SMSTwilioConfigColumnSID = "sid"
SMSTwilioConfigColumnSenderNumber = "sender_number"
SMSTwilioConfigColumnToken = "token"
smsTwilioTableSuffix = "twilio"
SMSTwilioColumnSMSID = "sms_id"
SMSTwilioColumnInstanceID = "instance_id"
SMSTwilioColumnSID = "sid"
SMSTwilioColumnSenderNumber = "sender_number"
SMSTwilioColumnToken = "token"
smsHTTPTableSuffix = "http"
SMSHTTPColumnSMSID = "sms_id"
SMSHTTPColumnInstanceID = "instance_id"
SMSHTTPColumnEndpoint = "endpoint"
)
type smsConfigProjection struct{}
@ -53,20 +59,30 @@ func (*smsConfigProjection) Init() *old_handler.Check {
handler.NewColumn(SMSColumnState, handler.ColumnTypeEnum),
handler.NewColumn(SMSColumnResourceOwner, handler.ColumnTypeText),
handler.NewColumn(SMSColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSColumnDescription, handler.ColumnTypeText),
},
handler.NewPrimaryKey(SMSColumnInstanceID, SMSColumnID),
),
handler.NewSuffixedTable([]*handler.InitColumn{
handler.NewColumn(SMSTwilioConfigColumnSMSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioColumnSMSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioColumnInstanceID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnSenderNumber, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioConfigColumnToken, handler.ColumnTypeJSONB),
handler.NewColumn(SMSTwilioColumnSID, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioColumnSenderNumber, handler.ColumnTypeText),
handler.NewColumn(SMSTwilioColumnToken, handler.ColumnTypeJSONB),
},
handler.NewPrimaryKey(SMSTwilioColumnInstanceID, SMSTwilioConfigColumnSMSID),
handler.NewPrimaryKey(SMSTwilioColumnInstanceID, SMSTwilioColumnSMSID),
smsTwilioTableSuffix,
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,
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,
Reduce: p.reduceSMSConfigActivated,
@ -109,9 +145,9 @@ func (p *smsConfigProjection) Reducers() []handler.AggregateReducer {
}
func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigTwilioAddedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-s8efs", "reduce.wrong.event.type %s", instance.SMSConfigTwilioAddedEventType)
e, err := assertEvent[*instance.SMSConfigTwilioAddedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
@ -126,15 +162,16 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event)
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(SMSTwilioConfigColumnSMSID, e.ID),
handler.NewCol(SMSTwilioColumnSMSID, e.ID),
handler.NewCol(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
handler.NewCol(SMSTwilioConfigColumnSID, e.SID),
handler.NewCol(SMSTwilioConfigColumnToken, e.Token),
handler.NewCol(SMSTwilioConfigColumnSenderNumber, e.SenderNumber),
handler.NewCol(SMSTwilioColumnSID, e.SID),
handler.NewCol(SMSTwilioColumnToken, e.Token),
handler.NewCol(SMSTwilioColumnSenderNumber, e.SenderNumber),
},
handler.WithTableSuffix(smsTwilioTableSuffix),
),
@ -142,57 +179,64 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioAdded(event eventstore.Event)
}
func (p *smsConfigProjection) reduceSMSConfigTwilioChanged(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigTwilioChangedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fi99F", "reduce.wrong.event.type %s", instance.SMSConfigTwilioChangedEventType)
}
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))
e, err := assertEvent[*instance.SMSConfigTwilioChangedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
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(SMSTwilioConfigColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(smsTwilioTableSuffix),
),
handler.AddUpdateStatement(
[]handler.Column{
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
[]handler.Condition{
handler.NewCond(SMSColumnID, e.ID),
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) {
e, ok := event.(*instance.SMSConfigTwilioTokenChangedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fi99F", "reduce.wrong.event.type %s", instance.SMSConfigTwilioTokenChangedEventType)
}
columns := make([]handler.Column, 0)
if e.Token != nil {
columns = append(columns, handler.NewCol(SMSTwilioConfigColumnToken, e.Token))
e, err := assertEvent[*instance.SMSConfigTwilioTokenChangedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
e,
handler.AddUpdateStatement(
columns,
[]handler.Column{
handler.NewCol(SMSTwilioColumnToken, e.Token),
},
[]handler.Condition{
handler.NewCond(SMSTwilioConfigColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnSMSID, e.ID),
handler.NewCond(SMSTwilioColumnInstanceID, e.Aggregate().InstanceID),
},
handler.WithTableSuffix(smsTwilioTableSuffix),
@ -210,15 +254,122 @@ func (p *smsConfigProjection) reduceSMSConfigTwilioTokenChanged(event eventstore
), nil
}
func (p *smsConfigProjection) reduceSMSConfigActivated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigActivatedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-fj9Ef", "reduce.wrong.event.type %s", instance.SMSConfigActivatedEventType)
func (p *smsConfigProjection) reduceSMSConfigHTTPAdded(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigHTTPAddedEvent](event)
if err != nil {
return nil, err
}
return handler.NewMultiStatement(
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.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
}
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.SMSConfigStateActive),
handler.NewCol(SMSColumnState, domain.SMSConfigStateInactive),
handler.NewCol(SMSColumnChangeDate, e.CreationDate()),
handler.NewCol(SMSColumnSequence, e.Sequence()),
},
@ -229,11 +380,61 @@ func (p *smsConfigProjection) reduceSMSConfigActivated(event eventstore.Event) (
), nil
}
func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigDeactivatedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-dj9Js", "reduce.wrong.event.type %s", instance.SMSConfigDeactivatedEventType)
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
}
func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event) (*handler.Statement, error) {
e, err := assertEvent[*instance.SMSConfigDeactivatedEvent](event)
if err != nil {
return nil, err
}
return handler.NewUpdateStatement(
e,
[]handler.Column{
@ -249,10 +450,11 @@ func (p *smsConfigProjection) reduceSMSConfigDeactivated(event eventstore.Event)
}
func (p *smsConfigProjection) reduceSMSConfigRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.SMSConfigRemovedEvent)
if !ok {
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-s9JJf", "reduce.wrong.event.type %s", instance.SMSConfigRemovedEventType)
e, err := assertEvent[*instance.SMSConfigRemovedEvent](event)
if err != nil {
return nil, err
}
return handler.NewDeleteStatement(
e,
[]handler.Condition{

View File

@ -37,9 +37,10 @@ func TestSMSProjection_reduces(t *testing.T) {
"keyId": "key-id",
"crypted": "Y3J5cHRlZA=="
},
"senderNumber": "sender-number"
"senderNumber": "sender-number",
"description": "description"
}`),
), instance.SMSConfigTwilioAddedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioAddedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioAdded,
want: wantReduce{
@ -48,7 +49,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
"id",
"agg-id",
@ -58,10 +59,11 @@ func TestSMSProjection_reduces(t *testing.T) {
"instance-id",
domain.SMSConfigStateInactive,
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{}{
"id",
"instance-id",
@ -89,9 +91,10 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{
"id": "id",
"sid": "sid",
"senderNumber": "sender-number"
"senderNumber": "sender-number",
"description": "description"
}`),
), instance.SMSConfigTwilioChangedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioChanged,
want: wantReduce{
@ -100,7 +103,17 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
"sid",
"sender-number",
@ -108,11 +121,75 @@ func TestSMSProjection_reduces(t *testing.T) {
"instance-id",
},
},
},
},
},
},
{
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_configs2 SET (change_date, sequence) = ($1, $2) WHERE (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",
},
},
},
},
},
},
{
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",
"instance-id",
},
@ -137,7 +214,7 @@ func TestSMSProjection_reduces(t *testing.T) {
"crypted": "Y3J5cHRlZA=="
}
}`),
), instance.SMSConfigTwilioTokenChangedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigTwilioTokenChangedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigTwilioTokenChanged,
want: wantReduce{
@ -146,7 +223,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
&crypto.CryptoValue{
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{}{
anyArg{},
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",
args: args{
@ -181,7 +522,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{
"id": "id"
}`),
), instance.SMSConfigActivatedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigActivatedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigActivated,
want: wantReduce{
@ -190,7 +531,18 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
domain.SMSConfigStateActive,
anyArg{},
@ -213,7 +565,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{
"id": "id"
}`),
), instance.SMSConfigDeactivatedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigDeactivatedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigDeactivated,
want: wantReduce{
@ -222,7 +574,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
domain.SMSConfigStateInactive,
anyArg{},
@ -245,7 +597,7 @@ func TestSMSProjection_reduces(t *testing.T) {
[]byte(`{
"id": "id"
}`),
), instance.SMSConfigRemovedEventMapper),
), eventstore.GenericEventMapper[instance.SMSConfigRemovedEvent]),
},
reduce: (&smsConfigProjection{}).reduceSMSConfigRemoved,
want: wantReduce{
@ -254,7 +606,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
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{}{
"id",
"instance-id",
@ -281,7 +633,7 @@ func TestSMSProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "DELETE FROM projections.sms_configs2 WHERE (instance_id = $1)",
expectedStmt: "DELETE FROM projections.sms_configs3 WHERE (instance_id = $1)",
expectedArgs: []interface{}{
"agg-id",
},

View File

@ -30,8 +30,10 @@ type SMSConfig struct {
ResourceOwner string
State domain.SMSConfigState
Sequence uint64
Description string
TwilioConfig *Twilio
HTTPConfig *HTTP
}
type Twilio struct {
@ -40,6 +42,10 @@ type Twilio struct {
SenderNumber string
}
type HTTP struct {
Endpoint string
}
type SMSConfigsSearchQueries struct {
SearchRequest
Queries []SearchQuery
@ -58,60 +64,79 @@ var (
name: projection.SMSConfigProjectionTable,
instanceIDCol: projection.SMSColumnInstanceID,
}
SMSConfigColumnID = Column{
SMSColumnID = Column{
name: projection.SMSColumnID,
table: smsConfigsTable,
}
SMSConfigColumnAggregateID = Column{
SMSColumnAggregateID = Column{
name: projection.SMSColumnAggregateID,
table: smsConfigsTable,
}
SMSConfigColumnCreationDate = Column{
SMSColumnCreationDate = Column{
name: projection.SMSColumnCreationDate,
table: smsConfigsTable,
}
SMSConfigColumnChangeDate = Column{
SMSColumnChangeDate = Column{
name: projection.SMSColumnChangeDate,
table: smsConfigsTable,
}
SMSConfigColumnResourceOwner = Column{
SMSColumnResourceOwner = Column{
name: projection.SMSColumnResourceOwner,
table: smsConfigsTable,
}
SMSConfigColumnInstanceID = Column{
SMSColumnInstanceID = Column{
name: projection.SMSColumnInstanceID,
table: smsConfigsTable,
}
SMSConfigColumnState = Column{
SMSColumnState = Column{
name: projection.SMSColumnState,
table: smsConfigsTable,
}
SMSConfigColumnSequence = Column{
SMSColumnSequence = Column{
name: projection.SMSColumnSequence,
table: smsConfigsTable,
}
SMSColumnDescription = Column{
name: projection.SMSColumnDescription,
table: smsConfigsTable,
}
)
var (
smsTwilioConfigsTable = table{
smsTwilioTable = table{
name: projection.SMSTwilioTable,
instanceIDCol: projection.SMSTwilioColumnInstanceID,
}
SMSTwilioConfigColumnSMSID = Column{
name: projection.SMSTwilioConfigColumnSMSID,
table: smsTwilioConfigsTable,
SMSTwilioColumnSMSID = Column{
name: projection.SMSTwilioColumnSMSID,
table: smsTwilioTable,
}
SMSTwilioConfigColumnSID = Column{
name: projection.SMSTwilioConfigColumnSID,
table: smsTwilioConfigsTable,
SMSTwilioColumnSID = Column{
name: projection.SMSTwilioColumnSID,
table: smsTwilioTable,
}
SMSTwilioConfigColumnToken = Column{
name: projection.SMSTwilioConfigColumnToken,
table: smsTwilioConfigsTable,
SMSTwilioColumnToken = Column{
name: projection.SMSTwilioColumnToken,
table: smsTwilioTable,
}
SMSTwilioConfigColumnSenderNumber = Column{
name: projection.SMSTwilioConfigColumnSenderNumber,
table: smsTwilioConfigsTable,
SMSTwilioColumnSenderNumber = Column{
name: projection.SMSTwilioColumnSenderNumber,
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)
stmt, args, err := query.Where(
sq.Eq{
SMSConfigColumnID.identifier(): id,
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
SMSColumnID.identifier(): id,
SMSColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
},
).ToSql()
if err != nil {
@ -137,17 +162,15 @@ func (q *Queries) SMSProviderConfigByID(ctx context.Context, id string) (config
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)
defer func() { span.EndWithError(err) }()
query, scan := prepareSMSConfigQuery(ctx, q.client)
for _, searchQuery := range queries {
query = searchQuery.toQuery(query)
}
stmt, args, err := query.Where(
sq.Eq{
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
SMSColumnInstanceID.identifier(): instanceID,
SMSColumnState.identifier(): domain.SMSConfigStateActive,
},
).ToSql()
if err != nil {
@ -168,7 +191,7 @@ func (q *Queries) SearchSMSConfigs(ctx context.Context, queries *SMSConfigsSearc
query, scan := prepareSMSConfigsQuery(ctx, q.client)
stmt, args, err := queries.toQuery(query).
Where(sq.Eq{
SMSConfigColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
SMSColumnInstanceID.identifier(): authz.GetInstance(ctx).InstanceID(),
}).ToSql()
if err != nil {
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) {
return NewNumberQuery(SMSConfigColumnState, state, NumberEquals)
return NewNumberQuery(SMSColumnState, state, NumberEquals)
}
func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*SMSConfig, error)) {
return sq.Select(
SMSConfigColumnID.identifier(),
SMSConfigColumnAggregateID.identifier(),
SMSConfigColumnCreationDate.identifier(),
SMSConfigColumnChangeDate.identifier(),
SMSConfigColumnResourceOwner.identifier(),
SMSConfigColumnState.identifier(),
SMSConfigColumnSequence.identifier(),
SMSColumnID.identifier(),
SMSColumnAggregateID.identifier(),
SMSColumnCreationDate.identifier(),
SMSColumnChangeDate.identifier(),
SMSColumnResourceOwner.identifier(),
SMSColumnState.identifier(),
SMSColumnSequence.identifier(),
SMSColumnDescription.identifier(),
SMSTwilioConfigColumnSMSID.identifier(),
SMSTwilioConfigColumnSID.identifier(),
SMSTwilioConfigColumnToken.identifier(),
SMSTwilioConfigColumnSenderNumber.identifier(),
SMSTwilioColumnSMSID.identifier(),
SMSTwilioColumnSID.identifier(),
SMSTwilioColumnToken.identifier(),
SMSTwilioColumnSenderNumber.identifier(),
SMSHTTPColumnSMSID.identifier(),
SMSHTTPColumnEndpoint.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) {
config := new(SMSConfig)
var (
twilioConfig = sqlTwilioConfig{}
httpConfig = sqlHTTPConfig{}
)
err := row.Scan(
@ -220,11 +249,15 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
&config.ResourceOwner,
&config.State,
&config.Sequence,
&config.Description,
&twilioConfig.smsID,
&twilioConfig.sid,
&twilioConfig.token,
&twilioConfig.senderNumber,
&httpConfig.smsID,
&httpConfig.endpoint,
)
if err != nil {
@ -235,6 +268,7 @@ func prepareSMSConfigQuery(ctx context.Context, db prepareDatabase) (sq.SelectBu
}
twilioConfig.set(config)
httpConfig.set(config)
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)) {
return sq.Select(
SMSConfigColumnID.identifier(),
SMSConfigColumnAggregateID.identifier(),
SMSConfigColumnCreationDate.identifier(),
SMSConfigColumnChangeDate.identifier(),
SMSConfigColumnResourceOwner.identifier(),
SMSConfigColumnState.identifier(),
SMSConfigColumnSequence.identifier(),
SMSColumnID.identifier(),
SMSColumnAggregateID.identifier(),
SMSColumnCreationDate.identifier(),
SMSColumnChangeDate.identifier(),
SMSColumnResourceOwner.identifier(),
SMSColumnState.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(),
).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) {
configs := &SMSConfigs{Configs: []*SMSConfig{}}
@ -264,6 +304,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
config := new(SMSConfig)
var (
twilioConfig = sqlTwilioConfig{}
httpConfig = sqlHTTPConfig{}
)
err := row.Scan(
@ -274,11 +315,16 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
&config.ResourceOwner,
&config.State,
&config.Sequence,
&config.Description,
&twilioConfig.smsID,
&twilioConfig.sid,
&twilioConfig.token,
&twilioConfig.senderNumber,
&httpConfig.smsID,
&httpConfig.endpoint,
&configs.Count,
)
@ -287,6 +333,7 @@ func prepareSMSConfigsQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
}
twilioConfig.set(config)
httpConfig.set(config)
configs.Configs = append(configs.Configs, config)
}
@ -312,3 +359,17 @@ func (c sqlTwilioConfig) set(smsConfig *SMSConfig) {
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 (
expectedSMSConfigQuery = regexp.QuoteMeta(`SELECT projections.sms_configs2.id,` +
` projections.sms_configs2.aggregate_id,` +
` projections.sms_configs2.creation_date,` +
` projections.sms_configs2.change_date,` +
` projections.sms_configs2.resource_owner,` +
` projections.sms_configs2.state,` +
` projections.sms_configs2.sequence,` +
expectedSMSConfigQuery = regexp.QuoteMeta(`SELECT projections.sms_configs3.id,` +
` projections.sms_configs3.aggregate_id,` +
` projections.sms_configs3.creation_date,` +
` projections.sms_configs3.change_date,` +
` projections.sms_configs3.resource_owner,` +
` projections.sms_configs3.state,` +
` projections.sms_configs3.sequence,` +
` projections.sms_configs3.description,` +
// twilio config
` projections.sms_configs2_twilio.sms_id,` +
` projections.sms_configs2_twilio.sid,` +
` projections.sms_configs2_twilio.token,` +
` projections.sms_configs2_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` +
` projections.sms_configs3_twilio.sms_id,` +
` projections.sms_configs3_twilio.sid,` +
` projections.sms_configs3_twilio.token,` +
` projections.sms_configs3_twilio.sender_number,` +
// 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'`)
expectedSMSConfigsQuery = regexp.QuoteMeta(`SELECT projections.sms_configs2.id,` +
` projections.sms_configs2.aggregate_id,` +
` projections.sms_configs2.creation_date,` +
` projections.sms_configs2.change_date,` +
` projections.sms_configs2.resource_owner,` +
` projections.sms_configs2.state,` +
` projections.sms_configs2.sequence,` +
expectedSMSConfigsQuery = regexp.QuoteMeta(`SELECT projections.sms_configs3.id,` +
` projections.sms_configs3.aggregate_id,` +
` projections.sms_configs3.creation_date,` +
` projections.sms_configs3.change_date,` +
` projections.sms_configs3.resource_owner,` +
` projections.sms_configs3.state,` +
` projections.sms_configs3.sequence,` +
` projections.sms_configs3.description,` +
// twilio config
` projections.sms_configs2_twilio.sms_id,` +
` projections.sms_configs2_twilio.sid,` +
` projections.sms_configs2_twilio.token,` +
` projections.sms_configs2_twilio.sender_number,` +
` projections.sms_configs3_twilio.sms_id,` +
` projections.sms_configs3_twilio.sid,` +
` projections.sms_configs3_twilio.token,` +
` projections.sms_configs3_twilio.sender_number,` +
// http config
` projections.sms_configs3_http.sms_id,` +
` projections.sms_configs3_http.endpoint,` +
` COUNT(*) OVER ()` +
` 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` +
` 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'`)
smsConfigCols = []string{
@ -56,16 +68,20 @@ var (
"resource_owner",
"state",
"sequence",
"description",
// twilio config
"sms_id",
"sid",
"token",
"sender-number",
// http config
"sms_id",
"endpoint",
}
smsConfigsCols = append(smsConfigCols, "count")
)
func Test_SMSConfigssPrepare(t *testing.T) {
func Test_SMSConfigsPrepare(t *testing.T) {
type want struct {
sqlExpectations sqlExpectation
err checkErr
@ -104,11 +120,15 @@ func Test_SMSConfigssPrepare(t *testing.T) {
"ro",
domain.SMSConfigStateInactive,
uint64(20211109),
"description",
// twilio config
"sms-id",
"sid",
&crypto.CryptoValue{},
"sender-number",
// http config
nil,
nil,
},
},
),
@ -126,6 +146,7 @@ func Test_SMSConfigssPrepare(t *testing.T) {
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{
SID: "sid",
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",
prepare: prepareSMSConfigsQuery,
@ -149,13 +220,17 @@ func Test_SMSConfigssPrepare(t *testing.T) {
testNow,
testNow,
"ro",
domain.SMSConfigStateInactive,
domain.SMSConfigStateActive,
uint64(20211109),
"description",
// twilio config
"sms-id",
"sid",
&crypto.CryptoValue{},
"sender-number",
// http config
nil,
nil,
},
{
"sms-id2",
@ -165,18 +240,40 @@ func Test_SMSConfigssPrepare(t *testing.T) {
"ro",
domain.SMSConfigStateInactive,
uint64(20211109),
"description",
// twilio config
"sms-id2",
"sid2",
&crypto.CryptoValue{},
"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{
SearchResponse: SearchResponse{
Count: 2,
Count: 3,
},
Configs: []*SMSConfig{
{
@ -185,8 +282,9 @@ func Test_SMSConfigssPrepare(t *testing.T) {
CreationDate: testNow,
ChangeDate: testNow,
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
State: domain.SMSConfigStateActive,
Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{
SID: "sid",
Token: &crypto.CryptoValue{},
@ -201,12 +299,26 @@ func Test_SMSConfigssPrepare(t *testing.T) {
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
Sequence: 20211109,
Description: "description",
TwilioConfig: &Twilio{
SID: "sid2",
Token: &crypto.CryptoValue{},
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),
},
{
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,
want: want{
sqlExpectations: mockQuery(
@ -279,11 +434,15 @@ func Test_SMSConfigPrepare(t *testing.T) {
"ro",
domain.SMSConfigStateInactive,
uint64(20211109),
"description",
// twilio config
nil,
nil,
nil,
nil,
// http config
"sms-id",
"sid",
&crypto.CryptoValue{},
"sender-number",
"endpoint",
},
),
},
@ -295,10 +454,9 @@ func Test_SMSConfigPrepare(t *testing.T) {
ResourceOwner: "ro",
State: domain.SMSConfigStateInactive,
Sequence: 20211109,
TwilioConfig: &Twilio{
SID: "sid",
SenderNumber: "sender-number",
Token: &crypto.CryptoValue{},
Description: "description",
HTTPConfig: &HTTP{
Endpoint: "endpoint",
},
},
},

View File

@ -18,12 +18,17 @@ func init() {
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigDeactivatedEventType, SMTPConfigDeactivatedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigPasswordChangedEventType, SMTPConfigPasswordChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMTPConfigRemovedEventType, SMTPConfigRemovedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, SMSConfigTwilioAddedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioChangedEventType, SMSConfigTwilioChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioTokenChangedEventType, SMSConfigTwilioTokenChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigActivatedEventType, SMSConfigActivatedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigDeactivatedEventType, SMSConfigDeactivatedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigRemovedEventType, SMSConfigRemovedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioAddedEventType, eventstore.GenericEventMapper[SMSConfigTwilioAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigTwilioTokenChangedEventType, eventstore.GenericEventMapper[SMSConfigTwilioTokenChangedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigHTTPAddedEventType, eventstore.GenericEventMapper[SMSConfigHTTPAddedEvent])
eventstore.RegisterFilterEventMapper(AggregateType, SMSConfigHTTPChangedEventType, eventstore.GenericEventMapper[SMSConfigHTTPChangedEvent])
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, DebugNotificationProviderFileChangedEventType, DebugNotificationProviderFileChangedEventMapper)
eventstore.RegisterFilterEventMapper(AggregateType, DebugNotificationProviderFileRemovedEventType, DebugNotificationProviderFileRemovedEventMapper)

View File

@ -11,18 +11,25 @@ import (
const (
smsConfigPrefix = "sms.config"
smsConfigTwilioPrefix = "twilio."
smsConfigHTTPPrefix = "http."
SMSConfigTwilioAddedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "added"
SMSConfigTwilioChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "changed"
SMSConfigHTTPAddedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigHTTPPrefix + "added"
SMSConfigHTTPChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigHTTPPrefix + "changed"
SMSConfigTwilioTokenChangedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "token.changed"
SMSConfigActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "activated"
SMSConfigDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "deactivated"
SMSConfigRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "removed"
SMSConfigTwilioActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "activated"
SMSConfigTwilioDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "deactivated"
SMSConfigTwilioRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + smsConfigTwilioPrefix + "removed"
SMSConfigActivatedEventType = instanceEventTypePrefix + smsConfigPrefix + "activated"
SMSConfigDeactivatedEventType = instanceEventTypePrefix + smsConfigPrefix + "deactivated"
SMSConfigRemovedEventType = instanceEventTypePrefix + smsConfigPrefix + "removed"
)
type SMSConfigTwilioAddedEvent struct {
eventstore.BaseEvent `json:"-"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
SID string `json:"sid,omitempty"`
Token *crypto.CryptoValue `json:"token,omitempty"`
SenderNumber string `json:"senderNumber,omitempty"`
@ -32,23 +39,29 @@ func NewSMSConfigTwilioAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
description string,
sid,
senderNumber string,
token *crypto.CryptoValue,
) *SMSConfigTwilioAddedEvent {
return &SMSConfigTwilioAddedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigTwilioAddedEventType,
),
ID: id,
Description: description,
SID: sid,
Token: token,
SenderNumber: senderNumber,
}
}
func (e *SMSConfigTwilioAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioAddedEvent) Payload() interface{} {
return e
}
@ -57,22 +70,11 @@ func (e *SMSConfigTwilioAddedEvent) UniqueConstraints() []*eventstore.UniqueCons
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 {
eventstore.BaseEvent `json:"-"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
Description *string `json:"description,omitempty"`
SID *string `json:"sid,omitempty"`
SenderNumber *string `json:"senderNumber,omitempty"`
}
@ -87,7 +89,7 @@ func NewSMSConfigTwilioChangedEvent(
return nil, zerrors.ThrowPreconditionFailed(nil, "IAM-smn8e", "Errors.NoChangesFound")
}
changeEvent := &SMSConfigTwilioChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
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) {
return func(e *SMSConfigTwilioChangedEvent) {
e.SenderNumber = &senderNumber
}
}
func (e *SMSConfigTwilioChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioChangedEvent) Payload() interface{} {
return e
}
@ -122,20 +134,8 @@ func (e *SMSConfigTwilioChangedEvent) UniqueConstraints() []*eventstore.UniqueCo
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 {
eventstore.BaseEvent `json:"-"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
Token *crypto.CryptoValue `json:"token,omitempty"`
@ -148,7 +148,7 @@ func NewSMSConfigTokenChangedEvent(
token *crypto.CryptoValue,
) *SMSConfigTwilioTokenChangedEvent {
return &SMSConfigTwilioTokenChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigTwilioTokenChangedEventType,
@ -158,6 +158,10 @@ func NewSMSConfigTokenChangedEvent(
}
}
func (e *SMSConfigTwilioTokenChangedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigTwilioTokenChangedEvent) Payload() interface{} {
return e
}
@ -166,30 +170,130 @@ func (e *SMSConfigTwilioTokenChangedEvent) UniqueConstraints() []*eventstore.Uni
return nil
}
func SMSConfigTwilioTokenChangedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smtpConfigTokenChagned := &SMSConfigTwilioTokenChangedEvent{
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")
}
type SMSConfigHTTPAddedEvent struct {
*eventstore.BaseEvent `json:"-"`
return smtpConfigTokenChagned, nil
ID string `json:"id,omitempty"`
Description string `json:"description,omitempty"`
Endpoint string `json:"endpoint,omitempty"`
}
func NewSMSConfigHTTPAddedEvent(
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"`
}
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"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
func NewSMSConfigTwilioActivatedEvent(
func NewSMSConfigActivatedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
) *SMSConfigActivatedEvent {
return &SMSConfigActivatedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigActivatedEventType,
@ -198,6 +302,10 @@ func NewSMSConfigTwilioActivatedEvent(
}
}
func (e *SMSConfigActivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigActivatedEvent) Payload() interface{} {
return e
}
@ -206,21 +314,26 @@ func (e *SMSConfigActivatedEvent) UniqueConstraints() []*eventstore.UniqueConstr
return nil
}
func SMSConfigActivatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smsConfigActivated := &SMSConfigActivatedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smsConfigActivated)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-dn92f", "unable to unmarshal sms config twilio activated changed")
}
type SMSConfigTwilioDeactivatedEvent struct {
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
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 {
eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
func NewSMSConfigDeactivatedEvent(
@ -229,7 +342,7 @@ func NewSMSConfigDeactivatedEvent(
id string,
) *SMSConfigDeactivatedEvent {
return &SMSConfigDeactivatedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigDeactivatedEventType,
@ -238,6 +351,10 @@ func NewSMSConfigDeactivatedEvent(
}
}
func (e *SMSConfigDeactivatedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigDeactivatedEvent) Payload() interface{} {
return e
}
@ -246,21 +363,26 @@ func (e *SMSConfigDeactivatedEvent) UniqueConstraints() []*eventstore.UniqueCons
return nil
}
func SMSConfigDeactivatedEventMapper(event eventstore.Event) (eventstore.Event, error) {
smsConfigDeactivated := &SMSConfigDeactivatedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := event.Unmarshal(smsConfigDeactivated)
if err != nil {
return nil, zerrors.ThrowInternal(err, "IAM-dn92f", "unable to unmarshal sms config twilio deactivated changed")
}
type SMSConfigTwilioRemovedEvent struct {
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
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 {
eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
*eventstore.BaseEvent `json:"-"`
ID string `json:"id,omitempty"`
}
func NewSMSConfigRemovedEvent(
@ -269,7 +391,7 @@ func NewSMSConfigRemovedEvent(
id string,
) *SMSConfigRemovedEvent {
return &SMSConfigRemovedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
BaseEvent: eventstore.NewBaseEventForPush(
ctx,
aggregate,
SMSConfigRemovedEventType,
@ -278,6 +400,10 @@ func NewSMSConfigRemovedEvent(
}
}
func (e *SMSConfigRemovedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
e.BaseEvent = event
}
func (e *SMSConfigRemovedEvent) Payload() interface{} {
return e
}
@ -285,15 +411,3 @@ func (e *SMSConfigRemovedEvent) Payload() interface{} {
func (e *SMSConfigRemovedEvent) UniqueConstraints() []*eventstore.UniqueConstraint {
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) {
option (google.api.http) = {
post: "/sms/{id}/_activate";
@ -4507,6 +4541,14 @@ message AddSMSProviderTwilioRequest {
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 {
@ -4534,6 +4576,14 @@ message UpdateSMSProviderTwilioRequest {
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 {
@ -4549,6 +4599,56 @@ message UpdateSMSProviderTwilioTokenResponse {
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 {
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;
option go_package ="github.com/zitadel/zitadel/pkg/grpc/settings";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings";
message SecretGenerator {
SecretGeneratorType generator_type = 1;
@ -99,9 +99,11 @@ message SMSProvider {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
SMSProviderConfigState state = 3;
string description = 6;
oneof config {
TwilioConfig twilio = 4;
HTTPConfig http = 5;
}
}
@ -110,6 +112,10 @@ message TwilioConfig {
string sender_number = 2;
}
message HTTPConfig {
string endpoint = 1;
}
enum SMSProviderConfigState {
SMS_PROVIDER_CONFIG_STATE_UNSPECIFIED = 0;
SMS_PROVIDER_CONFIG_ACTIVE = 1;
@ -117,8 +123,8 @@ enum SMSProviderConfigState {
}
message DebugNotificationProvider {
zitadel.v1.ObjectDetails details = 1;
bool compact = 2;
zitadel.v1.ObjectDetails details = 1;
bool compact = 2;
}
message OIDCSettings {