mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 22:27:23 +00:00
fix(notify): notify user in projection (#3889)
* start implement notify user in projection * fix(stmt): add copy to multi stmt * use projections for notify users * feat: notifications from projections * feat: notifications from projections * cleanup * pre-release * fix tests * fix types * fix command * fix queryNotifyUser * fix: build version * fix: HumanPasswordlessInitCodeSent Co-authored-by: adlerhurst <silvan.reusser@gmail.com>
This commit is contained in:
parent
d15a15c809
commit
a1d404291d
@ -3,7 +3,7 @@ module.exports = {
|
|||||||
{name: 'main'},
|
{name: 'main'},
|
||||||
{name: '1.x.x', range: '1.x.x', channel: '1.x.x'},
|
{name: '1.x.x', range: '1.x.x', channel: '1.x.x'},
|
||||||
{name: 'v2-alpha', prerelease: true},
|
{name: 'v2-alpha', prerelease: true},
|
||||||
{name: 'update-projection-on-query', prerelease: true},
|
{name: 'notify-users', prerelease: true},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@semantic-release/commit-analyzer"
|
"@semantic-release/commit-analyzer"
|
||||||
|
@ -20,7 +20,6 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/database"
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
"github.com/zitadel/zitadel/internal/notification"
|
|
||||||
"github.com/zitadel/zitadel/internal/query/projection"
|
"github.com/zitadel/zitadel/internal/query/projection"
|
||||||
static_config "github.com/zitadel/zitadel/internal/static/config"
|
static_config "github.com/zitadel/zitadel/internal/static/config"
|
||||||
tracing "github.com/zitadel/zitadel/internal/telemetry/tracing/config"
|
tracing "github.com/zitadel/zitadel/internal/telemetry/tracing/config"
|
||||||
@ -45,7 +44,6 @@ type Config struct {
|
|||||||
OIDC oidc.Config
|
OIDC oidc.Config
|
||||||
Login login.Config
|
Login login.Config
|
||||||
Console console.Config
|
Console console.Config
|
||||||
Notification notification.Config
|
|
||||||
AssetStorage static_config.AssetStorageConfig
|
AssetStorage static_config.AssetStorageConfig
|
||||||
InternalAuthZ internal_authz.Config
|
InternalAuthZ internal_authz.Config
|
||||||
SystemDefaults systemdefaults.SystemDefaults
|
SystemDefaults systemdefaults.SystemDefaults
|
||||||
|
@ -139,7 +139,7 @@ func startZitadel(config *Config, masterKey string) error {
|
|||||||
return fmt.Errorf("cannot start commands: %w", err)
|
return fmt.Errorf("cannot start commands: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
notification.Start(config.Notification, config.ExternalPort, config.ExternalSecure, commands, queries, dbClient, assets.HandlerPrefix, config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
|
notification.Start(ctx, config.Projections.Customizations["notifications"], config.ExternalPort, config.ExternalSecure, commands, queries, eventstoreClient, assets.AssetAPI(config.ExternalSecure), config.SystemDefaults.Notifications.FileSystemPath, keys.User, keys.SMTP, keys.SMS)
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
tlsConfig, err := config.TLS.Config()
|
tlsConfig, err := config.TLS.Config()
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||||
"github.com/zitadel/zitadel/internal/api/grpc/org"
|
"github.com/zitadel/zitadel/internal/api/grpc/org"
|
||||||
user_grpc "github.com/zitadel/zitadel/internal/api/grpc/user"
|
user_grpc "github.com/zitadel/zitadel/internal/api/grpc/user"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
@ -45,7 +46,7 @@ func (s *Server) RemoveMyUser(ctx context.Context, _ *auth_pb.RemoveMyUserReques
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
details, err := s.command.RemoveUser(ctx, ctxData.UserID, ctxData.ResourceOwner, memberships.Memberships, userGrantsToIDs(grants.UserGrants)...)
|
details, err := s.command.RemoveUser(ctx, ctxData.UserID, ctxData.ResourceOwner, cascadingMemberships(memberships.Memberships), userGrantsToIDs(grants.UserGrants)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -277,6 +278,46 @@ func MemberTypeToDomain(m *query.Membership) (_ domain.MemberType, displayName,
|
|||||||
return domain.MemberTypeUnspecified, "", "", ""
|
return domain.MemberTypeUnspecified, "", "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cascadingMemberships(memberships []*query.Membership) []*command.CascadingMembership {
|
||||||
|
cascades := make([]*command.CascadingMembership, len(memberships))
|
||||||
|
for i, membership := range memberships {
|
||||||
|
cascades[i] = &command.CascadingMembership{
|
||||||
|
UserID: membership.UserID,
|
||||||
|
ResourceOwner: membership.ResourceOwner,
|
||||||
|
IAM: cascadingIAMMembership(membership.IAM),
|
||||||
|
Org: cascadingOrgMembership(membership.Org),
|
||||||
|
Project: cascadingProjectMembership(membership.Project),
|
||||||
|
ProjectGrant: cascadingProjectGrantMembership(membership.ProjectGrant),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cascades
|
||||||
|
}
|
||||||
|
|
||||||
|
func cascadingIAMMembership(membership *query.IAMMembership) *command.CascadingIAMMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingIAMMembership{IAMID: membership.IAMID}
|
||||||
|
}
|
||||||
|
func cascadingOrgMembership(membership *query.OrgMembership) *command.CascadingOrgMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingOrgMembership{OrgID: membership.OrgID}
|
||||||
|
}
|
||||||
|
func cascadingProjectMembership(membership *query.ProjectMembership) *command.CascadingProjectMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingProjectMembership{ProjectID: membership.ProjectID}
|
||||||
|
}
|
||||||
|
func cascadingProjectGrantMembership(membership *query.ProjectGrantMembership) *command.CascadingProjectGrantMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingProjectGrantMembership{ProjectID: membership.ProjectID, GrantID: membership.GrantID}
|
||||||
|
}
|
||||||
|
|
||||||
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||||
converted := make([]string, len(userGrants))
|
converted := make([]string, len(userGrants))
|
||||||
for i, grant := range userGrants {
|
for i, grant := range userGrants {
|
||||||
|
@ -338,7 +338,7 @@ func (s *Server) RemoveUser(ctx context.Context, req *mgmt_pb.RemoveUserRequest)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
objectDetails, err := s.command.RemoveUser(ctx, req.Id, authz.GetCtxData(ctx).OrgID, memberships.Memberships, userGrantsToIDs(grants.UserGrants)...)
|
objectDetails, err := s.command.RemoveUser(ctx, req.Id, authz.GetCtxData(ctx).OrgID, cascadingMemberships(memberships.Memberships), userGrantsToIDs(grants.UserGrants)...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -347,14 +347,6 @@ func (s *Server) RemoveUser(ctx context.Context, req *mgmt_pb.RemoveUserRequest)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
|
||||||
converted := make([]string, len(userGrants))
|
|
||||||
for i, grant := range userGrants {
|
|
||||||
converted[i] = grant.ID
|
|
||||||
}
|
|
||||||
return converted
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Server) UpdateUserName(ctx context.Context, req *mgmt_pb.UpdateUserNameRequest) (*mgmt_pb.UpdateUserNameResponse, error) {
|
func (s *Server) UpdateUserName(ctx context.Context, req *mgmt_pb.UpdateUserNameRequest) (*mgmt_pb.UpdateUserNameResponse, error) {
|
||||||
objectDetails, err := s.command.ChangeUsername(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.UserName)
|
objectDetails, err := s.command.ChangeUsername(ctx, authz.GetCtxData(ctx).OrgID, req.UserId, req.UserName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -860,3 +852,51 @@ func (s *Server) ListUserMemberships(ctx context.Context, req *mgmt_pb.ListUserM
|
|||||||
Details: obj_grpc.ToListDetails(response.Count, response.Sequence, response.Timestamp),
|
Details: obj_grpc.ToListDetails(response.Count, response.Sequence, response.Timestamp),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cascadingMemberships(memberships []*query.Membership) []*command.CascadingMembership {
|
||||||
|
cascades := make([]*command.CascadingMembership, len(memberships))
|
||||||
|
for i, membership := range memberships {
|
||||||
|
cascades[i] = &command.CascadingMembership{
|
||||||
|
UserID: membership.UserID,
|
||||||
|
ResourceOwner: membership.ResourceOwner,
|
||||||
|
IAM: cascadingIAMMembership(membership.IAM),
|
||||||
|
Org: cascadingOrgMembership(membership.Org),
|
||||||
|
Project: cascadingProjectMembership(membership.Project),
|
||||||
|
ProjectGrant: cascadingProjectGrantMembership(membership.ProjectGrant),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cascades
|
||||||
|
}
|
||||||
|
|
||||||
|
func cascadingIAMMembership(membership *query.IAMMembership) *command.CascadingIAMMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingIAMMembership{IAMID: membership.IAMID}
|
||||||
|
}
|
||||||
|
func cascadingOrgMembership(membership *query.OrgMembership) *command.CascadingOrgMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingOrgMembership{OrgID: membership.OrgID}
|
||||||
|
}
|
||||||
|
func cascadingProjectMembership(membership *query.ProjectMembership) *command.CascadingProjectMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingProjectMembership{ProjectID: membership.ProjectID}
|
||||||
|
}
|
||||||
|
func cascadingProjectGrantMembership(membership *query.ProjectGrantMembership) *command.CascadingProjectGrantMembership {
|
||||||
|
if membership == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &command.CascadingProjectGrantMembership{ProjectID: membership.ProjectID, GrantID: membership.GrantID}
|
||||||
|
}
|
||||||
|
|
||||||
|
func userGrantsToIDs(userGrants []*query.UserGrant) []string {
|
||||||
|
converted := make([]string, len(userGrants))
|
||||||
|
for i, grant := range userGrants {
|
||||||
|
converted[i] = grant.ID
|
||||||
|
}
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
)
|
)
|
||||||
@ -174,7 +173,7 @@ func (c *Commands) UnlockUser(ctx context.Context, userID, resourceOwner string)
|
|||||||
return writeModelToObjectDetails(&existingUser.WriteModel), nil
|
return writeModelToObjectDetails(&existingUser.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingUserMemberships []*query.Membership, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
|
func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string, cascadingUserMemberships []*CascadingMembership, cascadingGrantIDs ...string) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2M0ds", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,39 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
"github.com/zitadel/zitadel/internal/repository/org"
|
"github.com/zitadel/zitadel/internal/repository/org"
|
||||||
"github.com/zitadel/zitadel/internal/repository/project"
|
"github.com/zitadel/zitadel/internal/repository/project"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Commands) removeUserMemberships(ctx context.Context, memberships []*query.Membership) (_ []eventstore.Command, err error) {
|
type CascadingMembership struct {
|
||||||
|
UserID string
|
||||||
|
ResourceOwner string
|
||||||
|
|
||||||
|
IAM *CascadingIAMMembership
|
||||||
|
Org *CascadingOrgMembership
|
||||||
|
Project *CascadingProjectMembership
|
||||||
|
ProjectGrant *CascadingProjectGrantMembership
|
||||||
|
}
|
||||||
|
|
||||||
|
type CascadingIAMMembership struct {
|
||||||
|
IAMID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CascadingOrgMembership struct {
|
||||||
|
OrgID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CascadingProjectMembership struct {
|
||||||
|
ProjectID string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CascadingProjectGrantMembership struct {
|
||||||
|
ProjectID string
|
||||||
|
GrantID string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) removeUserMemberships(ctx context.Context, memberships []*CascadingMembership) (_ []eventstore.Command, err error) {
|
||||||
events := make([]eventstore.Command, 0)
|
events := make([]eventstore.Command, 0)
|
||||||
for _, membership := range memberships {
|
for _, membership := range memberships {
|
||||||
if membership.IAM != nil {
|
if membership.IAM != nil {
|
||||||
|
@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||||
"github.com/zitadel/zitadel/internal/id"
|
"github.com/zitadel/zitadel/internal/id"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
)
|
)
|
||||||
@ -929,7 +928,7 @@ func TestCommandSide_RemoveUser(t *testing.T) {
|
|||||||
instanceID string
|
instanceID string
|
||||||
orgID string
|
orgID string
|
||||||
userID string
|
userID string
|
||||||
cascadeUserMemberships []*query.Membership
|
cascadeUserMemberships []*CascadingMembership
|
||||||
cascadeUserGrants []string
|
cascadeUserGrants []string
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1215,16 +1214,16 @@ func TestCommandSide_RemoveUser(t *testing.T) {
|
|||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
orgID: "org1",
|
orgID: "org1",
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
cascadeUserMemberships: []*query.Membership{
|
cascadeUserMemberships: []*CascadingMembership{
|
||||||
{
|
{
|
||||||
IAM: &query.IAMMembership{
|
IAM: &CascadingIAMMembership{
|
||||||
IAMID: "INSTANCE",
|
IAMID: "INSTANCE",
|
||||||
},
|
},
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Org: &query.OrgMembership{
|
Org: &CascadingOrgMembership{
|
||||||
OrgID: "org1",
|
OrgID: "org1",
|
||||||
},
|
},
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
@ -1232,14 +1231,14 @@ func TestCommandSide_RemoveUser(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
||||||
Project: &query.ProjectMembership{
|
Project: &CascadingProjectMembership{
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
},
|
},
|
||||||
UserID: "user1",
|
UserID: "user1",
|
||||||
ResourceOwner: "org1",
|
ResourceOwner: "org1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ProjectGrant: &query.ProjectGrantMembership{
|
ProjectGrant: &CascadingProjectGrantMembership{
|
||||||
ProjectID: "project1",
|
ProjectID: "project1",
|
||||||
GrantID: "grant1",
|
GrantID: "grant1",
|
||||||
},
|
},
|
||||||
|
@ -189,6 +189,12 @@ func AddDeleteStatement(conditions []handler.Condition, opts ...execOption) func
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func AddCopyStatement(from, to []handler.Column, conditions []handler.Condition, opts ...execOption) func(eventstore.Event) Exec {
|
||||||
|
return func(event eventstore.Event) Exec {
|
||||||
|
return NewCopyStatement(event, from, to, conditions, opts...).Execute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewArrayAppendCol(column string, value interface{}) handler.Column {
|
func NewArrayAppendCol(column string, value interface{}) handler.Column {
|
||||||
return handler.Column{
|
return handler.Column{
|
||||||
Name: column,
|
Name: column,
|
||||||
@ -233,19 +239,19 @@ func NewArrayIntersectCol(column string, value interface{}) handler.Column {
|
|||||||
// if the value of a col is empty the data will be copied from the selected row
|
// if the value of a col is empty the data will be copied from the selected row
|
||||||
// if the value of a col is not empty the data will be set by the static value
|
// if the value of a col is not empty the data will be set by the static value
|
||||||
// conds represent the conditions for the selection subquery
|
// conds represent the conditions for the selection subquery
|
||||||
func NewCopyStatement(event eventstore.Event, cols []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement {
|
func NewCopyStatement(event eventstore.Event, from, to []handler.Column, conds []handler.Condition, opts ...execOption) *handler.Statement {
|
||||||
columnNames := make([]string, len(cols))
|
columnNames := make([]string, len(to))
|
||||||
selectColumns := make([]string, len(cols))
|
selectColumns := make([]string, len(from))
|
||||||
argCounter := 0
|
argCounter := 0
|
||||||
args := []interface{}{}
|
args := []interface{}{}
|
||||||
|
|
||||||
for i, col := range cols {
|
for i := range from {
|
||||||
columnNames[i] = col.Name
|
columnNames[i] = to[i].Name
|
||||||
selectColumns[i] = col.Name
|
selectColumns[i] = from[i].Name
|
||||||
if col.Value != nil {
|
if from[i].Value != nil {
|
||||||
argCounter++
|
argCounter++
|
||||||
selectColumns[i] = "$" + strconv.Itoa(argCounter)
|
selectColumns[i] = "$" + strconv.Itoa(argCounter)
|
||||||
args = append(args, col.Value)
|
args = append(args, from[i].Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +266,7 @@ func NewCopyStatement(event eventstore.Event, cols []handler.Column, conds []han
|
|||||||
args: args,
|
args: args,
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cols) == 0 {
|
if len(from) == 0 || len(to) == 0 || len(from) != len(to) {
|
||||||
config.err = handler.ErrNoValues
|
config.err = handler.ErrNoValues
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -801,7 +801,8 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
type args struct {
|
type args struct {
|
||||||
table string
|
table string
|
||||||
event *testEvent
|
event *testEvent
|
||||||
cols []handler.Column
|
from []handler.Column
|
||||||
|
to []handler.Column
|
||||||
conds []handler.Condition
|
conds []handler.Condition
|
||||||
}
|
}
|
||||||
type want struct {
|
type want struct {
|
||||||
@ -856,7 +857,12 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
previousSequence: 0,
|
previousSequence: 0,
|
||||||
},
|
},
|
||||||
conds: []handler.Condition{},
|
conds: []handler.Condition{},
|
||||||
cols: []handler.Column{
|
from: []handler.Column{
|
||||||
|
{
|
||||||
|
Name: "col",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to: []handler.Column{
|
||||||
{
|
{
|
||||||
Name: "col",
|
Name: "col",
|
||||||
},
|
},
|
||||||
@ -876,7 +882,44 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "no values",
|
name: "more to than from cols",
|
||||||
|
args: args{
|
||||||
|
table: "my_table",
|
||||||
|
event: &testEvent{
|
||||||
|
aggregateType: "agg",
|
||||||
|
sequence: 1,
|
||||||
|
previousSequence: 0,
|
||||||
|
},
|
||||||
|
conds: []handler.Condition{},
|
||||||
|
from: []handler.Column{
|
||||||
|
{
|
||||||
|
Name: "col",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to: []handler.Column{
|
||||||
|
{
|
||||||
|
Name: "col",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
table: "my_table",
|
||||||
|
aggregateType: "agg",
|
||||||
|
sequence: 1,
|
||||||
|
previousSequence: 1,
|
||||||
|
executer: &wantExecuter{
|
||||||
|
shouldExecute: false,
|
||||||
|
},
|
||||||
|
isErr: func(err error) bool {
|
||||||
|
return errors.Is(err, handler.ErrNoCondition)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no columns",
|
||||||
args: args{
|
args: args{
|
||||||
table: "my_table",
|
table: "my_table",
|
||||||
event: &testEvent{
|
event: &testEvent{
|
||||||
@ -889,7 +932,7 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
Name: "col",
|
Name: "col",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cols: []handler.Column{},
|
from: []handler.Column{},
|
||||||
},
|
},
|
||||||
want: want{
|
want: want{
|
||||||
table: "my_table",
|
table: "my_table",
|
||||||
@ -905,7 +948,7 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "correct",
|
name: "correct same column names",
|
||||||
args: args{
|
args: args{
|
||||||
table: "my_table",
|
table: "my_table",
|
||||||
event: &testEvent{
|
event: &testEvent{
|
||||||
@ -913,7 +956,7 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
sequence: 1,
|
sequence: 1,
|
||||||
previousSequence: 0,
|
previousSequence: 0,
|
||||||
},
|
},
|
||||||
cols: []handler.Column{
|
from: []handler.Column{
|
||||||
{
|
{
|
||||||
Name: "state",
|
Name: "state",
|
||||||
Value: 1,
|
Value: 1,
|
||||||
@ -928,6 +971,20 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
Name: "col_b",
|
Name: "col_b",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
to: []handler.Column{
|
||||||
|
{
|
||||||
|
Name: "state",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_b",
|
||||||
|
},
|
||||||
|
},
|
||||||
conds: []handler.Condition{
|
conds: []handler.Condition{
|
||||||
{
|
{
|
||||||
Name: "id",
|
Name: "id",
|
||||||
@ -958,11 +1015,78 @@ func TestNewCopyStatement(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "correct different column names",
|
||||||
|
args: args{
|
||||||
|
table: "my_table",
|
||||||
|
event: &testEvent{
|
||||||
|
aggregateType: "agg",
|
||||||
|
sequence: 1,
|
||||||
|
previousSequence: 0,
|
||||||
|
},
|
||||||
|
from: []handler.Column{
|
||||||
|
{
|
||||||
|
Value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
to: []handler.Column{
|
||||||
|
{
|
||||||
|
Name: "state",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "col_d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
conds: []handler.Condition{
|
||||||
|
{
|
||||||
|
Name: "id",
|
||||||
|
Value: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "state",
|
||||||
|
Value: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
table: "my_table",
|
||||||
|
aggregateType: "agg",
|
||||||
|
sequence: 1,
|
||||||
|
previousSequence: 1,
|
||||||
|
executer: &wantExecuter{
|
||||||
|
params: []params{
|
||||||
|
{
|
||||||
|
query: "UPSERT INTO my_table (state, id, col_c, col_d) SELECT $1, id, col_a, col_b FROM my_table AS copy_table WHERE copy_table.id = $2 AND copy_table.state = $3",
|
||||||
|
args: []interface{}{1, 2, 3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
shouldExecute: true,
|
||||||
|
},
|
||||||
|
isErr: func(err error) bool {
|
||||||
|
return err == nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
tt.want.executer.t = t
|
tt.want.executer.t = t
|
||||||
stmt := NewCopyStatement(tt.args.event, tt.args.cols, tt.args.conds)
|
stmt := NewCopyStatement(tt.args.event, tt.args.from, tt.args.to, tt.args.conds)
|
||||||
|
|
||||||
err := stmt.Execute(tt.want.executer, tt.args.table)
|
err := stmt.Execute(tt.want.executer, tt.args.table)
|
||||||
if !tt.want.isErr(err) {
|
if !tt.want.isErr(err) {
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package notification
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/rakyll/statik/fs"
|
|
||||||
"github.com/zitadel/logging"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/repository/eventsourcing"
|
|
||||||
_ "github.com/zitadel/zitadel/internal/notification/statik"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Repository eventsourcing.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func Start(config Config,
|
|
||||||
externalPort uint16,
|
|
||||||
externalSecure bool,
|
|
||||||
command *command.Commands,
|
|
||||||
queries *query.Queries,
|
|
||||||
dbClient *sql.DB,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath string,
|
|
||||||
userEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smtpEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smsEncryption crypto.EncryptionAlgorithm,
|
|
||||||
) {
|
|
||||||
statikFS, err := fs.NewWithNamespace("notification")
|
|
||||||
logging.OnError(err).Panic("unable to start listener")
|
|
||||||
|
|
||||||
_, err = eventsourcing.Start(config.Repository, statikFS, externalPort, externalSecure, command, queries, dbClient, assetsPrefix, fileSystemPath, userEncryption, smtpEncryption, smsEncryption)
|
|
||||||
logging.OnError(err).Panic("unable to start app")
|
|
||||||
}
|
|
663
internal/notification/projection.go
Normal file
663
internal/notification/projection.go
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
statik_fs "github.com/rakyll/statik/fs"
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
||||||
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/handler"
|
||||||
|
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||||
|
"github.com/zitadel/zitadel/internal/i18n"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
||||||
|
_ "github.com/zitadel/zitadel/internal/notification/statik"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/types"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
"github.com/zitadel/zitadel/internal/query/projection"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotificationsProjectionTable = "projections.notifications"
|
||||||
|
NotifyUserID = "NOTIFICATION" //TODO: system?
|
||||||
|
)
|
||||||
|
|
||||||
|
func Start(ctx context.Context, customConfig projection.CustomConfig, externalPort uint16, externalSecure bool, commands *command.Commands, queries *query.Queries, es *eventstore.Eventstore, assetsPrefix func(context.Context) string, fileSystemPath string, userEncryption, smtpEncryption, smsEncryption crypto.EncryptionAlgorithm) {
|
||||||
|
statikFS, err := statik_fs.NewWithNamespace("notification")
|
||||||
|
logging.OnError(err).Panic("unable to start listener")
|
||||||
|
|
||||||
|
projection.NotificationsProjection = newNotificationsProjection(ctx, projection.ApplyCustomConfig(customConfig), commands, queries, es, userEncryption, smtpEncryption, smsEncryption, externalSecure, externalPort, fileSystemPath, assetsPrefix, statikFS)
|
||||||
|
}
|
||||||
|
|
||||||
|
type notificationsProjection struct {
|
||||||
|
crdb.StatementHandler
|
||||||
|
commands *command.Commands
|
||||||
|
queries *query.Queries
|
||||||
|
es *eventstore.Eventstore
|
||||||
|
userDataCrypto crypto.EncryptionAlgorithm
|
||||||
|
smtpPasswordCrypto crypto.EncryptionAlgorithm
|
||||||
|
smsTokenCrypto crypto.EncryptionAlgorithm
|
||||||
|
assetsPrefix func(context.Context) string
|
||||||
|
fileSystemPath string
|
||||||
|
externalPort uint16
|
||||||
|
externalSecure bool
|
||||||
|
statikDir http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNotificationsProjection(
|
||||||
|
ctx context.Context,
|
||||||
|
config crdb.StatementHandlerConfig,
|
||||||
|
commands *command.Commands,
|
||||||
|
queries *query.Queries,
|
||||||
|
es *eventstore.Eventstore,
|
||||||
|
userDataCrypto,
|
||||||
|
smtpPasswordCrypto,
|
||||||
|
smsTokenCrypto crypto.EncryptionAlgorithm,
|
||||||
|
externalSecure bool,
|
||||||
|
externalPort uint16,
|
||||||
|
fileSystemPath string,
|
||||||
|
assetsPrefix func(context.Context) string,
|
||||||
|
statikDir http.FileSystem,
|
||||||
|
) *notificationsProjection {
|
||||||
|
p := new(notificationsProjection)
|
||||||
|
config.ProjectionName = NotificationsProjectionTable
|
||||||
|
config.Reducers = p.reducers()
|
||||||
|
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||||
|
p.commands = commands
|
||||||
|
p.queries = queries
|
||||||
|
p.es = es
|
||||||
|
p.userDataCrypto = userDataCrypto
|
||||||
|
p.smtpPasswordCrypto = smtpPasswordCrypto
|
||||||
|
p.smsTokenCrypto = smsTokenCrypto
|
||||||
|
p.assetsPrefix = assetsPrefix
|
||||||
|
p.externalPort = externalPort
|
||||||
|
p.externalSecure = externalSecure
|
||||||
|
p.fileSystemPath = fileSystemPath
|
||||||
|
p.statikDir = statikDir
|
||||||
|
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reducers() []handler.AggregateReducer {
|
||||||
|
return []handler.AggregateReducer{
|
||||||
|
{
|
||||||
|
Aggregate: user.AggregateType,
|
||||||
|
EventRedusers: []handler.EventReducer{
|
||||||
|
{
|
||||||
|
Event: user.UserV1InitialCodeAddedType,
|
||||||
|
Reduce: p.reduceInitCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanInitialCodeAddedType,
|
||||||
|
Reduce: p.reduceInitCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.UserV1EmailCodeAddedType,
|
||||||
|
Reduce: p.reduceEmailCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanEmailCodeAddedType,
|
||||||
|
Reduce: p.reduceEmailCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.UserV1PasswordCodeAddedType,
|
||||||
|
Reduce: p.reducePasswordCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanPasswordCodeAddedType,
|
||||||
|
Reduce: p.reducePasswordCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.UserDomainClaimedType,
|
||||||
|
Reduce: p.reduceDomainClaimed,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanPasswordlessInitCodeRequestedType,
|
||||||
|
Reduce: p.reducePasswordlessCodeRequested,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.UserV1PhoneCodeAddedType,
|
||||||
|
Reduce: p.reducePhoneCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanPhoneCodeAddedType,
|
||||||
|
Reduce: p.reducePhoneCodeAdded,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reduceInitCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanInitialCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-EFe2f", "reduce.wrong.event.type %s", user.HumanInitialCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.UserV1InitialCodeAddedType, user.UserV1InitialCodeSentType,
|
||||||
|
user.HumanInitialCodeAddedType, user.HumanInitialCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, p.userDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.InitCodeMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getSMTPConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
).SendUserInitCode(notifyUser, origin, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.HumanInitCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reduceEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanEmailCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-SWf3g", "reduce.wrong.event.type %s", user.HumanEmailCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.UserV1EmailCodeAddedType, user.UserV1EmailCodeSentType,
|
||||||
|
user.HumanEmailCodeAddedType, user.HumanEmailCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, p.userDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyEmailMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getSMTPConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
).SendEmailVerificationCode(notifyUser, origin, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.HumanEmailVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reducePasswordCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanPasswordCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Eeg3s", "reduce.wrong.event.type %s", user.HumanPasswordCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.UserV1PasswordCodeAddedType, user.UserV1PasswordCodeSentType,
|
||||||
|
user.HumanPasswordCodeAddedType, user.HumanPasswordCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, p.userDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordResetMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
notify := types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getSMTPConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
)
|
||||||
|
if e.NotificationType == domain.NotificationTypeSms {
|
||||||
|
notify = types.SendSMSTwilio(
|
||||||
|
ctx,
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getTwilioConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
err = notify.SendPasswordCode(notifyUser, origin, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.PasswordCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.DomainClaimedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-Drh5w", "reduce.wrong.event.type %s", user.UserDomainClaimedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfAlreadyHandled(ctx, event, nil,
|
||||||
|
user.UserDomainClaimedType, user.UserDomainClaimedSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.DomainClaimedMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getSMTPConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
).SendDomainClaimed(notifyUser, origin, e.UserName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.UserDomainClaimedSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reducePasswordlessCodeRequested(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanPasswordlessInitCodeRequestedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-EDtjd", "reduce.wrong.event.type %s", user.HumanPasswordlessInitCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, map[string]interface{}{"id": e.ID}, user.HumanPasswordlessInitCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, p.userDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := p.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.PasswordlessRegistrationMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getSMTPConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
).SendPasswordlessRegistrationLink(notifyUser, origin, code, e.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.HumanPasswordlessInitCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner, e.ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) reducePhoneCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanPhoneCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-He83g", "reduce.wrong.event.type %s", user.HumanPhoneCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := setNotificationContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := p.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.UserV1PhoneCodeAddedType, user.UserV1PhoneCodeSentType,
|
||||||
|
user.HumanPhoneCodeAddedType, user.HumanPhoneCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, p.userDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := p.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := p.queries.GeNotifyUser(ctx, true, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := p.getTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyPhoneMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
origin, err := p.origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = types.SendSMSTwilio(
|
||||||
|
ctx,
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
p.getTwilioConfig,
|
||||||
|
p.getFileSystemProvider,
|
||||||
|
p.getLogProvider,
|
||||||
|
colors,
|
||||||
|
p.assetsPrefix(ctx),
|
||||||
|
).SendPhoneVerificationCode(notifyUser, origin, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = p.commands.HumanPhoneVerificationCodeSent(ctx, e.Aggregate().ResourceOwner, e.Aggregate().ID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event eventstore.Event, expiry time.Duration, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) {
|
||||||
|
if event.CreationDate().Add(expiry).Before(time.Now().UTC()) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return p.checkIfAlreadyHandled(ctx, event, data, eventTypes...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) checkIfAlreadyHandled(ctx context.Context, event eventstore.Event, data map[string]interface{}, eventTypes ...eventstore.EventType) (bool, error) {
|
||||||
|
events, err := p.es.Filter(
|
||||||
|
ctx,
|
||||||
|
eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
InstanceID(event.Aggregate().InstanceID).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(user.AggregateType).
|
||||||
|
AggregateIDs(event.Aggregate().ID).
|
||||||
|
SequenceGreater(event.Sequence()).
|
||||||
|
EventTypes(eventTypes...).
|
||||||
|
EventData(data).
|
||||||
|
Builder(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return len(events) > 0, nil
|
||||||
|
}
|
||||||
|
func (p *notificationsProjection) getSMTPConfig(ctx context.Context) (*smtp.EmailConfig, error) {
|
||||||
|
config, err := p.queries.SMTPConfigByAggregateID(ctx, authz.GetInstance(ctx).InstanceID())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
password, err := crypto.DecryptString(config.Password, p.smtpPasswordCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &smtp.EmailConfig{
|
||||||
|
From: config.SenderAddress,
|
||||||
|
FromName: config.SenderName,
|
||||||
|
Tls: config.TLS,
|
||||||
|
SMTP: smtp.SMTP{
|
||||||
|
Host: config.Host,
|
||||||
|
User: config.User,
|
||||||
|
Password: password,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read iam twilio config
|
||||||
|
func (p *notificationsProjection) getTwilioConfig(ctx context.Context) (*twilio.TwilioConfig, error) {
|
||||||
|
active, err := query.NewSMSProviderStateQuery(domain.SMSConfigStateActive)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
config, err := p.queries.SMSProviderConfig(ctx, active)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config.TwilioConfig == nil {
|
||||||
|
return nil, errors.ThrowNotFound(nil, "HANDLER-8nfow", "Errors.SMS.Twilio.NotFound")
|
||||||
|
}
|
||||||
|
token, err := crypto.DecryptString(config.TwilioConfig.Token, p.smsTokenCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &twilio.TwilioConfig{
|
||||||
|
SID: config.TwilioConfig.SID,
|
||||||
|
Token: token,
|
||||||
|
SenderNumber: config.TwilioConfig.SenderNumber,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read iam filesystem provider config
|
||||||
|
func (p *notificationsProjection) getFileSystemProvider(ctx context.Context) (*fs.FSConfig, error) {
|
||||||
|
config, err := p.queries.NotificationProviderByIDAndType(ctx, authz.GetInstance(ctx).InstanceID(), domain.NotificationProviderTypeFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &fs.FSConfig{
|
||||||
|
Compact: config.Compact,
|
||||||
|
Path: p.fileSystemPath,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read iam log provider config
|
||||||
|
func (p *notificationsProjection) getLogProvider(ctx context.Context) (*log.LogConfig, error) {
|
||||||
|
config, err := p.queries.NotificationProviderByIDAndType(ctx, authz.GetInstance(ctx).InstanceID(), domain.NotificationProviderTypeLog)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &log.LogConfig{
|
||||||
|
Compact: config.Compact,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) getTranslatorWithOrgTexts(ctx context.Context, orgID, textType string) (*i18n.Translator, error) {
|
||||||
|
translator, err := i18n.NewTranslator(p.statikDir, p.queries.GetDefaultLanguage(ctx), "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
allCustomTexts, err := p.queries.CustomTextListByTemplate(ctx, authz.GetInstance(ctx).InstanceID(), textType)
|
||||||
|
if err != nil {
|
||||||
|
return translator, nil
|
||||||
|
}
|
||||||
|
customTexts, err := p.queries.CustomTextListByTemplate(ctx, orgID, textType)
|
||||||
|
if err != nil {
|
||||||
|
return translator, nil
|
||||||
|
}
|
||||||
|
allCustomTexts.CustomTexts = append(allCustomTexts.CustomTexts, customTexts.CustomTexts...)
|
||||||
|
|
||||||
|
for _, text := range allCustomTexts.CustomTexts {
|
||||||
|
msg := i18n.Message{
|
||||||
|
ID: text.Template + "." + text.Key,
|
||||||
|
Text: text.Text,
|
||||||
|
}
|
||||||
|
err = translator.AddMessages(text.Language, msg)
|
||||||
|
logging.WithFields("instanceID", authz.GetInstance(ctx).InstanceID(), "orgID", orgID, "messageType", textType, "messageID", msg.ID).
|
||||||
|
OnError(err).
|
||||||
|
Warn("could not add translation message")
|
||||||
|
}
|
||||||
|
return translator, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *notificationsProjection) origin(ctx context.Context) (string, error) {
|
||||||
|
primary, err := query.NewInstanceDomainPrimarySearchQuery(true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
domains, err := p.queries.SearchInstanceDomains(ctx, &query.InstanceDomainSearchQueries{
|
||||||
|
Queries: []query.SearchQuery{primary},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(domains.Domains) < 1 {
|
||||||
|
return "", errors.ThrowInternal(nil, "NOTIF-Ef3r1", "Errors.Notification.NoDomain")
|
||||||
|
}
|
||||||
|
return http_utils.BuildHTTP(domains.Domains[0].Domain, p.externalPort, p.externalSecure), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setNotificationContext(event eventstore.Aggregate) context.Context {
|
||||||
|
ctx := authz.WithInstanceID(context.Background(), event.InstanceID)
|
||||||
|
return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: event.ResourceOwner})
|
||||||
|
}
|
@ -1,89 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
|
|
||||||
queryv1 "github.com/zitadel/zitadel/internal/eventstore/v1/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/repository/eventsourcing/view"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Configs map[string]*Config
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
MinimumCycleDuration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type handler struct {
|
|
||||||
view *view.View
|
|
||||||
bulkLimit uint64
|
|
||||||
cycleDuration time.Duration
|
|
||||||
errorCountUntilSkip uint64
|
|
||||||
|
|
||||||
es v1.Eventstore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) Eventstore() v1.Eventstore {
|
|
||||||
return h.es
|
|
||||||
}
|
|
||||||
|
|
||||||
func Register(configs Configs,
|
|
||||||
bulkLimit,
|
|
||||||
errorCount uint64,
|
|
||||||
view *view.View,
|
|
||||||
es v1.Eventstore,
|
|
||||||
command *command.Commands,
|
|
||||||
queries *query.Queries,
|
|
||||||
externalPort uint16,
|
|
||||||
externalSecure bool,
|
|
||||||
dir http.FileSystem,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath string,
|
|
||||||
userEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smtpEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smsEncryption crypto.EncryptionAlgorithm,
|
|
||||||
) []queryv1.Handler {
|
|
||||||
return []queryv1.Handler{
|
|
||||||
newNotifyUser(
|
|
||||||
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
|
|
||||||
queries,
|
|
||||||
),
|
|
||||||
newNotification(
|
|
||||||
handler{view, bulkLimit, configs.cycleDuration("Notification"), errorCount, es},
|
|
||||||
command,
|
|
||||||
queries,
|
|
||||||
externalPort,
|
|
||||||
externalSecure,
|
|
||||||
dir,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath,
|
|
||||||
userEncryption,
|
|
||||||
smtpEncryption,
|
|
||||||
smsEncryption,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (configs Configs) cycleDuration(viewModel string) time.Duration {
|
|
||||||
c, ok := configs[viewModel]
|
|
||||||
if !ok {
|
|
||||||
return 1 * time.Minute
|
|
||||||
}
|
|
||||||
return c.MinimumCycleDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) MinimumCycleDuration() time.Duration {
|
|
||||||
return h.cycleDuration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) LockDuration() time.Duration {
|
|
||||||
return h.cycleDuration / 3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *handler) QueryLimit() uint64 {
|
|
||||||
return h.bulkLimit
|
|
||||||
}
|
|
@ -1,637 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zitadel/logging"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
|
||||||
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
|
||||||
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
||||||
queryv1 "github.com/zitadel/zitadel/internal/eventstore/v1/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
|
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/types"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
user_repo "github.com/zitadel/zitadel/internal/repository/user"
|
|
||||||
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
|
|
||||||
"github.com/zitadel/zitadel/internal/user/repository/view"
|
|
||||||
"github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
notificationTable = "notification.notifications"
|
|
||||||
NotifyUserID = "NOTIFICATION"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Notification struct {
|
|
||||||
handler
|
|
||||||
command *command.Commands
|
|
||||||
fileSystemPath string
|
|
||||||
statikDir http.FileSystem
|
|
||||||
subscription *v1.Subscription
|
|
||||||
assetsPrefix string
|
|
||||||
queries *query.Queries
|
|
||||||
userDataCrypto crypto.EncryptionAlgorithm
|
|
||||||
smtpPasswordCrypto crypto.EncryptionAlgorithm
|
|
||||||
smsTokenCrypto crypto.EncryptionAlgorithm
|
|
||||||
externalPort uint16
|
|
||||||
externalSecure bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNotification(
|
|
||||||
handler handler,
|
|
||||||
command *command.Commands,
|
|
||||||
query *query.Queries,
|
|
||||||
externalPort uint16,
|
|
||||||
externalSecure bool,
|
|
||||||
statikDir http.FileSystem,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath string,
|
|
||||||
userEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smtpEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smsEncryption crypto.EncryptionAlgorithm,
|
|
||||||
) *Notification {
|
|
||||||
h := &Notification{
|
|
||||||
handler: handler,
|
|
||||||
command: command,
|
|
||||||
statikDir: statikDir,
|
|
||||||
assetsPrefix: assetsPrefix,
|
|
||||||
queries: query,
|
|
||||||
userDataCrypto: userEncryption,
|
|
||||||
smtpPasswordCrypto: smtpEncryption,
|
|
||||||
smsTokenCrypto: smsEncryption,
|
|
||||||
externalSecure: externalSecure,
|
|
||||||
externalPort: externalPort,
|
|
||||||
fileSystemPath: fileSystemPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
h.subscribe()
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *Notification) subscribe() {
|
|
||||||
k.subscription = k.es.Subscribe(k.AggregateTypes()...)
|
|
||||||
go func() {
|
|
||||||
for event := range k.subscription.Events {
|
|
||||||
queryv1.ReduceEvent(k, event)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) ViewModel() string {
|
|
||||||
return notificationTable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) Subscription() *v1.Subscription {
|
|
||||||
return n.subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *Notification) AggregateTypes() []models.AggregateType {
|
|
||||||
return []models.AggregateType{user_repo.AggregateType}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) CurrentSequence(instanceID string) (uint64, error) {
|
|
||||||
sequence, err := n.view.GetLatestNotificationSequence(instanceID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return sequence.CurrentSequence, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) EventQuery() (*models.SearchQuery, error) {
|
|
||||||
sequences, err := n.view.GetLatestNotificationSequences()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
query := models.NewSearchQuery()
|
|
||||||
instances := make([]string, 0)
|
|
||||||
for _, sequence := range sequences {
|
|
||||||
for _, instance := range instances {
|
|
||||||
if sequence.InstanceID == instance {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instances = append(instances, sequence.InstanceID)
|
|
||||||
query.AddQuery().
|
|
||||||
AggregateTypeFilter(n.AggregateTypes()...).
|
|
||||||
LatestSequenceFilter(sequence.CurrentSequence).
|
|
||||||
InstanceIDFilter(sequence.InstanceID)
|
|
||||||
}
|
|
||||||
return query.AddQuery().
|
|
||||||
AggregateTypeFilter(n.AggregateTypes()...).
|
|
||||||
LatestSequenceFilter(0).
|
|
||||||
ExcludedInstanceIDsFilter(instances...).
|
|
||||||
SearchQuery(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) Reduce(event *models.Event) (err error) {
|
|
||||||
switch eventstore.EventType(event.Type) {
|
|
||||||
case user_repo.UserV1InitialCodeAddedType,
|
|
||||||
user_repo.HumanInitialCodeAddedType:
|
|
||||||
err = n.handleInitUserCode(event)
|
|
||||||
case user_repo.UserV1EmailCodeAddedType,
|
|
||||||
user_repo.HumanEmailCodeAddedType:
|
|
||||||
err = n.handleEmailVerificationCode(event)
|
|
||||||
case user_repo.UserV1PhoneCodeAddedType,
|
|
||||||
user_repo.HumanPhoneCodeAddedType:
|
|
||||||
err = n.handlePhoneVerificationCode(event)
|
|
||||||
case user_repo.UserV1PasswordCodeAddedType,
|
|
||||||
user_repo.HumanPasswordCodeAddedType:
|
|
||||||
err = n.handlePasswordCode(event)
|
|
||||||
case user_repo.UserDomainClaimedType:
|
|
||||||
err = n.handleDomainClaimed(event)
|
|
||||||
case user_repo.HumanPasswordlessInitCodeRequestedType:
|
|
||||||
err = n.handlePasswordlessRegistrationLink(event)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.view.ProcessedNotificationSequence(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
|
|
||||||
initCode := new(es_model.InitUserCode)
|
|
||||||
if err := initCode.SetData(event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
alreadyHandled, err := n.checkIfCodeAlreadyHandledOrExpired(ctx, event, initCode.Expiry,
|
|
||||||
user_repo.UserV1InitialCodeAddedType, user_repo.UserV1InitialCodeSentType,
|
|
||||||
user_repo.HumanInitialCodeAddedType, user_repo.HumanInitialCodeSentType)
|
|
||||||
if err != nil || alreadyHandled {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
colors, err := n.getLabelPolicy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := n.getMailTemplate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence < event.Sequence {
|
|
||||||
if err = n.verifyLatestUser(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence == 0 {
|
|
||||||
return errors.ThrowNotFound(nil, "HANDL-JED2R", "no user events found")
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.InitCodeMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := n.origin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix, origin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.HumanInitCodeSent(ctx, event.ResourceOwner, event.AggregateID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
|
|
||||||
pwCode := new(es_model.PasswordCode)
|
|
||||||
if err := pwCode.SetData(event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
alreadyHandled, err := n.checkIfCodeAlreadyHandledOrExpired(ctx, event, pwCode.Expiry,
|
|
||||||
user_repo.UserV1PasswordCodeAddedType, user_repo.UserV1PasswordCodeSentType,
|
|
||||||
user_repo.HumanPasswordCodeAddedType, user_repo.HumanPasswordCodeSentType)
|
|
||||||
if err != nil || alreadyHandled {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
colors, err := n.getLabelPolicy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := n.getMailTemplate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence < event.Sequence {
|
|
||||||
if err = n.verifyLatestUser(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence == 0 {
|
|
||||||
return errors.ThrowNotFound(nil, "HANDL-JED2R", "no user events found")
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.PasswordResetMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := n.origin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.getSMTPConfig, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix, origin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.PasswordCodeSent(ctx, event.ResourceOwner, event.AggregateID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handleEmailVerificationCode(event *models.Event) (err error) {
|
|
||||||
emailCode := new(es_model.EmailCode)
|
|
||||||
if err := emailCode.SetData(event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
alreadyHandled, err := n.checkIfCodeAlreadyHandledOrExpired(ctx, event, emailCode.Expiry,
|
|
||||||
user_repo.UserV1EmailCodeAddedType, user_repo.UserV1EmailCodeSentType,
|
|
||||||
user_repo.HumanEmailCodeAddedType, user_repo.HumanEmailCodeSentType)
|
|
||||||
if err != nil || alreadyHandled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
colors, err := n.getLabelPolicy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := n.getMailTemplate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if user.Sequence < event.Sequence {
|
|
||||||
if err = n.verifyLatestUser(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence == 0 {
|
|
||||||
return errors.ThrowNotFound(nil, "HANDL-JED2R", "no user events found")
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.VerifyEmailMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := n.origin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix, origin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.HumanEmailVerificationCodeSent(ctx, event.ResourceOwner, event.AggregateID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err error) {
|
|
||||||
phoneCode := new(es_model.PhoneCode)
|
|
||||||
if err := phoneCode.SetData(event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
alreadyHandled, err := n.checkIfCodeAlreadyHandledOrExpired(ctx, event, phoneCode.Expiry,
|
|
||||||
user_repo.UserV1PhoneCodeAddedType, user_repo.UserV1PhoneCodeSentType,
|
|
||||||
user_repo.HumanPhoneCodeAddedType, user_repo.HumanPhoneCodeSentType)
|
|
||||||
if err != nil || alreadyHandled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence < event.Sequence {
|
|
||||||
if err = n.verifyLatestUser(ctx, user); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Sequence == 0 {
|
|
||||||
return errors.ThrowNotFound(nil, "HANDL-JED2R", "no user events found")
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.VerifyPhoneMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendPhoneVerificationCode(ctx, translator, user, phoneCode, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.HumanPhoneVerificationCodeSent(ctx, event.ResourceOwner, event.AggregateID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handleDomainClaimed(event *models.Event) (err error) {
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
alreadyHandled, err := n.checkIfAlreadyHandled(ctx, event.AggregateID, event.InstanceID, event.Sequence, user_repo.UserDomainClaimedType, user_repo.UserDomainClaimedSentType)
|
|
||||||
if err != nil || alreadyHandled {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
data := make(map[string]string)
|
|
||||||
if err := json.Unmarshal(event.Data, &data); err != nil {
|
|
||||||
logging.Log("HANDLE-Gghq2").WithError(err).Error("could not unmarshal event data")
|
|
||||||
return errors.ThrowInternal(err, "HANDLE-7hgj3", "could not unmarshal event")
|
|
||||||
}
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if user.LastEmail == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
colors, err := n.getLabelPolicy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := n.getMailTemplate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.DomainClaimedMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := n.origin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendDomainClaimed(ctx, string(template.Template), translator, user, data["userName"], n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, colors, n.assetsPrefix, origin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.UserDomainClaimedSent(ctx, event.ResourceOwner, event.AggregateID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) handlePasswordlessRegistrationLink(event *models.Event) (err error) {
|
|
||||||
addedEvent := new(user_repo.HumanPasswordlessInitCodeRequestedEvent)
|
|
||||||
if err := json.Unmarshal(event.Data, addedEvent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ctx := getSetNotifyContextData(event.InstanceID, event.ResourceOwner)
|
|
||||||
events, err := n.getUserEvents(ctx, event.AggregateID, event.InstanceID, event.Sequence)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, e := range events {
|
|
||||||
if eventstore.EventType(e.Type) == user_repo.HumanPasswordlessInitCodeSentType {
|
|
||||||
sentEvent := new(user_repo.HumanPasswordlessInitCodeSentEvent)
|
|
||||||
if err := json.Unmarshal(e.Data, sentEvent); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if sentEvent.ID == addedEvent.ID {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
user, err := n.getUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
colors, err := n.getLabelPolicy(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := n.getMailTemplate(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
translator, err := n.getTranslatorWithOrgTexts(ctx, user.ResourceOwner, domain.PasswordlessRegistrationMessageType)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
origin, err := n.origin(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix, origin)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return n.command.HumanPasswordlessInitCodeSent(ctx, event.AggregateID, event.ResourceOwner, addedEvent.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) checkIfCodeAlreadyHandledOrExpired(ctx context.Context, event *models.Event, expiry time.Duration, eventTypes ...eventstore.EventType) (bool, error) {
|
|
||||||
if event.CreationDate.Add(expiry).Before(time.Now().UTC()) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
return n.checkIfAlreadyHandled(ctx, event.AggregateID, event.InstanceID, event.Sequence, eventTypes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) checkIfAlreadyHandled(ctx context.Context, userID, instanceID string, sequence uint64, eventTypes ...eventstore.EventType) (bool, error) {
|
|
||||||
events, err := n.getUserEvents(ctx, userID, instanceID, sequence)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
for _, event := range events {
|
|
||||||
for _, eventType := range eventTypes {
|
|
||||||
if eventstore.EventType(event.Type) == eventType {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) getUserEvents(ctx context.Context, userID, instanceID string, sequence uint64) ([]*models.Event, error) {
|
|
||||||
query, err := view.UserByIDQuery(userID, instanceID, sequence)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.es.FilterEvents(ctx, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) OnError(event *models.Event, err error) error {
|
|
||||||
logging.WithFields("id", event.AggregateID, "sequence", event.Sequence).WithError(err).Warn("something went wrong in notification handler")
|
|
||||||
return spooler.HandleError(event, err, n.view.GetLatestNotificationFailedEvent, n.view.ProcessedNotificationFailedEvent, n.view.ProcessedNotificationSequence, n.errorCountUntilSkip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) OnSuccess() error {
|
|
||||||
return spooler.HandleSuccess(n.view.UpdateNotificationSpoolerRunTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSetNotifyContextData(instanceID, orgID string) context.Context {
|
|
||||||
ctx := authz.WithInstanceID(context.Background(), instanceID)
|
|
||||||
return authz.SetCtxData(ctx, authz.CtxData{UserID: NotifyUserID, OrgID: orgID})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read organization specific colors
|
|
||||||
func (n *Notification) getLabelPolicy(ctx context.Context) (*query.LabelPolicy, error) {
|
|
||||||
return n.queries.ActiveLabelPolicyByOrg(ctx, authz.GetCtxData(ctx).OrgID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read organization specific template
|
|
||||||
func (n *Notification) getMailTemplate(ctx context.Context) (*query.MailTemplate, error) {
|
|
||||||
return n.queries.MailTemplateByOrg(ctx, authz.GetCtxData(ctx).OrgID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read iam smtp config
|
|
||||||
func (n *Notification) getSMTPConfig(ctx context.Context) (*smtp.EmailConfig, error) {
|
|
||||||
config, err := n.queries.SMTPConfigByAggregateID(ctx, authz.GetInstance(ctx).InstanceID())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
password, err := crypto.Decrypt(config.Password, n.smtpPasswordCrypto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &smtp.EmailConfig{
|
|
||||||
From: config.SenderAddress,
|
|
||||||
FromName: config.SenderName,
|
|
||||||
Tls: config.TLS,
|
|
||||||
SMTP: smtp.SMTP{
|
|
||||||
Host: config.Host,
|
|
||||||
User: config.User,
|
|
||||||
Password: string(password),
|
|
||||||
},
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read iam twilio config
|
|
||||||
func (n *Notification) getTwilioConfig(ctx context.Context) (*twilio.TwilioConfig, error) {
|
|
||||||
active, err := query.NewSMSProviderStateQuery(domain.SMSConfigStateActive)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
config, err := n.queries.SMSProviderConfig(ctx, active)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if config.TwilioConfig == nil {
|
|
||||||
return nil, errors.ThrowNotFound(nil, "HANDLER-8nfow", "Errors.SMS.Twilio.NotFound")
|
|
||||||
}
|
|
||||||
token, err := crypto.Decrypt(config.TwilioConfig.Token, n.smsTokenCrypto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &twilio.TwilioConfig{
|
|
||||||
SID: config.TwilioConfig.SID,
|
|
||||||
Token: string(token),
|
|
||||||
SenderNumber: config.TwilioConfig.SenderNumber,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read iam filesystem provider config
|
|
||||||
func (n *Notification) getFileSystemProvider(ctx context.Context) (*fs.FSConfig, error) {
|
|
||||||
config, err := n.queries.NotificationProviderByIDAndType(ctx, authz.GetInstance(ctx).InstanceID(), domain.NotificationProviderTypeFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &fs.FSConfig{
|
|
||||||
Compact: config.Compact,
|
|
||||||
Path: n.fileSystemPath,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read iam log provider config
|
|
||||||
func (n *Notification) getLogProvider(ctx context.Context) (*log.LogConfig, error) {
|
|
||||||
config, err := n.queries.NotificationProviderByIDAndType(ctx, authz.GetInstance(ctx).InstanceID(), domain.NotificationProviderTypeLog)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &log.LogConfig{
|
|
||||||
Compact: config.Compact,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) getTranslatorWithOrgTexts(ctx context.Context, orgID, textType string) (*i18n.Translator, error) {
|
|
||||||
translator, err := i18n.NewTranslator(n.statikDir, n.queries.GetDefaultLanguage(ctx), "")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
allCustomTexts, err := n.queries.CustomTextListByTemplate(ctx, authz.GetInstance(ctx).InstanceID(), textType)
|
|
||||||
if err != nil {
|
|
||||||
return translator, nil
|
|
||||||
}
|
|
||||||
customTexts, err := n.queries.CustomTextListByTemplate(ctx, orgID, textType)
|
|
||||||
if err != nil {
|
|
||||||
return translator, nil
|
|
||||||
}
|
|
||||||
allCustomTexts.CustomTexts = append(allCustomTexts.CustomTexts, customTexts.CustomTexts...)
|
|
||||||
|
|
||||||
for _, text := range allCustomTexts.CustomTexts {
|
|
||||||
msg := i18n.Message{
|
|
||||||
ID: text.Template + "." + text.Key,
|
|
||||||
Text: text.Text,
|
|
||||||
}
|
|
||||||
translator.AddMessages(text.Language, msg)
|
|
||||||
}
|
|
||||||
return translator, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) getUserByID(userID, instanceID string) (*model.NotifyUser, error) {
|
|
||||||
return n.view.NotifyUserByID(userID, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) origin(ctx context.Context) (string, error) {
|
|
||||||
primary, err := query.NewInstanceDomainPrimarySearchQuery(true)
|
|
||||||
domains, err := n.queries.SearchInstanceDomains(ctx, &query.InstanceDomainSearchQueries{
|
|
||||||
Queries: []query.SearchQuery{primary},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if len(domains.Domains) < 1 {
|
|
||||||
return "", errors.ThrowInternal(nil, "NOTIF-Ef3r1", "Errors.Notification.NoDomain")
|
|
||||||
}
|
|
||||||
return http_utils.BuildHTTP(domains.Domains[0].Domain, n.externalPort, n.externalSecure), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *Notification) verifyLatestUser(ctx context.Context, user *model.NotifyUser) error {
|
|
||||||
events, err := n.getUserEvents(ctx, user.ID, user.InstanceID, user.Sequence)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, event := range events {
|
|
||||||
if err = user.AppendEvent(event); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,278 +0,0 @@
|
|||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/zitadel/logging"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
|
||||||
caos_errs "github.com/zitadel/zitadel/internal/errors"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
|
||||||
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
|
|
||||||
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/query"
|
|
||||||
es_sdk "github.com/zitadel/zitadel/internal/eventstore/v1/sdk"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
|
|
||||||
org_model "github.com/zitadel/zitadel/internal/org/model"
|
|
||||||
org_es_model "github.com/zitadel/zitadel/internal/org/repository/eventsourcing/model"
|
|
||||||
org_view "github.com/zitadel/zitadel/internal/org/repository/view"
|
|
||||||
query2 "github.com/zitadel/zitadel/internal/query"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/org"
|
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
userTable = "notification.notify_users"
|
|
||||||
)
|
|
||||||
|
|
||||||
type NotifyUser struct {
|
|
||||||
handler
|
|
||||||
subscription *v1.Subscription
|
|
||||||
queries *query2.Queries
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNotifyUser(
|
|
||||||
handler handler,
|
|
||||||
queries *query2.Queries,
|
|
||||||
) *NotifyUser {
|
|
||||||
h := &NotifyUser{
|
|
||||||
handler: handler,
|
|
||||||
queries: queries,
|
|
||||||
}
|
|
||||||
|
|
||||||
h.subscribe()
|
|
||||||
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *NotifyUser) subscribe() {
|
|
||||||
k.subscription = k.es.Subscribe(k.AggregateTypes()...)
|
|
||||||
go func() {
|
|
||||||
for event := range k.subscription.Events {
|
|
||||||
query.ReduceEvent(k, event)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NotifyUser) ViewModel() string {
|
|
||||||
return userTable
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NotifyUser) Subscription() *v1.Subscription {
|
|
||||||
return p.subscription
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_ *NotifyUser) AggregateTypes() []es_models.AggregateType {
|
|
||||||
return []es_models.AggregateType{user.AggregateType, org.AggregateType}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NotifyUser) CurrentSequence(instanceID string) (uint64, error) {
|
|
||||||
sequence, err := p.view.GetLatestNotifyUserSequence(instanceID)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return sequence.CurrentSequence, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NotifyUser) EventQuery() (*es_models.SearchQuery, error) {
|
|
||||||
sequences, err := p.view.GetLatestNotifyUserSequences()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
query := es_models.NewSearchQuery()
|
|
||||||
instances := make([]string, 0)
|
|
||||||
for _, sequence := range sequences {
|
|
||||||
for _, instance := range instances {
|
|
||||||
if sequence.InstanceID == instance {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
instances = append(instances, sequence.InstanceID)
|
|
||||||
query.AddQuery().
|
|
||||||
AggregateTypeFilter(p.AggregateTypes()...).
|
|
||||||
LatestSequenceFilter(sequence.CurrentSequence).
|
|
||||||
InstanceIDFilter(sequence.InstanceID)
|
|
||||||
}
|
|
||||||
return query.AddQuery().
|
|
||||||
AggregateTypeFilter(p.AggregateTypes()...).
|
|
||||||
LatestSequenceFilter(0).
|
|
||||||
ExcludedInstanceIDsFilter(instances...).
|
|
||||||
SearchQuery(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) Reduce(event *es_models.Event) (err error) {
|
|
||||||
switch event.AggregateType {
|
|
||||||
case user.AggregateType:
|
|
||||||
return u.ProcessUser(event)
|
|
||||||
case org.AggregateType:
|
|
||||||
return u.ProcessOrg(event)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) ProcessUser(event *es_models.Event) (err error) {
|
|
||||||
notifyUser := new(view_model.NotifyUser)
|
|
||||||
switch eventstore.EventType(event.Type) {
|
|
||||||
case user.UserV1AddedType,
|
|
||||||
user.UserV1RegisteredType,
|
|
||||||
user.HumanRegisteredType,
|
|
||||||
user.HumanAddedType,
|
|
||||||
user.MachineAddedEventType:
|
|
||||||
err = notifyUser.AppendEvent(event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = u.fillLoginNames(notifyUser)
|
|
||||||
case user.UserV1ProfileChangedType,
|
|
||||||
user.UserV1EmailChangedType,
|
|
||||||
user.UserV1EmailVerifiedType,
|
|
||||||
user.UserV1PhoneChangedType,
|
|
||||||
user.UserV1PhoneVerifiedType,
|
|
||||||
user.UserV1PhoneRemovedType,
|
|
||||||
user.HumanProfileChangedType,
|
|
||||||
user.HumanEmailChangedType,
|
|
||||||
user.HumanEmailVerifiedType,
|
|
||||||
user.HumanPhoneChangedType,
|
|
||||||
user.HumanPhoneVerifiedType,
|
|
||||||
user.HumanPhoneRemovedType,
|
|
||||||
user.MachineChangedEventType:
|
|
||||||
notifyUser, err = u.view.NotifyUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = notifyUser.AppendEvent(event)
|
|
||||||
case user.UserDomainClaimedType,
|
|
||||||
user.UserUserNameChangedType:
|
|
||||||
notifyUser, err = u.view.NotifyUserByID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = notifyUser.AppendEvent(event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = u.fillLoginNames(notifyUser)
|
|
||||||
case user.UserRemovedType:
|
|
||||||
return u.view.DeleteNotifyUser(event.AggregateID, event.InstanceID, event)
|
|
||||||
default:
|
|
||||||
return u.view.ProcessedNotifyUserSequence(event)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return u.view.PutNotifyUser(notifyUser, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) ProcessOrg(event *es_models.Event) (err error) {
|
|
||||||
switch eventstore.EventType(event.Type) {
|
|
||||||
case org.OrgDomainVerifiedEventType,
|
|
||||||
org.OrgDomainRemovedEventType,
|
|
||||||
org.DomainPolicyAddedEventType,
|
|
||||||
org.DomainPolicyChangedEventType,
|
|
||||||
org.DomainPolicyRemovedEventType:
|
|
||||||
return u.fillLoginNamesOnOrgUsers(event)
|
|
||||||
case org.OrgDomainPrimarySetEventType:
|
|
||||||
return u.fillPreferredLoginNamesOnOrgUsers(event)
|
|
||||||
default:
|
|
||||||
return u.view.ProcessedNotifyUserSequence(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) fillLoginNamesOnOrgUsers(event *es_models.Event) error {
|
|
||||||
userLoginMustBeDomain, _, domains, err := u.loginNameInformation(context.Background(), event.ResourceOwner, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
users, err := u.view.NotifyUsersByOrgID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, user := range users {
|
|
||||||
user.SetLoginNames(userLoginMustBeDomain, domains)
|
|
||||||
err := u.view.PutNotifyUser(user, event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return u.view.ProcessedNotifyUserSequence(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) fillPreferredLoginNamesOnOrgUsers(event *es_models.Event) error {
|
|
||||||
userLoginMustBeDomain, primaryDomain, _, err := u.loginNameInformation(context.Background(), event.ResourceOwner, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if !userLoginMustBeDomain {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
users, err := u.view.NotifyUsersByOrgID(event.AggregateID, event.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, user := range users {
|
|
||||||
user.PreferredLoginName = user.GenerateLoginName(primaryDomain, userLoginMustBeDomain)
|
|
||||||
err := u.view.PutNotifyUser(user, event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) fillLoginNames(user *view_model.NotifyUser) (err error) {
|
|
||||||
userLoginMustBeDomain, primaryDomain, domains, err := u.loginNameInformation(context.Background(), user.ResourceOwner, user.InstanceID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user.SetLoginNames(userLoginMustBeDomain, domains)
|
|
||||||
user.PreferredLoginName = user.GenerateLoginName(primaryDomain, userLoginMustBeDomain)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *NotifyUser) OnError(event *es_models.Event, err error) error {
|
|
||||||
logging.LogWithFields("SPOOL-9spwf", "id", event.AggregateID).WithError(err).Warn("something went wrong in notify user handler")
|
|
||||||
return spooler.HandleError(event, err, p.view.GetLatestNotifyUserFailedEvent, p.view.ProcessedNotifyUserFailedEvent, p.view.ProcessedNotifyUserSequence, p.errorCountUntilSkip)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) OnSuccess() error {
|
|
||||||
return spooler.HandleSuccess(u.view.UpdateNotifyUserSpoolerRunTimestamp)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) getOrgByID(ctx context.Context, orgID, instanceID string) (*org_model.Org, error) {
|
|
||||||
query, err := org_view.OrgByIDQuery(orgID, instanceID, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
esOrg := &org_es_model.Org{
|
|
||||||
ObjectRoot: es_models.ObjectRoot{
|
|
||||||
AggregateID: orgID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
err = es_sdk.Filter(ctx, u.Eventstore().FilterEvents, esOrg.AppendEvents, query)
|
|
||||||
if err != nil && !caos_errs.IsNotFound(err) {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if esOrg.Sequence == 0 {
|
|
||||||
return nil, caos_errs.ThrowNotFound(nil, "EVENT-kVLb2", "Errors.Org.NotFound")
|
|
||||||
}
|
|
||||||
|
|
||||||
return org_es_model.OrgToModel(esOrg), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *NotifyUser) loginNameInformation(ctx context.Context, orgID, instanceID string) (userLoginMustBeDomain bool, primaryDomain string, domains []*org_model.OrgDomain, err error) {
|
|
||||||
org, err := u.getOrgByID(ctx, orgID, instanceID)
|
|
||||||
if err != nil {
|
|
||||||
return false, "", nil, err
|
|
||||||
}
|
|
||||||
if org.DomainPolicy == nil {
|
|
||||||
policy, err := u.queries.DefaultDomainPolicy(authz.WithInstanceID(ctx, org.InstanceID))
|
|
||||||
if err != nil {
|
|
||||||
return false, "", nil, err
|
|
||||||
}
|
|
||||||
userLoginMustBeDomain = policy.UserLoginMustBeDomain
|
|
||||||
}
|
|
||||||
return userLoginMustBeDomain, org.GetPrimaryDomain().Domain, org.Domains, nil
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package eventsourcing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
|
|
||||||
es_spol "github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/repository/eventsourcing/spooler"
|
|
||||||
noti_view "github.com/zitadel/zitadel/internal/notification/repository/eventsourcing/view"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
|
||||||
Spooler spooler.SpoolerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type EsRepository struct {
|
|
||||||
spooler *es_spol.Spooler
|
|
||||||
}
|
|
||||||
|
|
||||||
func Start(conf Config,
|
|
||||||
dir http.FileSystem,
|
|
||||||
externalPort uint16,
|
|
||||||
externalSecure bool,
|
|
||||||
command *command.Commands,
|
|
||||||
queries *query.Queries,
|
|
||||||
dbClient *sql.DB,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath string,
|
|
||||||
userEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smtpEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smsEncryption crypto.EncryptionAlgorithm,
|
|
||||||
) (*EsRepository, error) {
|
|
||||||
es, err := v1.Start(dbClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
view, err := noti_view.StartView(dbClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, externalPort, externalSecure, dir, assetsPrefix, fileSystemPath, userEncryption, smtpEncryption, smsEncryption)
|
|
||||||
|
|
||||||
return &EsRepository{
|
|
||||||
spool,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (repo *EsRepository) Health() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package spooler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
es_locker "github.com/zitadel/zitadel/internal/eventstore/v1/locker"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
lockTable = "notification.locks"
|
|
||||||
)
|
|
||||||
|
|
||||||
type locker struct {
|
|
||||||
dbClient *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *locker) Renew(lockerID, viewModel, instanceID string, waitTime time.Duration) error {
|
|
||||||
return es_locker.Renew(l.dbClient, lockTable, lockerID, viewModel, instanceID, waitTime)
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package spooler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
v1 "github.com/zitadel/zitadel/internal/eventstore/v1"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/spooler"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/repository/eventsourcing/handler"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/repository/eventsourcing/view"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SpoolerConfig struct {
|
|
||||||
BulkLimit uint64
|
|
||||||
FailureCountUntilSkip uint64
|
|
||||||
ConcurrentWorkers int
|
|
||||||
Handlers handler.Configs
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartSpooler(c SpoolerConfig,
|
|
||||||
es v1.Eventstore,
|
|
||||||
view *view.View,
|
|
||||||
sql *sql.DB,
|
|
||||||
command *command.Commands,
|
|
||||||
queries *query.Queries,
|
|
||||||
externalPort uint16,
|
|
||||||
externalSecure bool,
|
|
||||||
dir http.FileSystem,
|
|
||||||
assetsPrefix,
|
|
||||||
fileSystemPath string,
|
|
||||||
userEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smtpEncryption crypto.EncryptionAlgorithm,
|
|
||||||
smsEncryption crypto.EncryptionAlgorithm,
|
|
||||||
) *spooler.Spooler {
|
|
||||||
spoolerConfig := spooler.Config{
|
|
||||||
Eventstore: es,
|
|
||||||
Locker: &locker{dbClient: sql},
|
|
||||||
ConcurrentWorkers: c.ConcurrentWorkers,
|
|
||||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, externalPort, externalSecure, dir, assetsPrefix, fileSystemPath, userEncryption, smtpEncryption, smsEncryption),
|
|
||||||
}
|
|
||||||
spool := spoolerConfig.New()
|
|
||||||
spool.Start()
|
|
||||||
return spool
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package view
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/zitadel/zitadel/internal/view/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
errTable = "notification.failed_events"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *View) saveFailedEvent(failedEvent *repository.FailedEvent) error {
|
|
||||||
return repository.SaveFailedEvent(v.Db, errTable, failedEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) latestFailedEvent(viewName, instanceID string, sequence uint64) (*repository.FailedEvent, error) {
|
|
||||||
return repository.LatestFailedEvent(v.Db, errTable, viewName, instanceID, sequence)
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package view
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
||||||
"github.com/zitadel/zitadel/internal/view/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
notificationTable = "notification.notifications"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotificationSequence(instanceID string) (*repository.CurrentSequence, error) {
|
|
||||||
return v.latestSequence(notificationTable, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotificationSequences() ([]*repository.CurrentSequence, error) {
|
|
||||||
return v.latestSequences(notificationTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) ProcessedNotificationSequence(event *models.Event) error {
|
|
||||||
return v.saveCurrentSequence(notificationTable, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) UpdateNotificationSpoolerRunTimestamp() error {
|
|
||||||
return v.updateSpoolerRunSequence(notificationTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotificationFailedEvent(sequence uint64, instanceID string) (*repository.FailedEvent, error) {
|
|
||||||
return v.latestFailedEvent(notificationTable, instanceID, sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) ProcessedNotificationFailedEvent(failedEvent *repository.FailedEvent) error {
|
|
||||||
return v.saveFailedEvent(failedEvent)
|
|
||||||
}
|
|
@ -1,61 +0,0 @@
|
|||||||
package view
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
||||||
"github.com/zitadel/zitadel/internal/user/repository/view"
|
|
||||||
"github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
"github.com/zitadel/zitadel/internal/view/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
notifyUserTable = "notification.notify_users"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *View) NotifyUserByID(userID, instanceID string) (*model.NotifyUser, error) {
|
|
||||||
return view.NotifyUserByID(v.Db, notifyUserTable, userID, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) PutNotifyUser(user *model.NotifyUser, event *models.Event) error {
|
|
||||||
err := view.PutNotifyUser(v.Db, notifyUserTable, user)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return v.ProcessedNotifyUserSequence(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) NotifyUsersByOrgID(orgID, instanceID string) ([]*model.NotifyUser, error) {
|
|
||||||
return view.NotifyUsersByOrgID(v.Db, notifyUserTable, orgID, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) DeleteNotifyUser(userID, instanceID string, event *models.Event) error {
|
|
||||||
err := view.DeleteNotifyUser(v.Db, notifyUserTable, userID, instanceID)
|
|
||||||
if err != nil && !errors.IsNotFound(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return v.ProcessedNotifyUserSequence(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotifyUserSequence(instanceID string) (*repository.CurrentSequence, error) {
|
|
||||||
return v.latestSequence(notifyUserTable, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotifyUserSequences() ([]*repository.CurrentSequence, error) {
|
|
||||||
return v.latestSequences(notifyUserTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) ProcessedNotifyUserSequence(event *models.Event) error {
|
|
||||||
return v.saveCurrentSequence(notifyUserTable, event)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) UpdateNotifyUserSpoolerRunTimestamp() error {
|
|
||||||
return v.updateSpoolerRunSequence(notifyUserTable)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) GetLatestNotifyUserFailedEvent(sequence uint64, instanceID string) (*repository.FailedEvent, error) {
|
|
||||||
return v.latestFailedEvent(notifyUserTable, instanceID, sequence)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) ProcessedNotifyUserFailedEvent(failedEvent *repository.FailedEvent) error {
|
|
||||||
return v.saveFailedEvent(failedEvent)
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package view
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
||||||
"github.com/zitadel/zitadel/internal/view/repository"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
sequencesTable = "notification.current_sequences"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (v *View) saveCurrentSequence(viewName string, event *models.Event) error {
|
|
||||||
return repository.SaveCurrentSequence(v.Db, sequencesTable, viewName, event.InstanceID, event.Sequence, event.CreationDate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) latestSequence(viewName, instanceID string) (*repository.CurrentSequence, error) {
|
|
||||||
return repository.LatestSequence(v.Db, sequencesTable, viewName, instanceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) latestSequences(viewName string) ([]*repository.CurrentSequence, error) {
|
|
||||||
return repository.LatestSequences(v.Db, sequencesTable, viewName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) updateSpoolerRunSequence(viewName string) error {
|
|
||||||
currentSequences, err := repository.LatestSequences(v.Db, sequencesTable, viewName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, currentSequence := range currentSequences {
|
|
||||||
if currentSequence.ViewName == "" {
|
|
||||||
currentSequence.ViewName = viewName
|
|
||||||
}
|
|
||||||
currentSequence.LastSuccessfulSpoolerRun = time.Now()
|
|
||||||
}
|
|
||||||
return repository.UpdateCurrentSequences(v.Db, sequencesTable, currentSequences)
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package view
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type View struct {
|
|
||||||
Db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartView(sqlClient *sql.DB) (*View, error) {
|
|
||||||
gorm, err := gorm.Open("postgres", sqlClient)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &View{
|
|
||||||
Db: gorm,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *View) Health() (err error) {
|
|
||||||
return v.Db.DB().Ping()
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
package repository
|
|
||||||
|
|
||||||
type Repository interface {
|
|
||||||
Health() error
|
|
||||||
}
|
|
@ -21,7 +21,7 @@ type TemplateData struct {
|
|||||||
Subject string
|
Subject string
|
||||||
Greeting string
|
Greeting string
|
||||||
Text string
|
Text string
|
||||||
Href string
|
URL string
|
||||||
ButtonText string
|
ButtonText string
|
||||||
PrimaryColor string
|
PrimaryColor string
|
||||||
BackgroundColor string
|
BackgroundColor string
|
||||||
|
@ -1,38 +1,17 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DomainClaimedData struct {
|
func (notify Notify) SendDomainClaimed(user *query.NotifyUser, origin, username string) error {
|
||||||
templates.TemplateData
|
|
||||||
URL string
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendDomainClaimed(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, username string, emailConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), colors *query.LabelPolicy, assetsPrefix string, origin string) error {
|
|
||||||
url := login.LoginLink(origin, user.ResourceOwner)
|
url := login.LoginLink(origin, user.ResourceOwner)
|
||||||
var args = mapNotifyUserToArgs(user)
|
args := make(map[string]interface{})
|
||||||
args["TempUsername"] = username
|
args["TempUsername"] = username
|
||||||
args["Domain"] = strings.Split(user.LastEmail, "@")[1]
|
args["Domain"] = strings.Split(user.LastEmail, "@")[1]
|
||||||
|
return notify(url, args, domain.DomainClaimedMessageType, true)
|
||||||
domainClaimedData := &DomainClaimedData{
|
|
||||||
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.DomainClaimedMessageType, user.PreferredLanguage, colors),
|
|
||||||
URL: url,
|
|
||||||
}
|
|
||||||
template, err := templates.GetParsedTemplate(mailhtml, domainClaimedData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return generateEmail(ctx, user, domainClaimedData.Subject, template, emailConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
}
|
}
|
||||||
|
@ -1,43 +1,14 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type EmailVerificationCodeData struct {
|
func (notify Notify) SendEmailVerificationCode(user *query.NotifyUser, origin, code string) error {
|
||||||
templates.TemplateData
|
url := login.MailVerificationLink(origin, user.ID, code, user.ResourceOwner)
|
||||||
URL string
|
args := make(map[string]interface{})
|
||||||
}
|
args["Code"] = code
|
||||||
|
return notify(url, args, domain.VerifyEmailMessageType, true)
|
||||||
func SendEmailVerificationCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.EmailCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
|
|
||||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := login.MailVerificationLink(origin, user.ID, codeString, user.ResourceOwner)
|
|
||||||
var args = mapNotifyUserToArgs(user)
|
|
||||||
args["Code"] = codeString
|
|
||||||
|
|
||||||
emailCodeData := &EmailVerificationCodeData{
|
|
||||||
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.VerifyEmailMessageType, user.PreferredLanguage, colors),
|
|
||||||
URL: url,
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return generateEmail(ctx, user, emailCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,14 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type InitCodeEmailData struct {
|
func (notify Notify) SendUserInitCode(user *query.NotifyUser, origin, code string) error {
|
||||||
templates.TemplateData
|
url := login.InitUserLink(origin, user.ID, code, user.ResourceOwner, user.PasswordSet)
|
||||||
URL string
|
args := make(map[string]interface{})
|
||||||
}
|
args["Code"] = code
|
||||||
|
return notify(url, args, domain.InitCodeMessageType, true)
|
||||||
type UrlData struct {
|
|
||||||
UserID string
|
|
||||||
Code string
|
|
||||||
PasswordSet bool
|
|
||||||
OrgID string
|
|
||||||
}
|
|
||||||
|
|
||||||
func SendUserInitCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.InitUserCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix, origin string) error {
|
|
||||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := login.InitUserLink(origin, user.ID, codeString, user.ResourceOwner, user.PasswordSet)
|
|
||||||
var args = mapNotifyUserToArgs(user)
|
|
||||||
args["Code"] = codeString
|
|
||||||
|
|
||||||
initCodeData := &InitCodeEmailData{
|
|
||||||
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.InitCodeMessageType, user.PreferredLanguage, colors),
|
|
||||||
URL: url,
|
|
||||||
}
|
|
||||||
template, err := templates.GetParsedTemplate(mailhtml, initCodeData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return generateEmail(ctx, user, initCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
}
|
}
|
||||||
|
73
internal/notification/types/notification.go
Normal file
73
internal/notification/types/notification.go
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/i18n"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
||||||
|
"github.com/zitadel/zitadel/internal/notification/templates"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Notify func(
|
||||||
|
url string,
|
||||||
|
args map[string]interface{},
|
||||||
|
messageType string,
|
||||||
|
allowUnverifiedNotificationChannel bool,
|
||||||
|
) error
|
||||||
|
|
||||||
|
func SendEmail(
|
||||||
|
ctx context.Context,
|
||||||
|
mailhtml string,
|
||||||
|
translator *i18n.Translator,
|
||||||
|
user *query.NotifyUser,
|
||||||
|
emailConfig func(ctx context.Context) (*smtp.EmailConfig, error),
|
||||||
|
getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error),
|
||||||
|
getLogProvider func(ctx context.Context) (*log.LogConfig, error),
|
||||||
|
colors *query.LabelPolicy,
|
||||||
|
assetsPrefix string,
|
||||||
|
) Notify {
|
||||||
|
return func(
|
||||||
|
url string,
|
||||||
|
args map[string]interface{},
|
||||||
|
messageType string,
|
||||||
|
allowUnverifiedNotificationChannel bool,
|
||||||
|
) error {
|
||||||
|
args = mapNotifyUserToArgs(user, args)
|
||||||
|
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
|
||||||
|
template, err := templates.GetParsedTemplate(mailhtml, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return generateEmail(ctx, user, data.Subject, template, emailConfig, getFileSystemProvider, getLogProvider, allowUnverifiedNotificationChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SendSMSTwilio(
|
||||||
|
ctx context.Context,
|
||||||
|
translator *i18n.Translator,
|
||||||
|
user *query.NotifyUser,
|
||||||
|
twilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error),
|
||||||
|
getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error),
|
||||||
|
getLogProvider func(ctx context.Context) (*log.LogConfig, error),
|
||||||
|
colors *query.LabelPolicy,
|
||||||
|
assetsPrefix string,
|
||||||
|
) Notify {
|
||||||
|
return func(
|
||||||
|
url string,
|
||||||
|
args map[string]interface{},
|
||||||
|
messageType string,
|
||||||
|
allowUnverifiedNotificationChannel bool,
|
||||||
|
) error {
|
||||||
|
args = mapNotifyUserToArgs(user, args)
|
||||||
|
data := GetTemplateData(translator, args, assetsPrefix, url, messageType, user.PreferredLanguage.String(), colors)
|
||||||
|
return generateSms(ctx, user, data.Text, twilioConfig, getFileSystemProvider, getLogProvider, allowUnverifiedNotificationChannel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func externalLink(origin string) string {
|
||||||
|
return origin + "/ui/login"
|
||||||
|
}
|
@ -1,51 +1,14 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PasswordCodeData struct {
|
func (notify Notify) SendPasswordCode(user *query.NotifyUser, origin, code string) error {
|
||||||
templates.TemplateData
|
url := login.InitPasswordLink(origin, user.ID, code, user.ResourceOwner)
|
||||||
FirstName string
|
args := make(map[string]interface{})
|
||||||
LastName string
|
args["Code"] = code
|
||||||
URL string
|
return notify(url, args, domain.PasswordResetMessageType, true)
|
||||||
}
|
|
||||||
|
|
||||||
func SendPasswordCode(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PasswordCode, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
|
|
||||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := login.InitPasswordLink(origin, user.ID, codeString, user.ResourceOwner)
|
|
||||||
var args = mapNotifyUserToArgs(user)
|
|
||||||
args["Code"] = codeString
|
|
||||||
|
|
||||||
passwordResetData := &PasswordCodeData{
|
|
||||||
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.PasswordResetMessageType, user.PreferredLanguage, colors),
|
|
||||||
FirstName: user.FirstName,
|
|
||||||
LastName: user.LastName,
|
|
||||||
URL: url,
|
|
||||||
}
|
|
||||||
template, err := templates.GetParsedTemplate(mailhtml, passwordResetData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if code.NotificationType == int32(domain.NotificationTypeSms) {
|
|
||||||
return generateSms(ctx, user, passwordResetData.Text, getTwilioConfig, getFileSystemProvider, getLogProvider, false)
|
|
||||||
}
|
|
||||||
return generateEmail(ctx, user, passwordResetData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PasswordlessRegistrationLinkData struct {
|
func (notify Notify) SendPasswordlessRegistrationLink(user *query.NotifyUser, origin, code, codeID string) error {
|
||||||
templates.TemplateData
|
url := domain.PasswordlessInitCodeLink(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, codeID, code)
|
||||||
URL string
|
return notify(url, nil, domain.PasswordlessRegistrationMessageType, true)
|
||||||
}
|
|
||||||
|
|
||||||
func SendPasswordlessRegistrationLink(ctx context.Context, mailhtml string, translator *i18n.Translator, user *view_model.NotifyUser, code *user.HumanPasswordlessInitCodeRequestedEvent, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm, colors *query.LabelPolicy, assetsPrefix string, origin string) error {
|
|
||||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
url := domain.PasswordlessInitCodeLink(origin+login.HandlerPrefix+login.EndpointPasswordlessRegistration, user.ID, user.ResourceOwner, code.ID, codeString)
|
|
||||||
var args = mapNotifyUserToArgs(user)
|
|
||||||
|
|
||||||
emailCodeData := &PasswordlessRegistrationLinkData{
|
|
||||||
TemplateData: GetTemplateData(translator, args, assetsPrefix, url, domain.PasswordlessRegistrationMessageType, user.PreferredLanguage, colors),
|
|
||||||
URL: url,
|
|
||||||
}
|
|
||||||
|
|
||||||
template, err := templates.GetParsedTemplate(mailhtml, emailCodeData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return generateEmail(ctx, user, emailCodeData.Subject, template, smtpConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
}
|
}
|
||||||
|
@ -1,38 +1,12 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/i18n"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
|
||||||
"github.com/zitadel/zitadel/internal/notification/templates"
|
|
||||||
es_model "github.com/zitadel/zitadel/internal/user/repository/eventsourcing/model"
|
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type PhoneVerificationCodeData struct {
|
func (notify Notify) SendPhoneVerificationCode(user *query.NotifyUser, origin, code string) error {
|
||||||
UserID string
|
args := make(map[string]interface{})
|
||||||
}
|
args["Code"] = code
|
||||||
|
return notify("", args, domain.VerifyPhoneMessageType, true)
|
||||||
func SendPhoneVerificationCode(ctx context.Context, translator *i18n.Translator, user *view_model.NotifyUser, code *es_model.PhoneCode, getTwilioConfig func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), alg crypto.EncryptionAlgorithm) error {
|
|
||||||
codeString, err := crypto.DecryptString(code.Code, alg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var args = mapNotifyUserToArgs(user)
|
|
||||||
args["Code"] = codeString
|
|
||||||
|
|
||||||
text := translator.Localize(fmt.Sprintf("%s.%s", domain.VerifyPhoneMessageType, domain.MessageText), args, user.PreferredLanguage)
|
|
||||||
|
|
||||||
codeData := &PhoneVerificationCodeData{UserID: user.ID}
|
|
||||||
template, err := templates.ParseTemplateText(text, codeData)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return generateSms(ctx, user, template, getTwilioConfig, getFileSystemProvider, getLogProvider, true)
|
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
func GetTemplateData(translator *i18n.Translator, translateArgs map[string]interface{}, assetsPrefix, href, msgType, lang string, policy *query.LabelPolicy) templates.TemplateData {
|
func GetTemplateData(translator *i18n.Translator, translateArgs map[string]interface{}, assetsPrefix, href, msgType, lang string, policy *query.LabelPolicy) templates.TemplateData {
|
||||||
templateData := templates.TemplateData{
|
templateData := templates.TemplateData{
|
||||||
Href: href,
|
URL: href,
|
||||||
PrimaryColor: templates.DefaultPrimaryColor,
|
PrimaryColor: templates.DefaultPrimaryColor,
|
||||||
BackgroundColor: templates.DefaultBackgroundColor,
|
BackgroundColor: templates.DefaultBackgroundColor,
|
||||||
FontColor: templates.DefaultFontColor,
|
FontColor: templates.DefaultFontColor,
|
||||||
|
@ -10,11 +10,10 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
"github.com/zitadel/zitadel/internal/notification/channels/smtp"
|
||||||
"github.com/zitadel/zitadel/internal/notification/messages"
|
"github.com/zitadel/zitadel/internal/notification/messages"
|
||||||
"github.com/zitadel/zitadel/internal/notification/senders"
|
"github.com/zitadel/zitadel/internal/notification/senders"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, content string, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastEmail bool) error {
|
func generateEmail(ctx context.Context, user *query.NotifyUser, subject, content string, smtpConfig func(ctx context.Context) (*smtp.EmailConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastEmail bool) error {
|
||||||
content = html.UnescapeString(content)
|
content = html.UnescapeString(content)
|
||||||
message := &messages.Email{
|
message := &messages.Email{
|
||||||
Recipients: []string{user.VerifiedEmail},
|
Recipients: []string{user.VerifiedEmail},
|
||||||
@ -36,20 +35,22 @@ func generateEmail(ctx context.Context, user *view_model.NotifyUser, subject, co
|
|||||||
return channelChain.HandleMessage(message)
|
return channelChain.HandleMessage(message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mapNotifyUserToArgs(user *view_model.NotifyUser) map[string]interface{} {
|
func mapNotifyUserToArgs(user *query.NotifyUser, args map[string]interface{}) map[string]interface{} {
|
||||||
return map[string]interface{}{
|
if args == nil {
|
||||||
"UserName": user.UserName,
|
args = make(map[string]interface{})
|
||||||
"FirstName": user.FirstName,
|
|
||||||
"LastName": user.LastName,
|
|
||||||
"NickName": user.NickName,
|
|
||||||
"DisplayName": user.DisplayName,
|
|
||||||
"LastEmail": user.LastEmail,
|
|
||||||
"VerifiedEmail": user.VerifiedEmail,
|
|
||||||
"LastPhone": user.LastPhone,
|
|
||||||
"VerifiedPhone": user.VerifiedPhone,
|
|
||||||
"PreferredLoginName": user.PreferredLoginName,
|
|
||||||
"LoginNames": user.LoginNames,
|
|
||||||
"ChangeDate": user.ChangeDate,
|
|
||||||
"CreationDate": user.CreationDate,
|
|
||||||
}
|
}
|
||||||
|
args["UserName"] = user.Username
|
||||||
|
args["FirstName"] = user.FirstName
|
||||||
|
args["LastName"] = user.LastName
|
||||||
|
args["NickName"] = user.NickName
|
||||||
|
args["DisplayName"] = user.DisplayName
|
||||||
|
args["LastEmail"] = user.LastEmail
|
||||||
|
args["VerifiedEmail"] = user.VerifiedEmail
|
||||||
|
args["LastPhone"] = user.LastPhone
|
||||||
|
args["VerifiedPhone"] = user.VerifiedPhone
|
||||||
|
args["PreferredLoginName"] = user.PreferredLoginName
|
||||||
|
args["LoginNames"] = user.LoginNames
|
||||||
|
args["ChangeDate"] = user.ChangeDate
|
||||||
|
args["CreationDate"] = user.CreationDate
|
||||||
|
return args
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,22 @@ package types
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
caos_errors "github.com/zitadel/zitadel/internal/errors"
|
caos_errors "github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
"github.com/zitadel/zitadel/internal/notification/channels/fs"
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
"github.com/zitadel/zitadel/internal/notification/channels/log"
|
||||||
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
"github.com/zitadel/zitadel/internal/notification/channels/twilio"
|
||||||
"github.com/zitadel/zitadel/internal/notification/messages"
|
"github.com/zitadel/zitadel/internal/notification/messages"
|
||||||
"github.com/zitadel/zitadel/internal/notification/senders"
|
"github.com/zitadel/zitadel/internal/notification/senders"
|
||||||
view_model "github.com/zitadel/zitadel/internal/user/repository/view/model"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateSms(ctx context.Context, user *view_model.NotifyUser, content string, getTwilioProvider func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastPhone bool) error {
|
func generateSms(ctx context.Context, user *query.NotifyUser, content string, getTwilioProvider func(ctx context.Context) (*twilio.TwilioConfig, error), getFileSystemProvider func(ctx context.Context) (*fs.FSConfig, error), getLogProvider func(ctx context.Context) (*log.LogConfig, error), lastPhone bool) error {
|
||||||
number := ""
|
number := ""
|
||||||
twilio, err := getTwilioProvider(ctx)
|
twilioConfig, err := getTwilioProvider(ctx)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
number = twilio.SenderNumber
|
number = twilioConfig.SenderNumber
|
||||||
}
|
}
|
||||||
message := &messages.SMS{
|
message := &messages.SMS{
|
||||||
SenderPhoneNumber: number,
|
SenderPhoneNumber: number,
|
||||||
@ -27,7 +29,8 @@ func generateSms(ctx context.Context, user *view_model.NotifyUser, content strin
|
|||||||
message.RecipientPhoneNumber = user.LastPhone
|
message.RecipientPhoneNumber = user.LastPhone
|
||||||
}
|
}
|
||||||
|
|
||||||
channelChain, err := senders.SMSChannels(ctx, twilio, getFileSystemProvider, getLogProvider)
|
channelChain, err := senders.SMSChannels(ctx, twilioConfig, getFileSystemProvider, getLogProvider)
|
||||||
|
logging.OnError(err).Error("could not create sms channel")
|
||||||
|
|
||||||
if channelChain.Len() == 0 {
|
if channelChain.Len() == 0 {
|
||||||
return caos_errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
|
return caos_errors.ThrowPreconditionFailed(nil, "PHONE-w8nfow", "Errors.Notification.Channels.NotPresent")
|
||||||
|
@ -20,18 +20,18 @@ var (
|
|||||||
", members.user_id" +
|
", members.user_id" +
|
||||||
", members.roles" +
|
", members.roles" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_machines.name" +
|
", projections.users2_machines.name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", COUNT(*) OVER () " +
|
", COUNT(*) OVER () " +
|
||||||
"FROM projections.instance_members as members " +
|
"FROM projections.instance_members as members " +
|
||||||
"LEFT JOIN projections.users_humans " +
|
"LEFT JOIN projections.users2_humans " +
|
||||||
"ON members.user_id = projections.users_humans.user_id " +
|
"ON members.user_id = projections.users2_humans.user_id " +
|
||||||
"LEFT JOIN projections.users_machines " +
|
"LEFT JOIN projections.users2_machines " +
|
||||||
"ON members.user_id = projections.users_machines.user_id " +
|
"ON members.user_id = projections.users2_machines.user_id " +
|
||||||
"LEFT JOIN projections.login_names " +
|
"LEFT JOIN projections.login_names " +
|
||||||
"ON members.user_id = projections.login_names.user_id " +
|
"ON members.user_id = projections.login_names.user_id " +
|
||||||
"WHERE projections.login_names.is_primary = $1")
|
"WHERE projections.login_names.is_primary = $1")
|
||||||
|
@ -20,18 +20,18 @@ var (
|
|||||||
", members.user_id" +
|
", members.user_id" +
|
||||||
", members.roles" +
|
", members.roles" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_machines.name" +
|
", projections.users2_machines.name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", COUNT(*) OVER () " +
|
", COUNT(*) OVER () " +
|
||||||
"FROM projections.org_members as members " +
|
"FROM projections.org_members as members " +
|
||||||
"LEFT JOIN projections.users_humans " +
|
"LEFT JOIN projections.users2_humans " +
|
||||||
"ON members.user_id = projections.users_humans.user_id " +
|
"ON members.user_id = projections.users2_humans.user_id " +
|
||||||
"LEFT JOIN projections.users_machines " +
|
"LEFT JOIN projections.users2_machines " +
|
||||||
"ON members.user_id = projections.users_machines.user_id " +
|
"ON members.user_id = projections.users2_machines.user_id " +
|
||||||
"LEFT JOIN projections.login_names " +
|
"LEFT JOIN projections.login_names " +
|
||||||
"ON members.user_id = projections.login_names.user_id " +
|
"ON members.user_id = projections.login_names.user_id " +
|
||||||
"WHERE projections.login_names.is_primary = $1")
|
"WHERE projections.login_names.is_primary = $1")
|
||||||
|
@ -20,18 +20,18 @@ var (
|
|||||||
", members.user_id" +
|
", members.user_id" +
|
||||||
", members.roles" +
|
", members.roles" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_machines.name" +
|
", projections.users2_machines.name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", COUNT(*) OVER () " +
|
", COUNT(*) OVER () " +
|
||||||
"FROM projections.project_grant_members as members " +
|
"FROM projections.project_grant_members as members " +
|
||||||
"LEFT JOIN projections.users_humans " +
|
"LEFT JOIN projections.users2_humans " +
|
||||||
"ON members.user_id = projections.users_humans.user_id " +
|
"ON members.user_id = projections.users2_humans.user_id " +
|
||||||
"LEFT JOIN projections.users_machines " +
|
"LEFT JOIN projections.users2_machines " +
|
||||||
"ON members.user_id = projections.users_machines.user_id " +
|
"ON members.user_id = projections.users2_machines.user_id " +
|
||||||
"LEFT JOIN projections.login_names " +
|
"LEFT JOIN projections.login_names " +
|
||||||
"ON members.user_id = projections.login_names.user_id " +
|
"ON members.user_id = projections.login_names.user_id " +
|
||||||
"LEFT JOIN projections.project_grants " +
|
"LEFT JOIN projections.project_grants " +
|
||||||
|
@ -20,18 +20,18 @@ var (
|
|||||||
", members.user_id" +
|
", members.user_id" +
|
||||||
", members.roles" +
|
", members.roles" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_machines.name" +
|
", projections.users2_machines.name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", COUNT(*) OVER () " +
|
", COUNT(*) OVER () " +
|
||||||
"FROM projections.project_members as members " +
|
"FROM projections.project_members as members " +
|
||||||
"LEFT JOIN projections.users_humans " +
|
"LEFT JOIN projections.users2_humans " +
|
||||||
"ON members.user_id = projections.users_humans.user_id " +
|
"ON members.user_id = projections.users2_humans.user_id " +
|
||||||
"LEFT JOIN projections.users_machines " +
|
"LEFT JOIN projections.users2_machines " +
|
||||||
"ON members.user_id = projections.users_machines.user_id " +
|
"ON members.user_id = projections.users2_machines.user_id " +
|
||||||
"LEFT JOIN projections.login_names " +
|
"LEFT JOIN projections.login_names " +
|
||||||
"ON members.user_id = projections.login_names.user_id " +
|
"ON members.user_id = projections.login_names.user_id " +
|
||||||
"WHERE projections.login_names.is_primary = $1")
|
"WHERE projections.login_names.is_primary = $1")
|
||||||
|
@ -358,6 +358,32 @@ func (p *labelPolicyProjection) reduceActivated(event eventstore.Event) (*handle
|
|||||||
handler.NewCol(LabelPolicyDarkLogoURLCol, nil),
|
handler.NewCol(LabelPolicyDarkLogoURLCol, nil),
|
||||||
handler.NewCol(LabelPolicyDarkIconURLCol, nil),
|
handler.NewCol(LabelPolicyDarkIconURLCol, nil),
|
||||||
},
|
},
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(LabelPolicyChangeDateCol, nil),
|
||||||
|
handler.NewCol(LabelPolicySequenceCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyStateCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyCreationDateCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyResourceOwnerCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyInstanceIDCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyIDCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyIsDefaultCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyHideLoginNameSuffixCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyFontURLCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyWatermarkDisabledCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyShouldErrorPopupCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightPrimaryColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightWarnColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightBackgroundColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightFontColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightLogoURLCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyLightIconURLCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkPrimaryColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkWarnColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkBackgroundColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkFontColorCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkLogoURLCol, nil),
|
||||||
|
handler.NewCol(LabelPolicyDarkIconURLCol, nil),
|
||||||
|
},
|
||||||
[]handler.Condition{
|
[]handler.Condition{
|
||||||
handler.NewCond(LabelPolicyIDCol, event.Aggregate().ID),
|
handler.NewCond(LabelPolicyIDCol, event.Aggregate().ID),
|
||||||
handler.NewCond(LabelPolicyStateCol, domain.LabelPolicyStatePreview),
|
handler.NewCond(LabelPolicyStateCol, domain.LabelPolicyStatePreview),
|
||||||
|
@ -18,6 +18,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
projectionConfig crdb.StatementHandlerConfig
|
||||||
OrgProjection *orgProjection
|
OrgProjection *orgProjection
|
||||||
ActionProjection *actionProjection
|
ActionProjection *actionProjection
|
||||||
FlowProjection *flowProjection
|
FlowProjection *flowProjection
|
||||||
@ -58,10 +59,11 @@ var (
|
|||||||
OIDCSettingsProjection *oidcSettingsProjection
|
OIDCSettingsProjection *oidcSettingsProjection
|
||||||
DebugNotificationProviderProjection *debugNotificationProviderProjection
|
DebugNotificationProviderProjection *debugNotificationProviderProjection
|
||||||
KeyProjection *keyProjection
|
KeyProjection *keyProjection
|
||||||
|
NotificationsProjection interface{}
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
|
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
|
||||||
projectionConfig := crdb.StatementHandlerConfig{
|
projectionConfig = crdb.StatementHandlerConfig{
|
||||||
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
|
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
|
||||||
HandlerConfig: handler.HandlerConfig{
|
HandlerConfig: handler.HandlerConfig{
|
||||||
Eventstore: es,
|
Eventstore: es,
|
||||||
@ -120,6 +122,11 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ApplyCustomConfig(customConfig CustomConfig) crdb.StatementHandlerConfig {
|
||||||
|
return applyCustomConfig(projectionConfig, customConfig)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomConfig) crdb.StatementHandlerConfig {
|
func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomConfig) crdb.StatementHandlerConfig {
|
||||||
if customConfig.BulkLimit != nil {
|
if customConfig.BulkLimit != nil {
|
||||||
config.BulkLimit = *customConfig.BulkLimit
|
config.BulkLimit = *customConfig.BulkLimit
|
||||||
|
@ -17,9 +17,10 @@ type userProjection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserTable = "projections.users"
|
UserTable = "projections.users2"
|
||||||
UserHumanTable = UserTable + "_" + UserHumanSuffix
|
UserHumanTable = UserTable + "_" + UserHumanSuffix
|
||||||
UserMachineTable = UserTable + "_" + UserMachineSuffix
|
UserMachineTable = UserTable + "_" + UserMachineSuffix
|
||||||
|
UserNotifyTable = UserTable + "_" + UserNotifySuffix
|
||||||
|
|
||||||
UserIDCol = "id"
|
UserIDCol = "id"
|
||||||
UserCreationDateCol = "creation_date"
|
UserCreationDateCol = "creation_date"
|
||||||
@ -58,6 +59,16 @@ const (
|
|||||||
MachineUserInstanceIDCol = "instance_id"
|
MachineUserInstanceIDCol = "instance_id"
|
||||||
MachineNameCol = "name"
|
MachineNameCol = "name"
|
||||||
MachineDescriptionCol = "description"
|
MachineDescriptionCol = "description"
|
||||||
|
|
||||||
|
// notify
|
||||||
|
UserNotifySuffix = "notifications"
|
||||||
|
NotifyUserIDCol = "user_id"
|
||||||
|
NotifyInstanceIDCol = "instance_id"
|
||||||
|
NotifyLastEmailCol = "last_email"
|
||||||
|
NotifyVerifiedEmailCol = "verified_email"
|
||||||
|
NotifyLastPhoneCol = "last_phone"
|
||||||
|
NotifyVerifiedPhoneCol = "verified_phone"
|
||||||
|
NotifyPasswordSetCol = "password_set"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig) *userProjection {
|
func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig) *userProjection {
|
||||||
@ -110,6 +121,19 @@ func newUserProjection(ctx context.Context, config crdb.StatementHandlerConfig)
|
|||||||
UserMachineSuffix,
|
UserMachineSuffix,
|
||||||
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_machine_ref_user")),
|
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_machine_ref_user")),
|
||||||
),
|
),
|
||||||
|
crdb.NewSuffixedTable([]*crdb.Column{
|
||||||
|
crdb.NewColumn(NotifyUserIDCol, crdb.ColumnTypeText),
|
||||||
|
crdb.NewColumn(NotifyInstanceIDCol, crdb.ColumnTypeText),
|
||||||
|
crdb.NewColumn(NotifyLastEmailCol, crdb.ColumnTypeText, crdb.Nullable()),
|
||||||
|
crdb.NewColumn(NotifyVerifiedEmailCol, crdb.ColumnTypeText, crdb.Nullable()),
|
||||||
|
crdb.NewColumn(NotifyLastPhoneCol, crdb.ColumnTypeText, crdb.Nullable()),
|
||||||
|
crdb.NewColumn(NotifyVerifiedPhoneCol, crdb.ColumnTypeText, crdb.Nullable()),
|
||||||
|
crdb.NewColumn(NotifyPasswordSetCol, crdb.ColumnTypeBool, crdb.Default(false)),
|
||||||
|
},
|
||||||
|
crdb.NewPrimaryKey(NotifyUserIDCol, NotifyInstanceIDCol),
|
||||||
|
UserNotifySuffix,
|
||||||
|
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys("fk_notify_ref_user")),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||||
return p
|
return p
|
||||||
@ -240,6 +264,10 @@ func (p *userProjection) reducers() []handler.AggregateReducer {
|
|||||||
Event: user.MachineChangedEventType,
|
Event: user.MachineChangedEventType,
|
||||||
Reduce: p.reduceMachineChanged,
|
Reduce: p.reduceMachineChanged,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanPasswordChangedType,
|
||||||
|
Reduce: p.reduceHumanPasswordChanged,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -280,6 +308,16 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddCreateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
|
||||||
|
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
|
||||||
|
handler.NewCol(NotifyPasswordSetCol, e.Secret != nil),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,6 +356,16 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddCreateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
|
||||||
|
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
|
||||||
|
handler.NewCol(NotifyPasswordSetCol, e.Secret != nil),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -552,6 +600,16 @@ func (p *userProjection) reduceHumanPhoneChanged(event eventstore.Event) (*handl
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddUpdateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -584,6 +642,17 @@ func (p *userProjection) reduceHumanPhoneRemoved(event eventstore.Event) (*handl
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddUpdateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyLastPhoneCol, nil),
|
||||||
|
handler.NewCol(NotifyVerifiedPhoneCol, nil),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -615,6 +684,23 @@ func (p *userProjection) reduceHumanPhoneVerified(event eventstore.Event) (*hand
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddCopyStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, nil),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, nil),
|
||||||
|
handler.NewCol(NotifyLastPhoneCol, nil),
|
||||||
|
},
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, nil),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, nil),
|
||||||
|
handler.NewCol(NotifyVerifiedPhoneCol, nil),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -647,6 +733,16 @@ func (p *userProjection) reduceHumanEmailChanged(event eventstore.Event) (*handl
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddUpdateStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyLastEmailCol, &sql.NullString{String: e.EmailAddress, Valid: e.EmailAddress != ""}),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -678,6 +774,23 @@ func (p *userProjection) reduceHumanEmailVerified(event eventstore.Event) (*hand
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(UserHumanSuffix),
|
crdb.WithTableSuffix(UserHumanSuffix),
|
||||||
),
|
),
|
||||||
|
crdb.AddCopyStatement(
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, nil),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, nil),
|
||||||
|
handler.NewCol(NotifyLastEmailCol, nil),
|
||||||
|
},
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyUserIDCol, nil),
|
||||||
|
handler.NewCol(NotifyInstanceIDCol, nil),
|
||||||
|
handler.NewCol(NotifyVerifiedEmailCol, nil),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
),
|
||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -743,6 +856,25 @@ func (p *userProjection) reduceHumanAvatarRemoved(event eventstore.Event) (*hand
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *userProjection) reduceHumanPasswordChanged(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanPasswordChangedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-jqXUY", "reduce.wrong.event.type %s", user.HumanPasswordChangedType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return crdb.NewUpdateStatement(
|
||||||
|
e,
|
||||||
|
[]handler.Column{
|
||||||
|
handler.NewCol(NotifyPasswordSetCol, true),
|
||||||
|
},
|
||||||
|
[]handler.Condition{
|
||||||
|
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
|
||||||
|
handler.NewCond(NotifyInstanceIDCol, e.Aggregate().InstanceID),
|
||||||
|
},
|
||||||
|
crdb.WithTableSuffix(UserNotifySuffix),
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (p *userProjection) reduceMachineAdded(event eventstore.Event) (*handler.Statement, error) {
|
func (p *userProjection) reduceMachineAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
e, ok := event.(*user.MachineAddedEvent)
|
e, ok := event.(*user.MachineAddedEvent)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -50,7 +50,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -64,7 +64,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -78,6 +78,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -110,7 +120,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -124,7 +134,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -138,6 +148,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -165,7 +185,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -179,7 +199,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -193,6 +213,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{},
|
&sql.NullString{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "", Valid: false},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -225,7 +255,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -239,7 +269,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -253,6 +283,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -285,7 +325,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -299,7 +339,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -313,6 +353,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -340,7 +390,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -354,7 +404,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
expectedStmt: "INSERT INTO projections.users2_humans (user_id, instance_id, first_name, last_name, nick_name, display_name, preferred_language, gender, email, phone) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -368,6 +418,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
&sql.NullString{},
|
&sql.NullString{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "INSERT INTO projections.users2_notifications (user_id, instance_id, last_email, last_phone, password_set) VALUES ($1, $2, $3, $4, $5)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
"email@zitadel.com",
|
||||||
|
&sql.NullString{String: "", Valid: false},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -390,7 +450,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2 SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
domain.UserStateInitial,
|
domain.UserStateInitial,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -419,7 +479,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2 SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
domain.UserStateInitial,
|
domain.UserStateInitial,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -448,7 +508,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2 SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
domain.UserStateActive,
|
domain.UserStateActive,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -477,7 +537,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2 SET (state) = ($1) WHERE (id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
domain.UserStateActive,
|
domain.UserStateActive,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -506,7 +566,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
domain.UserStateLocked,
|
domain.UserStateLocked,
|
||||||
@ -537,7 +597,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
domain.UserStateActive,
|
domain.UserStateActive,
|
||||||
@ -568,7 +628,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
domain.UserStateInactive,
|
domain.UserStateInactive,
|
||||||
@ -599,7 +659,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, state, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
domain.UserStateActive,
|
domain.UserStateActive,
|
||||||
@ -630,7 +690,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "DELETE FROM projections.users WHERE (id = $1) AND (instance_id = $2)",
|
expectedStmt: "DELETE FROM projections.users2 WHERE (id = $1) AND (instance_id = $2)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -660,7 +720,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, username, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
"username",
|
"username",
|
||||||
@ -698,7 +758,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -707,7 +767,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
expectedStmt: "UPDATE projections.users2_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"first-name",
|
"first-name",
|
||||||
"last-name",
|
"last-name",
|
||||||
@ -748,7 +808,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -757,7 +817,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
expectedStmt: "UPDATE projections.users2_humans SET (first_name, last_name, nick_name, display_name, preferred_language, gender) = ($1, $2, $3, $4, $5, $6) WHERE (user_id = $7) AND (instance_id = $8)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"first-name",
|
"first-name",
|
||||||
"last-name",
|
"last-name",
|
||||||
@ -793,7 +853,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -802,7 +862,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"+41 00 000 00 00",
|
"+41 00 000 00 00",
|
||||||
false,
|
false,
|
||||||
@ -810,6 +870,14 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_phone) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -834,7 +902,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -843,7 +911,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"+41 00 000 00 00",
|
"+41 00 000 00 00",
|
||||||
false,
|
false,
|
||||||
@ -851,6 +919,14 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_phone) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -873,7 +949,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -882,7 +958,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -912,7 +997,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -921,7 +1006,16 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_phone, verified_phone) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
@ -951,7 +1045,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -960,13 +1054,20 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (is_phone_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (is_phone_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPSERT INTO projections.users2_notifications (user_id, instance_id, verified_phone) SELECT user_id, instance_id, last_phone FROM projections.users2_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -989,7 +1090,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -998,13 +1099,20 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (is_phone_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (is_phone_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPSERT INTO projections.users2_notifications (user_id, instance_id, verified_phone) SELECT user_id, instance_id, last_phone FROM projections.users2_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1029,7 +1137,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1038,7 +1146,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"email@zitadel.com",
|
"email@zitadel.com",
|
||||||
false,
|
false,
|
||||||
@ -1046,6 +1154,14 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_email) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
&sql.NullString{String: "email@zitadel.com", Valid: true},
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1070,7 +1186,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1079,7 +1195,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"email@zitadel.com",
|
"email@zitadel.com",
|
||||||
false,
|
false,
|
||||||
@ -1087,6 +1203,14 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPDATE projections.users2_notifications SET (last_email) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
&sql.NullString{String: "email@zitadel.com", Valid: true},
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1109,7 +1233,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1118,13 +1242,20 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (is_email_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (is_email_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPSERT INTO projections.users2_notifications (user_id, instance_id, verified_email) SELECT user_id, instance_id, last_email FROM projections.users2_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1147,7 +1278,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1156,13 +1287,20 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (is_email_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (is_email_verified) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
true,
|
true,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
expectedStmt: "UPSERT INTO projections.users2_notifications (user_id, instance_id, verified_email) SELECT user_id, instance_id, last_email FROM projections.users2_notifications AS copy_table WHERE copy_table.user_id = $1 AND copy_table.instance_id = $2",
|
||||||
|
expectedArgs: []interface{}{
|
||||||
|
"agg-id",
|
||||||
|
"instance-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1187,7 +1325,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1196,7 +1334,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (avatar_key) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"users/agg-id/avatar",
|
"users/agg-id/avatar",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -1225,7 +1363,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1234,7 +1372,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_humans SET (avatar_key) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_humans SET (avatar_key) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
nil,
|
nil,
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -1266,7 +1404,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -1280,7 +1418,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
expectedStmt: "INSERT INTO projections.users2_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -1314,7 +1452,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
expectedStmt: "INSERT INTO projections.users2 (id, creation_date, change_date, resource_owner, instance_id, state, sequence, username, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
anyArg{},
|
anyArg{},
|
||||||
@ -1328,7 +1466,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "INSERT INTO projections.users_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
expectedStmt: "INSERT INTO projections.users2_machines (user_id, instance_id, name, description) VALUES ($1, $2, $3, $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"agg-id",
|
"agg-id",
|
||||||
"instance-id",
|
"instance-id",
|
||||||
@ -1361,7 +1499,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1370,7 +1508,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2_machines SET (name, description) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"machine-name",
|
"machine-name",
|
||||||
"description",
|
"description",
|
||||||
@ -1402,7 +1540,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1411,7 +1549,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_machines SET (name) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_machines SET (name) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"machine-name",
|
"machine-name",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
@ -1442,7 +1580,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
executer: &testExecuter{
|
executer: &testExecuter{
|
||||||
executions: []execution{
|
executions: []execution{
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
expectedStmt: "UPDATE projections.users2 SET (change_date, sequence) = ($1, $2) WHERE (id = $3) AND (instance_id = $4)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
anyArg{},
|
anyArg{},
|
||||||
uint64(15),
|
uint64(15),
|
||||||
@ -1451,7 +1589,7 @@ func TestUserProjection_reduces(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
expectedStmt: "UPDATE projections.users_machines SET (description) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
expectedStmt: "UPDATE projections.users2_machines SET (description) = ($1) WHERE (user_id = $2) AND (instance_id = $3)",
|
||||||
expectedArgs: []interface{}{
|
expectedArgs: []interface{}{
|
||||||
"description",
|
"description",
|
||||||
"agg-id",
|
"agg-id",
|
||||||
|
@ -11,9 +11,7 @@ import (
|
|||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/query/projection"
|
"github.com/zitadel/zitadel/internal/query/projection"
|
||||||
)
|
)
|
||||||
@ -92,6 +90,31 @@ type Machine struct {
|
|||||||
Description string
|
Description string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotifyUser struct {
|
||||||
|
ID string
|
||||||
|
CreationDate time.Time
|
||||||
|
ChangeDate time.Time
|
||||||
|
ResourceOwner string
|
||||||
|
Sequence uint64
|
||||||
|
State domain.UserState
|
||||||
|
Type domain.UserType
|
||||||
|
Username string
|
||||||
|
LoginNames []string
|
||||||
|
PreferredLoginName string
|
||||||
|
FirstName string
|
||||||
|
LastName string
|
||||||
|
NickName string
|
||||||
|
DisplayName string
|
||||||
|
AvatarKey string
|
||||||
|
PreferredLanguage language.Tag
|
||||||
|
Gender domain.Gender
|
||||||
|
LastEmail string
|
||||||
|
VerifiedEmail string
|
||||||
|
LastPhone string
|
||||||
|
VerifiedPhone string
|
||||||
|
PasswordSet bool
|
||||||
|
}
|
||||||
|
|
||||||
type UserSearchQueries struct {
|
type UserSearchQueries struct {
|
||||||
SearchRequest
|
SearchRequest
|
||||||
Queries []SearchQuery
|
Queries []SearchQuery
|
||||||
@ -237,6 +260,38 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
notifyTable = table{
|
||||||
|
name: projection.UserNotifyTable,
|
||||||
|
}
|
||||||
|
NotifyUserIDCol = Column{
|
||||||
|
name: projection.NotifyUserIDCol,
|
||||||
|
table: notifyTable,
|
||||||
|
}
|
||||||
|
NotifyEmailCol = Column{
|
||||||
|
name: projection.NotifyLastEmailCol,
|
||||||
|
table: notifyTable,
|
||||||
|
isOrderByLower: true,
|
||||||
|
}
|
||||||
|
NotifyVerifiedEmailCol = Column{
|
||||||
|
name: projection.NotifyVerifiedEmailCol,
|
||||||
|
table: notifyTable,
|
||||||
|
isOrderByLower: true,
|
||||||
|
}
|
||||||
|
NotifyPhoneCol = Column{
|
||||||
|
name: projection.NotifyLastPhoneCol,
|
||||||
|
table: notifyTable,
|
||||||
|
}
|
||||||
|
NotifyVerifiedPhoneCol = Column{
|
||||||
|
name: projection.NotifyVerifiedPhoneCol,
|
||||||
|
table: notifyTable,
|
||||||
|
}
|
||||||
|
NotifyPasswordSetCol = Column{
|
||||||
|
name: projection.NotifyPasswordSetCol,
|
||||||
|
table: notifyTable,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*User, error) {
|
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*User, error) {
|
||||||
if shouldTriggered {
|
if shouldTriggered {
|
||||||
projection.UserProjection.TriggerBulk(ctx)
|
projection.UserProjection.TriggerBulk(ctx)
|
||||||
@ -327,6 +382,28 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...S
|
|||||||
return scan(row)
|
return scan(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GeNotifyUser(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (*NotifyUser, error) {
|
||||||
|
if shouldTriggered {
|
||||||
|
projection.UserProjection.TriggerBulk(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
instanceID := authz.GetInstance(ctx).InstanceID()
|
||||||
|
query, scan := prepareNotifyUserQuery(instanceID)
|
||||||
|
for _, q := range queries {
|
||||||
|
query = q.toQuery(query)
|
||||||
|
}
|
||||||
|
stmt, args, err := query.Where(sq.Eq{
|
||||||
|
UserIDCol.identifier(): userID,
|
||||||
|
UserInstanceIDCol.identifier(): instanceID,
|
||||||
|
}).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
|
||||||
|
}
|
||||||
|
|
||||||
|
row := q.client.QueryRowContext(ctx, stmt, args...)
|
||||||
|
return scan(row)
|
||||||
|
}
|
||||||
|
|
||||||
func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (*Users, error) {
|
func (q *Queries) SearchUsers(ctx context.Context, queries *UserSearchQueries) (*Users, error) {
|
||||||
query, scan := prepareUsersQuery()
|
query, scan := prepareUsersQuery()
|
||||||
stmt, args, err := queries.toQuery(query).
|
stmt, args, err := queries.toQuery(query).
|
||||||
@ -748,6 +825,143 @@ func preparePhoneQuery() (sq.SelectBuilder, func(*sql.Row) (*Phone, error)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareNotifyUserQuery(instanceID string) (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||||
|
loginNamesQuery, loginNamesArgs, err := sq.Select(
|
||||||
|
userLoginNamesUserIDCol.identifier(),
|
||||||
|
"ARRAY_AGG("+userLoginNamesNameCol.identifier()+") as "+userLoginNamesListCol.name).
|
||||||
|
From(userLoginNamesTable.identifier()).
|
||||||
|
GroupBy(userLoginNamesUserIDCol.identifier()).
|
||||||
|
Where(sq.Eq{
|
||||||
|
userLoginNamesInstanceIDCol.identifier(): instanceID,
|
||||||
|
}).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return sq.SelectBuilder{}, nil
|
||||||
|
}
|
||||||
|
preferredLoginNameQuery, preferredLoginNameArgs, err := sq.Select(
|
||||||
|
userPreferredLoginNameUserIDCol.identifier(),
|
||||||
|
userPreferredLoginNameCol.identifier()).
|
||||||
|
From(userPreferredLoginNameTable.identifier()).
|
||||||
|
Where(sq.Eq{
|
||||||
|
userPreferredLoginNameIsPrimaryCol.identifier(): true,
|
||||||
|
userPreferredLoginNameInstanceIDCol.identifier(): instanceID,
|
||||||
|
}).ToSql()
|
||||||
|
if err != nil {
|
||||||
|
return sq.SelectBuilder{}, nil
|
||||||
|
}
|
||||||
|
return sq.Select(
|
||||||
|
UserIDCol.identifier(),
|
||||||
|
UserCreationDateCol.identifier(),
|
||||||
|
UserChangeDateCol.identifier(),
|
||||||
|
UserResourceOwnerCol.identifier(),
|
||||||
|
UserSequenceCol.identifier(),
|
||||||
|
UserStateCol.identifier(),
|
||||||
|
UserTypeCol.identifier(),
|
||||||
|
UserUsernameCol.identifier(),
|
||||||
|
userLoginNamesListCol.identifier(),
|
||||||
|
userPreferredLoginNameCol.identifier(),
|
||||||
|
HumanUserIDCol.identifier(),
|
||||||
|
HumanFirstNameCol.identifier(),
|
||||||
|
HumanLastNameCol.identifier(),
|
||||||
|
HumanNickNameCol.identifier(),
|
||||||
|
HumanDisplayNameCol.identifier(),
|
||||||
|
HumanPreferredLanguageCol.identifier(),
|
||||||
|
HumanGenderCol.identifier(),
|
||||||
|
HumanAvatarURLCol.identifier(),
|
||||||
|
NotifyUserIDCol.identifier(),
|
||||||
|
NotifyEmailCol.identifier(),
|
||||||
|
NotifyVerifiedEmailCol.identifier(),
|
||||||
|
NotifyPhoneCol.identifier(),
|
||||||
|
NotifyVerifiedPhoneCol.identifier(),
|
||||||
|
NotifyPasswordSetCol.identifier(),
|
||||||
|
).
|
||||||
|
From(userTable.identifier()).
|
||||||
|
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||||
|
LeftJoin(join(NotifyUserIDCol, UserIDCol)).
|
||||||
|
LeftJoin("("+loginNamesQuery+") as "+userLoginNamesTable.alias+" on "+userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier(), loginNamesArgs...).
|
||||||
|
LeftJoin("("+preferredLoginNameQuery+") as "+userPreferredLoginNameTable.alias+" on "+userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier(), preferredLoginNameArgs...).
|
||||||
|
PlaceholderFormat(sq.Dollar),
|
||||||
|
func(row *sql.Row) (*NotifyUser, error) {
|
||||||
|
u := new(NotifyUser)
|
||||||
|
loginNames := pq.StringArray{}
|
||||||
|
preferredLoginName := sql.NullString{}
|
||||||
|
|
||||||
|
humanID := sql.NullString{}
|
||||||
|
firstName := sql.NullString{}
|
||||||
|
lastName := sql.NullString{}
|
||||||
|
nickName := sql.NullString{}
|
||||||
|
displayName := sql.NullString{}
|
||||||
|
preferredLanguage := sql.NullString{}
|
||||||
|
gender := sql.NullInt32{}
|
||||||
|
avatarKey := sql.NullString{}
|
||||||
|
|
||||||
|
notifyUserID := sql.NullString{}
|
||||||
|
notifyEmail := sql.NullString{}
|
||||||
|
notifyVerifiedEmail := sql.NullString{}
|
||||||
|
notifyPhone := sql.NullString{}
|
||||||
|
notifyVerifiedPhone := sql.NullString{}
|
||||||
|
notifyPasswordSet := sql.NullBool{}
|
||||||
|
|
||||||
|
err := row.Scan(
|
||||||
|
&u.ID,
|
||||||
|
&u.CreationDate,
|
||||||
|
&u.ChangeDate,
|
||||||
|
&u.ResourceOwner,
|
||||||
|
&u.Sequence,
|
||||||
|
&u.State,
|
||||||
|
&u.Type,
|
||||||
|
&u.Username,
|
||||||
|
&loginNames,
|
||||||
|
&preferredLoginName,
|
||||||
|
&humanID,
|
||||||
|
&firstName,
|
||||||
|
&lastName,
|
||||||
|
&nickName,
|
||||||
|
&displayName,
|
||||||
|
&preferredLanguage,
|
||||||
|
&gender,
|
||||||
|
&avatarKey,
|
||||||
|
¬ifyUserID,
|
||||||
|
¬ifyEmail,
|
||||||
|
¬ifyVerifiedEmail,
|
||||||
|
¬ifyPhone,
|
||||||
|
¬ifyVerifiedPhone,
|
||||||
|
¬ifyPasswordSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if errs.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errors.ThrowNotFound(err, "QUERY-Dgqd2", "Errors.User.NotFound")
|
||||||
|
}
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-Dbwsg", "Errors.Internal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !notifyUserID.Valid {
|
||||||
|
return nil, errors.ThrowPreconditionFailed(nil, "QUERY-Sfw3f", "Errors.User.NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
u.LoginNames = loginNames
|
||||||
|
if preferredLoginName.Valid {
|
||||||
|
u.PreferredLoginName = preferredLoginName.String
|
||||||
|
}
|
||||||
|
if humanID.Valid {
|
||||||
|
u.FirstName = firstName.String
|
||||||
|
u.LastName = lastName.String
|
||||||
|
u.NickName = nickName.String
|
||||||
|
u.DisplayName = displayName.String
|
||||||
|
u.AvatarKey = avatarKey.String
|
||||||
|
u.PreferredLanguage = language.Make(preferredLanguage.String)
|
||||||
|
u.Gender = domain.Gender(gender.Int32)
|
||||||
|
}
|
||||||
|
u.LastEmail = notifyEmail.String
|
||||||
|
u.VerifiedEmail = notifyVerifiedEmail.String
|
||||||
|
u.LastPhone = notifyPhone.String
|
||||||
|
u.VerifiedPhone = notifyVerifiedPhone.String
|
||||||
|
u.PasswordSet = notifyPasswordSet.Bool
|
||||||
|
|
||||||
|
return u, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func prepareUserUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
func prepareUserUniqueQuery() (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
||||||
return sq.Select(
|
return sq.Select(
|
||||||
UserIDCol.identifier(),
|
UserIDCol.identifier(),
|
||||||
|
@ -24,14 +24,14 @@ var (
|
|||||||
", projections.user_grants.roles" +
|
", projections.user_grants.roles" +
|
||||||
", projections.user_grants.state" +
|
", projections.user_grants.state" +
|
||||||
", projections.user_grants.user_id" +
|
", projections.user_grants.user_id" +
|
||||||
", projections.users.username" +
|
", projections.users2.username" +
|
||||||
", projections.users.type" +
|
", projections.users2.type" +
|
||||||
", projections.users.resource_owner" +
|
", projections.users2.resource_owner" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.user_grants.resource_owner" +
|
", projections.user_grants.resource_owner" +
|
||||||
", projections.orgs.name" +
|
", projections.orgs.name" +
|
||||||
@ -39,8 +39,8 @@ var (
|
|||||||
", projections.user_grants.project_id" +
|
", projections.user_grants.project_id" +
|
||||||
", projections.projects.name" +
|
", projections.projects.name" +
|
||||||
" FROM projections.user_grants" +
|
" FROM projections.user_grants" +
|
||||||
" LEFT JOIN projections.users ON projections.user_grants.user_id = projections.users.id" +
|
" LEFT JOIN projections.users2 ON projections.user_grants.user_id = projections.users2.id" +
|
||||||
" LEFT JOIN projections.users_humans ON projections.user_grants.user_id = projections.users_humans.user_id" +
|
" LEFT JOIN projections.users2_humans ON projections.user_grants.user_id = projections.users2_humans.user_id" +
|
||||||
" LEFT JOIN projections.orgs ON projections.user_grants.resource_owner = projections.orgs.id" +
|
" LEFT JOIN projections.orgs ON projections.user_grants.resource_owner = projections.orgs.id" +
|
||||||
" LEFT JOIN projections.projects ON projections.user_grants.project_id = projections.projects.id" +
|
" LEFT JOIN projections.projects ON projections.user_grants.project_id = projections.projects.id" +
|
||||||
" LEFT JOIN projections.login_names ON projections.user_grants.user_id = projections.login_names.user_id" +
|
" LEFT JOIN projections.login_names ON projections.user_grants.user_id = projections.login_names.user_id" +
|
||||||
@ -78,14 +78,14 @@ var (
|
|||||||
", projections.user_grants.roles" +
|
", projections.user_grants.roles" +
|
||||||
", projections.user_grants.state" +
|
", projections.user_grants.state" +
|
||||||
", projections.user_grants.user_id" +
|
", projections.user_grants.user_id" +
|
||||||
", projections.users.username" +
|
", projections.users2.username" +
|
||||||
", projections.users.type" +
|
", projections.users2.type" +
|
||||||
", projections.users.resource_owner" +
|
", projections.users2.resource_owner" +
|
||||||
", projections.users_humans.first_name" +
|
", projections.users2_humans.first_name" +
|
||||||
", projections.users_humans.last_name" +
|
", projections.users2_humans.last_name" +
|
||||||
", projections.users_humans.email" +
|
", projections.users2_humans.email" +
|
||||||
", projections.users_humans.display_name" +
|
", projections.users2_humans.display_name" +
|
||||||
", projections.users_humans.avatar_key" +
|
", projections.users2_humans.avatar_key" +
|
||||||
", projections.login_names.login_name" +
|
", projections.login_names.login_name" +
|
||||||
", projections.user_grants.resource_owner" +
|
", projections.user_grants.resource_owner" +
|
||||||
", projections.orgs.name" +
|
", projections.orgs.name" +
|
||||||
@ -94,8 +94,8 @@ var (
|
|||||||
", projections.projects.name" +
|
", projections.projects.name" +
|
||||||
", COUNT(*) OVER ()" +
|
", COUNT(*) OVER ()" +
|
||||||
" FROM projections.user_grants" +
|
" FROM projections.user_grants" +
|
||||||
" LEFT JOIN projections.users ON projections.user_grants.user_id = projections.users.id" +
|
" LEFT JOIN projections.users2 ON projections.user_grants.user_id = projections.users2.id" +
|
||||||
" LEFT JOIN projections.users_humans ON projections.user_grants.user_id = projections.users_humans.user_id" +
|
" LEFT JOIN projections.users2_humans ON projections.user_grants.user_id = projections.users2_humans.user_id" +
|
||||||
" LEFT JOIN projections.orgs ON projections.user_grants.resource_owner = projections.orgs.id" +
|
" LEFT JOIN projections.orgs ON projections.user_grants.resource_owner = projections.orgs.id" +
|
||||||
" LEFT JOIN projections.projects ON projections.user_grants.project_id = projections.projects.id" +
|
" LEFT JOIN projections.projects ON projections.user_grants.project_id = projections.projects.id" +
|
||||||
" LEFT JOIN projections.login_names ON projections.user_grants.user_id = projections.login_names.user_id" +
|
" LEFT JOIN projections.login_names ON projections.user_grants.user_id = projections.login_names.user_id" +
|
||||||
|
@ -17,43 +17,43 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
userQuery = `SELECT projections.users.id,` +
|
userQuery = `SELECT projections.users2.id,` +
|
||||||
` projections.users.creation_date,` +
|
` projections.users2.creation_date,` +
|
||||||
` projections.users.change_date,` +
|
` projections.users2.change_date,` +
|
||||||
` projections.users.resource_owner,` +
|
` projections.users2.resource_owner,` +
|
||||||
` projections.users.sequence,` +
|
` projections.users2.sequence,` +
|
||||||
` projections.users.state,` +
|
` projections.users2.state,` +
|
||||||
` projections.users.type,` +
|
` projections.users2.type,` +
|
||||||
` projections.users.username,` +
|
` projections.users2.username,` +
|
||||||
` login_names.loginnames,` +
|
` login_names.loginnames,` +
|
||||||
` preferred_login_name.login_name,` +
|
` preferred_login_name.login_name,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.first_name,` +
|
` projections.users2_humans.first_name,` +
|
||||||
` projections.users_humans.last_name,` +
|
` projections.users2_humans.last_name,` +
|
||||||
` projections.users_humans.nick_name,` +
|
` projections.users2_humans.nick_name,` +
|
||||||
` projections.users_humans.display_name,` +
|
` projections.users2_humans.display_name,` +
|
||||||
` projections.users_humans.preferred_language,` +
|
` projections.users2_humans.preferred_language,` +
|
||||||
` projections.users_humans.gender,` +
|
` projections.users2_humans.gender,` +
|
||||||
` projections.users_humans.avatar_key,` +
|
` projections.users2_humans.avatar_key,` +
|
||||||
` projections.users_humans.email,` +
|
` projections.users2_humans.email,` +
|
||||||
` projections.users_humans.is_email_verified,` +
|
` projections.users2_humans.is_email_verified,` +
|
||||||
` projections.users_humans.phone,` +
|
` projections.users2_humans.phone,` +
|
||||||
` projections.users_humans.is_phone_verified,` +
|
` projections.users2_humans.is_phone_verified,` +
|
||||||
` projections.users_machines.user_id,` +
|
` projections.users2_machines.user_id,` +
|
||||||
` projections.users_machines.name,` +
|
` projections.users2_machines.name,` +
|
||||||
` projections.users_machines.description` +
|
` projections.users2_machines.description` +
|
||||||
` FROM projections.users` +
|
` FROM projections.users2` +
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id` +
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id` +
|
||||||
` LEFT JOIN projections.users_machines ON projections.users.id = projections.users_machines.user_id` +
|
` LEFT JOIN projections.users2_machines ON projections.users2.id = projections.users2_machines.user_id` +
|
||||||
` LEFT JOIN` +
|
` LEFT JOIN` +
|
||||||
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) as loginnames` +
|
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) as loginnames` +
|
||||||
` FROM projections.login_names as login_names` +
|
` FROM projections.login_names as login_names` +
|
||||||
` WHERE login_names.instance_id = $1` +
|
` WHERE login_names.instance_id = $1` +
|
||||||
` GROUP BY login_names.user_id) as login_names` +
|
` GROUP BY login_names.user_id) as login_names` +
|
||||||
` on login_names.user_id = projections.users.id` +
|
` on login_names.user_id = projections.users2.id` +
|
||||||
` LEFT JOIN` +
|
` LEFT JOIN` +
|
||||||
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names as preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) as preferred_login_name` +
|
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names as preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) as preferred_login_name` +
|
||||||
` on preferred_login_name.user_id = projections.users.id`
|
` on preferred_login_name.user_id = projections.users2.id`
|
||||||
userCols = []string{
|
userCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
@ -83,21 +83,21 @@ var (
|
|||||||
"name",
|
"name",
|
||||||
"description",
|
"description",
|
||||||
}
|
}
|
||||||
profileQuery = `SELECT projections.users.id,` +
|
profileQuery = `SELECT projections.users2.id,` +
|
||||||
` projections.users.creation_date,` +
|
` projections.users2.creation_date,` +
|
||||||
` projections.users.change_date,` +
|
` projections.users2.change_date,` +
|
||||||
` projections.users.resource_owner,` +
|
` projections.users2.resource_owner,` +
|
||||||
` projections.users.sequence,` +
|
` projections.users2.sequence,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.first_name,` +
|
` projections.users2_humans.first_name,` +
|
||||||
` projections.users_humans.last_name,` +
|
` projections.users2_humans.last_name,` +
|
||||||
` projections.users_humans.nick_name,` +
|
` projections.users2_humans.nick_name,` +
|
||||||
` projections.users_humans.display_name,` +
|
` projections.users2_humans.display_name,` +
|
||||||
` projections.users_humans.preferred_language,` +
|
` projections.users2_humans.preferred_language,` +
|
||||||
` projections.users_humans.gender,` +
|
` projections.users2_humans.gender,` +
|
||||||
` projections.users_humans.avatar_key` +
|
` projections.users2_humans.avatar_key` +
|
||||||
` FROM projections.users` +
|
` FROM projections.users2` +
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id`
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id`
|
||||||
profileCols = []string{
|
profileCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
@ -113,16 +113,16 @@ var (
|
|||||||
"gender",
|
"gender",
|
||||||
"avatar_key",
|
"avatar_key",
|
||||||
}
|
}
|
||||||
emailQuery = `SELECT projections.users.id,` +
|
emailQuery = `SELECT projections.users2.id,` +
|
||||||
` projections.users.creation_date,` +
|
` projections.users2.creation_date,` +
|
||||||
` projections.users.change_date,` +
|
` projections.users2.change_date,` +
|
||||||
` projections.users.resource_owner,` +
|
` projections.users2.resource_owner,` +
|
||||||
` projections.users.sequence,` +
|
` projections.users2.sequence,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.email,` +
|
` projections.users2_humans.email,` +
|
||||||
` projections.users_humans.is_email_verified` +
|
` projections.users2_humans.is_email_verified` +
|
||||||
` FROM projections.users` +
|
` FROM projections.users2` +
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id`
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id`
|
||||||
emailCols = []string{
|
emailCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
@ -133,16 +133,16 @@ var (
|
|||||||
"email",
|
"email",
|
||||||
"is_email_verified",
|
"is_email_verified",
|
||||||
}
|
}
|
||||||
phoneQuery = `SELECT projections.users.id,` +
|
phoneQuery = `SELECT projections.users2.id,` +
|
||||||
` projections.users.creation_date,` +
|
` projections.users2.creation_date,` +
|
||||||
` projections.users.change_date,` +
|
` projections.users2.change_date,` +
|
||||||
` projections.users.resource_owner,` +
|
` projections.users2.resource_owner,` +
|
||||||
` projections.users.sequence,` +
|
` projections.users2.sequence,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.phone,` +
|
` projections.users2_humans.phone,` +
|
||||||
` projections.users_humans.is_phone_verified` +
|
` projections.users2_humans.is_phone_verified` +
|
||||||
` FROM projections.users` +
|
` FROM projections.users2` +
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id`
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id`
|
||||||
phoneCols = []string{
|
phoneCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
@ -153,15 +153,14 @@ var (
|
|||||||
"phone",
|
"phone",
|
||||||
"is_phone_verified",
|
"is_phone_verified",
|
||||||
}
|
}
|
||||||
|
userUniqueQuery = `SELECT projections.users2.id,` +
|
||||||
userUniqueQuery = `SELECT projections.users.id,` +
|
` projections.users2.state,` +
|
||||||
` projections.users.state,` +
|
` projections.users2.username,` +
|
||||||
` projections.users.username,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.email,` +
|
||||||
` projections.users_humans.email,` +
|
` projections.users2_humans.is_email_verified` +
|
||||||
` projections.users_humans.is_email_verified` +
|
` FROM projections.users2` +
|
||||||
` FROM projections.users` +
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id`
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id`
|
|
||||||
userUniqueCols = []string{
|
userUniqueCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"state",
|
"state",
|
||||||
@ -170,43 +169,107 @@ var (
|
|||||||
"email",
|
"email",
|
||||||
"is_email_verified",
|
"is_email_verified",
|
||||||
}
|
}
|
||||||
usersQuery = `SELECT projections.users.id,` +
|
notifyUserQuery = `SELECT projections.users2.id,` +
|
||||||
` projections.users.creation_date,` +
|
` projections.users2.creation_date,` +
|
||||||
` projections.users.change_date,` +
|
` projections.users2.change_date,` +
|
||||||
` projections.users.resource_owner,` +
|
` projections.users2.resource_owner,` +
|
||||||
` projections.users.sequence,` +
|
` projections.users2.sequence,` +
|
||||||
` projections.users.state,` +
|
` projections.users2.state,` +
|
||||||
` projections.users.type,` +
|
` projections.users2.type,` +
|
||||||
` projections.users.username,` +
|
` projections.users2.username,` +
|
||||||
` login_names.loginnames,` +
|
` login_names.loginnames,` +
|
||||||
` preferred_login_name.login_name,` +
|
` preferred_login_name.login_name,` +
|
||||||
` projections.users_humans.user_id,` +
|
` projections.users2_humans.user_id,` +
|
||||||
` projections.users_humans.first_name,` +
|
` projections.users2_humans.first_name,` +
|
||||||
` projections.users_humans.last_name,` +
|
` projections.users2_humans.last_name,` +
|
||||||
` projections.users_humans.nick_name,` +
|
` projections.users2_humans.nick_name,` +
|
||||||
` projections.users_humans.display_name,` +
|
` projections.users2_humans.display_name,` +
|
||||||
` projections.users_humans.preferred_language,` +
|
` projections.users2_humans.preferred_language,` +
|
||||||
` projections.users_humans.gender,` +
|
` projections.users2_humans.gender,` +
|
||||||
` projections.users_humans.avatar_key,` +
|
` projections.users2_humans.avatar_key,` +
|
||||||
` projections.users_humans.email,` +
|
` projections.users2_notifications.user_id,` +
|
||||||
` projections.users_humans.is_email_verified,` +
|
` projections.users2_notifications.last_email,` +
|
||||||
` projections.users_humans.phone,` +
|
` projections.users2_notifications.verified_email,` +
|
||||||
` projections.users_humans.is_phone_verified,` +
|
` projections.users2_notifications.last_phone,` +
|
||||||
` projections.users_machines.user_id,` +
|
` projections.users2_notifications.verified_phone,` +
|
||||||
` projections.users_machines.name,` +
|
` projections.users2_notifications.password_set` +
|
||||||
` projections.users_machines.description,` +
|
` FROM projections.users2` +
|
||||||
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id` +
|
||||||
|
` LEFT JOIN projections.users2_notifications ON projections.users2.id = projections.users2_notifications.user_id` +
|
||||||
|
` LEFT JOIN` +
|
||||||
|
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) as loginnames` +
|
||||||
|
` FROM projections.login_names as login_names` +
|
||||||
|
` WHERE login_names.instance_id = $1` +
|
||||||
|
` GROUP BY login_names.user_id) as login_names` +
|
||||||
|
` on login_names.user_id = projections.users2.id` +
|
||||||
|
` LEFT JOIN` +
|
||||||
|
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names as preferred_login_name WHERE preferred_login_name.instance_id = $2 AND preferred_login_name.is_primary = $3) as preferred_login_name` +
|
||||||
|
` on preferred_login_name.user_id = projections.users2.id`
|
||||||
|
notifyUserCols = []string{
|
||||||
|
"id",
|
||||||
|
"creation_date",
|
||||||
|
"change_date",
|
||||||
|
"resource_owner",
|
||||||
|
"sequence",
|
||||||
|
"state",
|
||||||
|
"type",
|
||||||
|
"username",
|
||||||
|
"loginnames",
|
||||||
|
"login_name",
|
||||||
|
//human
|
||||||
|
"user_id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"nick_name",
|
||||||
|
"display_name",
|
||||||
|
"preferred_language",
|
||||||
|
"gender",
|
||||||
|
"avatar_key",
|
||||||
|
//machine
|
||||||
|
"user_id",
|
||||||
|
"last_email",
|
||||||
|
"verified_email",
|
||||||
|
"last_phone",
|
||||||
|
"verified_phone",
|
||||||
|
"password_set",
|
||||||
|
}
|
||||||
|
usersQuery = `SELECT projections.users2.id,` +
|
||||||
|
` projections.users2.creation_date,` +
|
||||||
|
` projections.users2.change_date,` +
|
||||||
|
` projections.users2.resource_owner,` +
|
||||||
|
` projections.users2.sequence,` +
|
||||||
|
` projections.users2.state,` +
|
||||||
|
` projections.users2.type,` +
|
||||||
|
` projections.users2.username,` +
|
||||||
|
` login_names.loginnames,` +
|
||||||
|
` preferred_login_name.login_name,` +
|
||||||
|
` projections.users2_humans.user_id,` +
|
||||||
|
` projections.users2_humans.first_name,` +
|
||||||
|
` projections.users2_humans.last_name,` +
|
||||||
|
` projections.users2_humans.nick_name,` +
|
||||||
|
` projections.users2_humans.display_name,` +
|
||||||
|
` projections.users2_humans.preferred_language,` +
|
||||||
|
` projections.users2_humans.gender,` +
|
||||||
|
` projections.users2_humans.avatar_key,` +
|
||||||
|
` projections.users2_humans.email,` +
|
||||||
|
` projections.users2_humans.is_email_verified,` +
|
||||||
|
` projections.users2_humans.phone,` +
|
||||||
|
` projections.users2_humans.is_phone_verified,` +
|
||||||
|
` projections.users2_machines.user_id,` +
|
||||||
|
` projections.users2_machines.name,` +
|
||||||
|
` projections.users2_machines.description,` +
|
||||||
` COUNT(*) OVER ()` +
|
` COUNT(*) OVER ()` +
|
||||||
` FROM projections.users` +
|
` FROM projections.users2` +
|
||||||
` LEFT JOIN projections.users_humans ON projections.users.id = projections.users_humans.user_id` +
|
` LEFT JOIN projections.users2_humans ON projections.users2.id = projections.users2_humans.user_id` +
|
||||||
` LEFT JOIN projections.users_machines ON projections.users.id = projections.users_machines.user_id` +
|
` LEFT JOIN projections.users2_machines ON projections.users2.id = projections.users2_machines.user_id` +
|
||||||
` LEFT JOIN` +
|
` LEFT JOIN` +
|
||||||
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) as loginnames` +
|
` (SELECT login_names.user_id, ARRAY_AGG(login_names.login_name) as loginnames` +
|
||||||
` FROM projections.login_names as login_names` +
|
` FROM projections.login_names as login_names` +
|
||||||
` GROUP BY login_names.user_id) as login_names` +
|
` GROUP BY login_names.user_id) as login_names` +
|
||||||
` on login_names.user_id = projections.users.id` +
|
` on login_names.user_id = projections.users2.id` +
|
||||||
` LEFT JOIN` +
|
` LEFT JOIN` +
|
||||||
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names as preferred_login_name WHERE preferred_login_name.is_primary = $1) as preferred_login_name` +
|
` (SELECT preferred_login_name.user_id, preferred_login_name.login_name FROM projections.login_names as preferred_login_name WHERE preferred_login_name.is_primary = $1) as preferred_login_name` +
|
||||||
` on preferred_login_name.user_id = projections.users.id`
|
` on preferred_login_name.user_id = projections.users2.id`
|
||||||
usersCols = []string{
|
usersCols = []string{
|
||||||
"id",
|
"id",
|
||||||
"creation_date",
|
"creation_date",
|
||||||
@ -760,6 +823,155 @@ func Test_UserPrepares(t *testing.T) {
|
|||||||
},
|
},
|
||||||
object: nil,
|
object: nil,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "prepareNotifyUserQuery no result",
|
||||||
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||||
|
return prepareNotifyUserQuery("instanceID")
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
sqlExpectations: mockQuery(
|
||||||
|
regexp.QuoteMeta(notifyUserQuery),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
),
|
||||||
|
err: func(err error) (error, bool) {
|
||||||
|
if !errs.IsNotFound(err) {
|
||||||
|
return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
object: (*NotifyUser)(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prepareNotifyUserQuery notify found",
|
||||||
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||||
|
return prepareNotifyUserQuery("instanceID")
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
sqlExpectations: mockQuery(
|
||||||
|
regexp.QuoteMeta(notifyUserQuery),
|
||||||
|
notifyUserCols,
|
||||||
|
[]driver.Value{
|
||||||
|
"id",
|
||||||
|
testNow,
|
||||||
|
testNow,
|
||||||
|
"resource_owner",
|
||||||
|
uint64(20211108),
|
||||||
|
domain.UserStateActive,
|
||||||
|
domain.UserTypeHuman,
|
||||||
|
"username",
|
||||||
|
pq.StringArray{"login_name1", "login_name2"},
|
||||||
|
"login_name1",
|
||||||
|
//human
|
||||||
|
"id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"nick_name",
|
||||||
|
"display_name",
|
||||||
|
"de",
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"avatar_key",
|
||||||
|
//notify
|
||||||
|
"id",
|
||||||
|
"lastEmail",
|
||||||
|
"verifiedEmail",
|
||||||
|
"lastPhone",
|
||||||
|
"verifiedPhone",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
object: &NotifyUser{
|
||||||
|
ID: "id",
|
||||||
|
CreationDate: testNow,
|
||||||
|
ChangeDate: testNow,
|
||||||
|
ResourceOwner: "resource_owner",
|
||||||
|
Sequence: 20211108,
|
||||||
|
State: domain.UserStateActive,
|
||||||
|
Type: domain.UserTypeHuman,
|
||||||
|
Username: "username",
|
||||||
|
LoginNames: []string{"login_name1", "login_name2"},
|
||||||
|
PreferredLoginName: "login_name1",
|
||||||
|
FirstName: "first_name",
|
||||||
|
LastName: "last_name",
|
||||||
|
NickName: "nick_name",
|
||||||
|
DisplayName: "display_name",
|
||||||
|
AvatarKey: "avatar_key",
|
||||||
|
PreferredLanguage: language.German,
|
||||||
|
Gender: domain.GenderUnspecified,
|
||||||
|
LastEmail: "lastEmail",
|
||||||
|
VerifiedEmail: "verifiedEmail",
|
||||||
|
LastPhone: "lastPhone",
|
||||||
|
VerifiedPhone: "verifiedPhone",
|
||||||
|
PasswordSet: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prepareNotifyUserQuery not notify found (error)",
|
||||||
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||||
|
return prepareNotifyUserQuery("instanceID")
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
sqlExpectations: mockQuery(
|
||||||
|
regexp.QuoteMeta(notifyUserQuery),
|
||||||
|
notifyUserCols,
|
||||||
|
[]driver.Value{
|
||||||
|
"id",
|
||||||
|
testNow,
|
||||||
|
testNow,
|
||||||
|
"resource_owner",
|
||||||
|
uint64(20211108),
|
||||||
|
domain.UserStateActive,
|
||||||
|
domain.UserTypeHuman,
|
||||||
|
"username",
|
||||||
|
pq.StringArray{"login_name1", "login_name2"},
|
||||||
|
"login_name1",
|
||||||
|
//human
|
||||||
|
"id",
|
||||||
|
"first_name",
|
||||||
|
"last_name",
|
||||||
|
"nick_name",
|
||||||
|
"display_name",
|
||||||
|
"de",
|
||||||
|
domain.GenderUnspecified,
|
||||||
|
"avatar_key",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
err: func(err error) (error, bool) {
|
||||||
|
if !errs.IsPreconditionFailed(err) {
|
||||||
|
return fmt.Errorf("err should be zitadel.PredconditionError got: %w", err), false
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
object: (*NotifyUser)(nil),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prepareNotifyUserQuery sql err",
|
||||||
|
prepare: func() (sq.SelectBuilder, func(*sql.Row) (*NotifyUser, error)) {
|
||||||
|
return prepareNotifyUserQuery("instanceID")
|
||||||
|
},
|
||||||
|
want: want{
|
||||||
|
sqlExpectations: mockQueryErr(
|
||||||
|
regexp.QuoteMeta(notifyUserQuery),
|
||||||
|
sql.ErrConnDone,
|
||||||
|
),
|
||||||
|
err: func(err error) (error, bool) {
|
||||||
|
if !errors.Is(err, sql.ErrConnDone) {
|
||||||
|
return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false
|
||||||
|
}
|
||||||
|
return nil, true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
object: nil,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "prepareUsersQuery no result",
|
name: "prepareUsersQuery no result",
|
||||||
prepare: prepareUsersQuery,
|
prepare: prepareUsersQuery,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user