mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 12:37:39 +00:00
fix(query): user performance (#6537)
* start user by id * ignore debug bin * use new user by id * new sql * fix(sql): replace STRING with text for psql compatabilit * some changes * fix: correct user queries * fix tests * unify sql statements * use specific get user methods * search login name case insensitive * refactor: optimise user statements * add index * fix queries * fix: correct domain segregation * return all login names * fix queries * improve readability * query should be correct now * cleanup statements * fix username / loginname handling * fix: psql doesn't support create view if not exists * fix: create pre-release * ignore release comments * add lower fields * fix: always to lower * update to latest projection --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
26
cmd/setup/18.go
Normal file
26
cmd/setup/18.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed 18.sql
|
||||||
|
addLowerFieldsToLoginNames string
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddLowerFieldsToLoginNames struct {
|
||||||
|
dbClient *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *AddLowerFieldsToLoginNames) Execute(ctx context.Context) error {
|
||||||
|
_, err := mig.dbClient.ExecContext(ctx, addLowerFieldsToLoginNames)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *AddLowerFieldsToLoginNames) String() string {
|
||||||
|
return "18_add_lower_fields_to_login_names"
|
||||||
|
}
|
6
cmd/setup/18.sql
Normal file
6
cmd/setup/18.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
ALTER TABLE IF EXISTS projections.login_names3_users ADD COLUMN IF NOT EXISTS user_name_lower TEXT GENERATED ALWAYS AS (lower(user_name)) STORED;
|
||||||
|
CREATE INDEX IF NOT EXISTS login_names3_users_search ON projections.login_names3_users (instance_id, user_name_lower) INCLUDE (resource_owner);
|
||||||
|
|
||||||
|
ALTER TABLE IF EXISTS projections.login_names3_domains ADD COLUMN IF NOT EXISTS name_lower TEXT GENERATED ALWAYS AS (lower(name)) STORED;
|
||||||
|
CREATE INDEX IF NOT EXISTS login_names3_domain_search ON projections.login_names3_domains (instance_id, resource_owner, name_lower);
|
||||||
|
CREATE INDEX IF NOT EXISTS login_names3_domain_search_result ON projections.login_names3_domains (instance_id, resource_owner) INCLUDE (is_primary);
|
@@ -75,6 +75,7 @@ type Steps struct {
|
|||||||
s15CurrentStates *CurrentProjectionState
|
s15CurrentStates *CurrentProjectionState
|
||||||
s16UniqueConstraintsLower *UniqueConstraintToLower
|
s16UniqueConstraintsLower *UniqueConstraintToLower
|
||||||
s17AddOffsetToUniqueConstraints *AddOffsetToCurrentStates
|
s17AddOffsetToUniqueConstraints *AddOffsetToCurrentStates
|
||||||
|
s18AddLowerFieldsToLoginNames *AddLowerFieldsToLoginNames
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptionKeyConfig struct {
|
type encryptionKeyConfig struct {
|
||||||
|
@@ -105,6 +105,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
|||||||
steps.s15CurrentStates = &CurrentProjectionState{dbClient: zitadelDBClient}
|
steps.s15CurrentStates = &CurrentProjectionState{dbClient: zitadelDBClient}
|
||||||
steps.s16UniqueConstraintsLower = &UniqueConstraintToLower{dbClient: zitadelDBClient}
|
steps.s16UniqueConstraintsLower = &UniqueConstraintToLower{dbClient: zitadelDBClient}
|
||||||
steps.s17AddOffsetToUniqueConstraints = &AddOffsetToCurrentStates{dbClient: zitadelDBClient}
|
steps.s17AddOffsetToUniqueConstraints = &AddOffsetToCurrentStates{dbClient: zitadelDBClient}
|
||||||
|
steps.s18AddLowerFieldsToLoginNames = &AddLowerFieldsToLoginNames{dbClient: zitadelDBClient}
|
||||||
|
|
||||||
err = projection.Create(ctx, zitadelDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
err = projection.Create(ctx, zitadelDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||||
logging.OnError(err).Fatal("unable to start projections")
|
logging.OnError(err).Fatal("unable to start projections")
|
||||||
@@ -154,6 +155,10 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
|||||||
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
||||||
logging.OnError(err).Fatalf("unable to migrate repeatable step: %s", repeatableStep.String())
|
logging.OnError(err).Fatalf("unable to migrate repeatable step: %s", repeatableStep.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This step is executed after the repeatable steps because it adds fields to the login_names3 projection
|
||||||
|
err = migration.Migrate(ctx, eventstoreClient, steps.s18AddLowerFieldsToLoginNames)
|
||||||
|
logging.WithFields("name", steps.s18AddLowerFieldsToLoginNames.String()).OnError(err).Fatal("migration failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
func readStmt(fs embed.FS, folder, typ, filename string) (string, error) {
|
func readStmt(fs embed.FS, folder, typ, filename string) (string, error) {
|
||||||
|
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/api/ui/login"
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
@@ -27,13 +28,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (s *Server) getUserByID(ctx context.Context, id string) (*query.User, error) {
|
func (s *Server) getUserByID(ctx context.Context, id string) (*query.User, error) {
|
||||||
owner, err := query.NewUserResourceOwnerSearchQuery(authz.GetCtxData(ctx).OrgID, query.TextEquals)
|
user, err := s.query.GetUserByID(ctx, true, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
user, err := s.query.GetUserByID(ctx, true, id, owner)
|
if user.ResourceOwner != authz.GetCtxData(ctx).OrgID {
|
||||||
if err != nil {
|
return nil, errors.ThrowNotFound(nil, "MANAG-fpo4B", "Errors.User.NotFound")
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
return user, nil
|
return user, nil
|
||||||
}
|
}
|
||||||
@@ -49,11 +49,7 @@ func (s *Server) GetUserByID(ctx context.Context, req *mgmt_pb.GetUserByIDReques
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUserByLoginNameGlobal(ctx context.Context, req *mgmt_pb.GetUserByLoginNameGlobalRequest) (*mgmt_pb.GetUserByLoginNameGlobalResponse, error) {
|
func (s *Server) GetUserByLoginNameGlobal(ctx context.Context, req *mgmt_pb.GetUserByLoginNameGlobalRequest) (*mgmt_pb.GetUserByLoginNameGlobalResponse, error) {
|
||||||
loginName, err := query.NewUserPreferredLoginNameSearchQuery(req.LoginName, query.TextEquals)
|
user, err := s.query.GetUserByLoginName(ctx, true, req.LoginName)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user, err := s.query.GetUser(ctx, true, loginName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -474,11 +474,7 @@ func userByID(userID string) userSearch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func userByLoginName(loginName string) (userSearch, error) {
|
func userByLoginName(loginName string) (userSearch, error) {
|
||||||
loginNameQuery, err := query.NewUserLoginNamesSearchQuery(loginName)
|
return userSearchByLoginName{loginName}, nil
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return userSearchByLoginName{loginNameQuery}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type userSearchByID struct {
|
type userSearchByID struct {
|
||||||
@@ -490,9 +486,9 @@ func (u userSearchByID) search(ctx context.Context, q *query.Queries) (*query.Us
|
|||||||
}
|
}
|
||||||
|
|
||||||
type userSearchByLoginName struct {
|
type userSearchByLoginName struct {
|
||||||
loginNameQuery query.SearchQuery
|
loginName string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u userSearchByLoginName) search(ctx context.Context, q *query.Queries) (*query.User, error) {
|
func (u userSearchByLoginName) search(ctx context.Context, q *query.Queries) (*query.User, error) {
|
||||||
return q.GetUser(ctx, true, u.loginNameQuery)
|
return q.GetUserByLoginName(ctx, true, u.loginName)
|
||||||
}
|
}
|
||||||
|
@@ -586,12 +586,6 @@ func Test_sessionQueryToQuery(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func mustUserLoginNamesSearchQuery(t testing.TB, value string) query.SearchQuery {
|
|
||||||
loginNameQuery, err := query.NewUserLoginNamesSearchQuery("bar")
|
|
||||||
require.NoError(t, err)
|
|
||||||
return loginNameQuery
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_userCheck(t *testing.T) {
|
func Test_userCheck(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
user *session.CheckUser
|
user *session.CheckUser
|
||||||
@@ -623,7 +617,7 @@ func Test_userCheck(t *testing.T) {
|
|||||||
LoginName: "bar",
|
LoginName: "bar",
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
want: userSearchByLoginName{mustUserLoginNamesSearchQuery(t, "bar")},
|
want: userSearchByLoginName{"bar"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unimplemented error",
|
name: "unimplemented error",
|
||||||
|
@@ -197,11 +197,7 @@ func (o *OPStorage) SetIntrospectionFromToken(ctx context.Context, introspection
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scope []string) (op.TokenRequest, error) {
|
func (o *OPStorage) ClientCredentialsTokenRequest(ctx context.Context, clientID string, scope []string) (op.TokenRequest, error) {
|
||||||
loginname, err := query.NewUserLoginNamesSearchQuery(clientID)
|
user, err := o.query.GetUserByLoginName(ctx, false, clientID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user, err := o.query.GetUser(ctx, false, loginname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@@ -34,11 +34,7 @@ func (c *clientCredentialsRequest) GetScopes() []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
func (s *Server) clientCredentialsAuth(ctx context.Context, clientID, clientSecret string) (op.Client, error) {
|
||||||
searchQuery, err := query.NewUserLoginNamesSearchQuery(clientID)
|
user, err := s.query.GetUserByLoginName(ctx, false, clientID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
user, err := s.query.GetUser(ctx, false, searchQuery)
|
|
||||||
if errors.IsNotFound(err) {
|
if errors.IsNotFound(err) {
|
||||||
return nil, oidc.ErrInvalidClient().WithParent(err).WithDescription("client not found")
|
return nil, oidc.ErrInvalidClient().WithParent(err).WithDescription("client not found")
|
||||||
}
|
}
|
||||||
|
@@ -159,11 +159,7 @@ func (p *Storage) SetUserinfoWithLoginName(ctx context.Context, userinfo models.
|
|||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
loginNameSQ, err := query.NewUserLoginNamesSearchQuery(loginName)
|
user, err := p.query.GetUserByLoginName(ctx, true, loginName)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
user, err := p.query.GetUser(ctx, true, loginNameSQ)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@@ -7,7 +7,6 @@ import (
|
|||||||
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -91,7 +90,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
|
|||||||
if authReq != nil {
|
if authReq != nil {
|
||||||
userOrg = authReq.UserOrgID
|
userOrg = authReq.UserOrgID
|
||||||
}
|
}
|
||||||
loginName, err := query.NewUserLoginNamesSearchQuery(authReq.LoginName)
|
user, err := l.query.GetUserByLoginName(setContext(r.Context(), userOrg), false, authReq.LoginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
||||||
return
|
return
|
||||||
@@ -101,11 +100,6 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
|
|||||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user, err := l.query.GetUser(setContext(r.Context(), userOrg), false, loginName)
|
|
||||||
if err != nil {
|
|
||||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), user.ID, user.ResourceOwner, domain.NotificationTypeEmail, passwordCodeGenerator)
|
_, err = l.command.RequestSetPassword(setContext(r.Context(), userOrg), user.ID, user.ResourceOwner, domain.NotificationTypeEmail, passwordCodeGenerator)
|
||||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
||||||
}
|
}
|
||||||
|
@@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"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"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -18,12 +17,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loginName, err := query.NewUserLoginNamesSearchQuery(authReq.LoginName)
|
user, err := l.query.GetUserByLoginName(setContext(r.Context(), authReq.UserOrgID), true, authReq.LoginName)
|
||||||
if err != nil {
|
|
||||||
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
user, err := l.query.GetUser(setContext(r.Context(), authReq.UserOrgID), true, loginName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if authReq.LoginPolicy.IgnoreUnknownUsernames && errors.IsNotFound(err) {
|
if authReq.LoginPolicy.IgnoreUnknownUsernames && errors.IsNotFound(err) {
|
||||||
err = nil
|
err = nil
|
||||||
|
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/errors"
|
"github.com/zitadel/zitadel/internal/errors"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
@@ -21,25 +22,31 @@ func (v *View) UserByID(userID, instanceID string) (*model.UserView, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) UserByLoginName(ctx context.Context, loginName, instanceID string) (*model.UserView, error) {
|
func (v *View) UserByLoginName(ctx context.Context, loginName, instanceID string) (*model.UserView, error) {
|
||||||
loginNameQuery, err := query.NewUserLoginNamesSearchQuery(loginName)
|
queriedUser, err := v.query.GetNotifyUserByLoginName(ctx, true, loginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.userByID(ctx, instanceID, loginNameQuery)
|
//nolint: contextcheck // no lint was added because refactor would change too much code
|
||||||
|
return view.UserByID(v.Db, userTable, queriedUser.ID, instanceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) UserByLoginNameAndResourceOwner(ctx context.Context, loginName, resourceOwner, instanceID string) (*model.UserView, error) {
|
func (v *View) UserByLoginNameAndResourceOwner(ctx context.Context, loginName, resourceOwner, instanceID string) (*model.UserView, error) {
|
||||||
loginNameQuery, err := query.NewUserLoginNamesSearchQuery(loginName)
|
queriedUser, err := v.query.GetNotifyUserByLoginName(ctx, true, loginName)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resourceOwnerQuery, err := query.NewUserResourceOwnerSearchQuery(resourceOwner, query.TextEquals)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return v.userByID(ctx, instanceID, loginNameQuery, resourceOwnerQuery)
|
//nolint: contextcheck // no lint was added because refactor would change too much code
|
||||||
|
user, err := view.UserByID(v.Db, userTable, queriedUser.ID, instanceID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if user.ResourceOwner != resourceOwner {
|
||||||
|
return nil, errors.ThrowNotFound(nil, "VIEW-qScmi", "Errors.User.NotFound")
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *View) UserByEmail(ctx context.Context, email, instanceID string) (*model.UserView, error) {
|
func (v *View) UserByEmail(ctx context.Context, email, instanceID string) (*model.UserView, error) {
|
||||||
|
@@ -101,23 +101,18 @@ func (mr *MockQueriesMockRecorder) GetInstanceRestrictions(arg0 any) *gomock.Cal
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetNotifyUserByID mocks base method.
|
// GetNotifyUserByID mocks base method.
|
||||||
func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string, arg3 ...query.SearchQuery) (*query.NotifyUser, error) {
|
func (m *MockQueries) GetNotifyUserByID(arg0 context.Context, arg1 bool, arg2 string) (*query.NotifyUser, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
varargs := []any{arg0, arg1, arg2}
|
ret := m.ctrl.Call(m, "GetNotifyUserByID", arg0, arg1, arg2)
|
||||||
for _, a := range arg3 {
|
|
||||||
varargs = append(varargs, a)
|
|
||||||
}
|
|
||||||
ret := m.ctrl.Call(m, "GetNotifyUserByID", varargs...)
|
|
||||||
ret0, _ := ret[0].(*query.NotifyUser)
|
ret0, _ := ret[0].(*query.NotifyUser)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNotifyUserByID indicates an expected call of GetNotifyUserByID.
|
// GetNotifyUserByID indicates an expected call of GetNotifyUserByID.
|
||||||
func (mr *MockQueriesMockRecorder) GetNotifyUserByID(arg0, arg1, arg2 any, arg3 ...any) *gomock.Call {
|
func (mr *MockQueriesMockRecorder) GetNotifyUserByID(arg0, arg1, arg2 any) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
varargs := append([]any{arg0, arg1, arg2}, arg3...)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotifyUserByID", reflect.TypeOf((*MockQueries)(nil).GetNotifyUserByID), arg0, arg1, arg2)
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNotifyUserByID", reflect.TypeOf((*MockQueries)(nil).GetNotifyUserByID), varargs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MailTemplateByOrg mocks base method.
|
// MailTemplateByOrg mocks base method.
|
||||||
|
@@ -13,7 +13,7 @@ import (
|
|||||||
type Queries interface {
|
type Queries interface {
|
||||||
ActiveLabelPolicyByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.LabelPolicy, error)
|
ActiveLabelPolicyByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.LabelPolicy, error)
|
||||||
MailTemplateByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.MailTemplate, error)
|
MailTemplateByOrg(ctx context.Context, orgID string, withOwnerRemoved bool) (*query.MailTemplate, error)
|
||||||
GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...query.SearchQuery) (*query.NotifyUser, error)
|
GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string) (*query.NotifyUser, error)
|
||||||
CustomTextListByTemplate(ctx context.Context, aggregateID, template string, withOwnerRemoved bool) (*query.CustomTexts, error)
|
CustomTextListByTemplate(ctx context.Context, aggregateID, template string, withOwnerRemoved bool) (*query.CustomTexts, error)
|
||||||
SearchInstanceDomains(ctx context.Context, queries *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error)
|
SearchInstanceDomains(ctx context.Context, queries *query.InstanceDomainSearchQueries) (*query.InstanceDomains, error)
|
||||||
SessionByID(ctx context.Context, shouldTriggerBulk bool, id, sessionToken string) (*query.Session, error)
|
SessionByID(ctx context.Context, shouldTriggerBulk bool, id, sessionToken string) (*query.Session, error)
|
||||||
|
@@ -17,6 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// if the table name of the users or domains table is changed please update setup step 18
|
||||||
LoginNameTableAlias = "login_names3"
|
LoginNameTableAlias = "login_names3"
|
||||||
LoginNameProjectionTable = "projections." + LoginNameTableAlias
|
LoginNameProjectionTable = "projections." + LoginNameTableAlias
|
||||||
LoginNameUserProjectionTable = LoginNameProjectionTable + "_" + loginNameUserSuffix
|
LoginNameUserProjectionTable = LoginNameProjectionTable + "_" + loginNameUserSuffix
|
||||||
@@ -38,11 +39,14 @@ const (
|
|||||||
loginNameUserSuffix = "users"
|
loginNameUserSuffix = "users"
|
||||||
LoginNameUserIDCol = "id"
|
LoginNameUserIDCol = "id"
|
||||||
LoginNameUserUserNameCol = "user_name"
|
LoginNameUserUserNameCol = "user_name"
|
||||||
|
// internal fields for faster search
|
||||||
|
loginNameUserUserNameLowerCol = "user_name_lower"
|
||||||
LoginNameUserResourceOwnerCol = "resource_owner"
|
LoginNameUserResourceOwnerCol = "resource_owner"
|
||||||
LoginNameUserInstanceIDCol = "instance_id"
|
LoginNameUserInstanceIDCol = "instance_id"
|
||||||
|
|
||||||
loginNameDomainSuffix = "domains"
|
loginNameDomainSuffix = "domains"
|
||||||
LoginNameDomainNameCol = "name"
|
LoginNameDomainNameCol = "name"
|
||||||
|
loginNameDomainNameLowerCol = "name_lower"
|
||||||
LoginNameDomainIsPrimaryCol = "is_primary"
|
LoginNameDomainIsPrimaryCol = "is_primary"
|
||||||
LoginNameDomainResourceOwnerCol = "resource_owner"
|
LoginNameDomainResourceOwnerCol = "resource_owner"
|
||||||
LoginNameDomainInstanceIDCol = "instance_id"
|
LoginNameDomainInstanceIDCol = "instance_id"
|
||||||
@@ -171,12 +175,16 @@ func (*loginNameProjection) Init() *old_handler.Check {
|
|||||||
[]*handler.InitColumn{
|
[]*handler.InitColumn{
|
||||||
handler.NewColumn(LoginNameUserIDCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameUserIDCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LoginNameUserUserNameCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameUserUserNameCol, handler.ColumnTypeText),
|
||||||
|
// TODO: implement computed columns
|
||||||
|
// handler.NewComputedColumn(loginNameUserUserNameLowerCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LoginNameUserResourceOwnerCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameUserResourceOwnerCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LoginNameUserInstanceIDCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameUserInstanceIDCol, handler.ColumnTypeText),
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(LoginNameUserInstanceIDCol, LoginNameUserIDCol),
|
handler.NewPrimaryKey(LoginNameUserInstanceIDCol, LoginNameUserIDCol),
|
||||||
loginNameUserSuffix,
|
loginNameUserSuffix,
|
||||||
handler.WithIndex(handler.NewIndex("resource_owner", []string{LoginNameUserResourceOwnerCol})),
|
handler.WithIndex(handler.NewIndex("instance_user_name", []string{LoginNameUserInstanceIDCol, LoginNameUserUserNameCol},
|
||||||
|
handler.WithInclude(LoginNameUserResourceOwnerCol),
|
||||||
|
)),
|
||||||
handler.WithIndex(
|
handler.WithIndex(
|
||||||
handler.NewIndex("lnu_instance_ro_id", []string{LoginNameUserInstanceIDCol, LoginNameUserResourceOwnerCol, LoginNameUserIDCol},
|
handler.NewIndex("lnu_instance_ro_id", []string{LoginNameUserInstanceIDCol, LoginNameUserResourceOwnerCol, LoginNameUserIDCol},
|
||||||
handler.WithInclude(
|
handler.WithInclude(
|
||||||
@@ -184,16 +192,33 @@ func (*loginNameProjection) Init() *old_handler.Check {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
// TODO: uncomment the following line when login_names4 will be created
|
||||||
|
// handler.WithIndex(
|
||||||
|
// handler.NewIndex("search", []string{LoginNameUserInstanceIDCol, loginNameUserUserNameLowerCol},
|
||||||
|
// handler.WithInclude(LoginNameUserResourceOwnerCol),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
),
|
),
|
||||||
handler.NewSuffixedTable(
|
handler.NewSuffixedTable(
|
||||||
[]*handler.InitColumn{
|
[]*handler.InitColumn{
|
||||||
handler.NewColumn(LoginNameDomainNameCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameDomainNameCol, handler.ColumnTypeText),
|
||||||
|
// TODO: implement computed columns
|
||||||
|
// handler.NewComputedColumn(loginNameDomainNameLowerCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LoginNameDomainIsPrimaryCol, handler.ColumnTypeBool, handler.Default(false)),
|
handler.NewColumn(LoginNameDomainIsPrimaryCol, handler.ColumnTypeBool, handler.Default(false)),
|
||||||
handler.NewColumn(LoginNameDomainResourceOwnerCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameDomainResourceOwnerCol, handler.ColumnTypeText),
|
||||||
handler.NewColumn(LoginNameDomainInstanceIDCol, handler.ColumnTypeText),
|
handler.NewColumn(LoginNameDomainInstanceIDCol, handler.ColumnTypeText),
|
||||||
},
|
},
|
||||||
handler.NewPrimaryKey(LoginNameDomainInstanceIDCol, LoginNameDomainResourceOwnerCol, LoginNameDomainNameCol),
|
handler.NewPrimaryKey(LoginNameDomainInstanceIDCol, LoginNameDomainResourceOwnerCol, LoginNameDomainNameCol),
|
||||||
loginNameDomainSuffix,
|
loginNameDomainSuffix,
|
||||||
|
// TODO: uncomment the following line when login_names4 will be created
|
||||||
|
// handler.WithIndex(
|
||||||
|
// handler.NewIndex("search", []string{LoginNameDomainInstanceIDCol, LoginNameDomainResourceOwnerCol, loginNameDomainNameLowerCol}),
|
||||||
|
// ),
|
||||||
|
// handler.WithIndex(
|
||||||
|
// handler.NewIndex("search_result", []string{LoginNameDomainInstanceIDCol, LoginNameDomainResourceOwnerCol},
|
||||||
|
// handler.WithInclude(LoginNameDomainIsPrimaryCol),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
),
|
),
|
||||||
handler.NewSuffixedTable(
|
handler.NewSuffixedTable(
|
||||||
[]*handler.InitColumn{
|
[]*handler.InitColumn{
|
||||||
|
@@ -3,6 +3,7 @@ package query
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
_ "embed"
|
||||||
errs "errors"
|
errs "errors"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -314,7 +315,10 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string, queries ...SearchQuery) (user *User, err error) {
|
//go:embed user_by_id.sql
|
||||||
|
var userByIDQuery string
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userID string) (user *User, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
@@ -322,26 +326,55 @@ func (q *Queries) GetUserByID(ctx context.Context, shouldTriggerBulk bool, userI
|
|||||||
triggerUserProjections(ctx)
|
triggerUserProjections(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
query, scan := prepareUserQuery(ctx, q.client)
|
err = q.client.QueryRowContext(ctx,
|
||||||
for _, q := range queries {
|
func(row *sql.Row) error {
|
||||||
query = q.toQuery(query)
|
user, err = scanUser(row)
|
||||||
}
|
|
||||||
eq := sq.Eq{
|
|
||||||
UserIDCol.identifier(): userID,
|
|
||||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
|
||||||
}
|
|
||||||
stmt, args, err := query.Where(eq).ToSql()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.ThrowInternal(err, "QUERY-FBg21", "Errors.Query.SQLStatment")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
|
||||||
user, err = scan(row)
|
|
||||||
return err
|
return err
|
||||||
}, stmt, args...)
|
},
|
||||||
|
userByIDQuery,
|
||||||
|
userID,
|
||||||
|
authz.GetInstance(ctx).InstanceID(),
|
||||||
|
)
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:embed user_by_login_name.sql
|
||||||
|
var userByLoginNameQuery string
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByLoginName(ctx context.Context, shouldTriggered bool, loginName string) (user *User, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
if shouldTriggered {
|
||||||
|
triggerUserProjections(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
loginName = strings.ToLower(loginName)
|
||||||
|
|
||||||
|
username := loginName
|
||||||
|
domainIndex := strings.LastIndex(loginName, "@")
|
||||||
|
var domainSuffix string
|
||||||
|
// split between the last @ (so ignore it if the login name ends with it)
|
||||||
|
if domainIndex > 0 && domainIndex != len(loginName)-1 {
|
||||||
|
domainSuffix = loginName[domainIndex+1:]
|
||||||
|
username = loginName[:domainIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryRowContext(ctx,
|
||||||
|
func(row *sql.Row) error {
|
||||||
|
user, err = scanUser(row)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
userByLoginNameQuery,
|
||||||
|
username,
|
||||||
|
domainSuffix,
|
||||||
|
loginName,
|
||||||
|
authz.GetInstance(ctx).InstanceID(),
|
||||||
|
)
|
||||||
|
return user, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: use either GetUserByID or GetUserByLoginName
|
||||||
func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (user *User, err error) {
|
func (q *Queries) GetUser(ctx context.Context, shouldTriggerBulk bool, queries ...SearchQuery) (user *User, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@@ -441,7 +474,10 @@ func (q *Queries) GetHumanPhone(ctx context.Context, userID string, queries ...S
|
|||||||
return phone, err
|
return phone, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string, queries ...SearchQuery) (user *NotifyUser, err error) {
|
//go:embed user_notify_by_id.sql
|
||||||
|
var notifyUserByIDQuery string
|
||||||
|
|
||||||
|
func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, userID string) (user *NotifyUser, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
@@ -449,23 +485,51 @@ func (q *Queries) GetNotifyUserByID(ctx context.Context, shouldTriggered bool, u
|
|||||||
triggerUserProjections(ctx)
|
triggerUserProjections(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
query, scan := prepareNotifyUserQuery(ctx, q.client)
|
err = q.client.QueryRowContext(ctx,
|
||||||
for _, q := range queries {
|
func(row *sql.Row) error {
|
||||||
query = q.toQuery(query)
|
user, err = scanNotifyUser(row)
|
||||||
}
|
return err
|
||||||
eq := sq.Eq{
|
},
|
||||||
UserIDCol.identifier(): userID,
|
notifyUserByIDQuery,
|
||||||
UserInstanceIDCol.identifier(): authz.GetInstance(ctx).InstanceID(),
|
userID,
|
||||||
}
|
authz.GetInstance(ctx).InstanceID(),
|
||||||
stmt, args, err := query.Where(eq).ToSql()
|
)
|
||||||
if err != nil {
|
return user, err
|
||||||
return nil, errors.ThrowInternal(err, "QUERY-Err3g", "Errors.Query.SQLStatment")
|
}
|
||||||
|
|
||||||
|
//go:embed user_notify_by_login_name.sql
|
||||||
|
var notifyUserByLoginNameQuery string
|
||||||
|
|
||||||
|
func (q *Queries) GetNotifyUserByLoginName(ctx context.Context, shouldTriggered bool, loginName string) (user *NotifyUser, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
if shouldTriggered {
|
||||||
|
triggerUserProjections(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = q.client.QueryRowContext(ctx, func(row *sql.Row) error {
|
loginName = strings.ToLower(loginName)
|
||||||
user, err = scan(row)
|
|
||||||
|
username := loginName
|
||||||
|
domainIndex := strings.LastIndex(loginName, "@")
|
||||||
|
var domainSuffix string
|
||||||
|
// split between the last @ (so ignore it if the login name ends with it)
|
||||||
|
if domainIndex > 0 && domainIndex != len(loginName)-1 {
|
||||||
|
domainSuffix = loginName[domainIndex+1:]
|
||||||
|
username = loginName[:domainIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
err = q.client.QueryRowContext(ctx,
|
||||||
|
func(row *sql.Row) error {
|
||||||
|
user, err = scanNotifyUser(row)
|
||||||
return err
|
return err
|
||||||
}, stmt, args...)
|
},
|
||||||
|
notifyUserByLoginNameQuery,
|
||||||
|
username,
|
||||||
|
domainSuffix,
|
||||||
|
loginName,
|
||||||
|
authz.GetInstance(ctx).InstanceID(),
|
||||||
|
)
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -706,58 +770,7 @@ func preparePreferredLoginNamesQuery() (string, []interface{}, error) {
|
|||||||
).ToSql()
|
).ToSql()
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
func scanUser(row *sql.Row) (*User, error) {
|
||||||
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
|
|
||||||
if err != nil {
|
|
||||||
return sq.SelectBuilder{}, nil
|
|
||||||
}
|
|
||||||
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
|
|
||||||
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(),
|
|
||||||
HumanEmailCol.identifier(),
|
|
||||||
HumanIsEmailVerifiedCol.identifier(),
|
|
||||||
HumanPhoneCol.identifier(),
|
|
||||||
HumanIsPhoneVerifiedCol.identifier(),
|
|
||||||
MachineUserIDCol.identifier(),
|
|
||||||
MachineNameCol.identifier(),
|
|
||||||
MachineDescriptionCol.identifier(),
|
|
||||||
MachineSecretCol.identifier(),
|
|
||||||
MachineAccessTokenTypeCol.identifier(),
|
|
||||||
countColumn.identifier(),
|
|
||||||
).
|
|
||||||
From(userTable.identifier()).
|
|
||||||
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
|
||||||
LeftJoin(join(MachineUserIDCol, UserIDCol)).
|
|
||||||
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
|
|
||||||
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
|
||||||
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
|
||||||
loginNamesArgs...).
|
|
||||||
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
|
|
||||||
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
|
||||||
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier()+db.Timetravel(call.Took(ctx)),
|
|
||||||
preferredLoginNameArgs...).
|
|
||||||
PlaceholderFormat(sq.Dollar),
|
|
||||||
func(row *sql.Row) (*User, error) {
|
|
||||||
u := new(User)
|
u := new(User)
|
||||||
var count int
|
var count int
|
||||||
preferredLoginName := sql.NullString{}
|
preferredLoginName := sql.NullString{}
|
||||||
@@ -844,7 +857,60 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return u, nil
|
return u, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*User, error)) {
|
||||||
|
loginNamesQuery, loginNamesArgs, err := prepareLoginNamesQuery()
|
||||||
|
if err != nil {
|
||||||
|
return sq.SelectBuilder{}, nil
|
||||||
}
|
}
|
||||||
|
preferredLoginNameQuery, preferredLoginNameArgs, err := preparePreferredLoginNamesQuery()
|
||||||
|
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(),
|
||||||
|
HumanEmailCol.identifier(),
|
||||||
|
HumanIsEmailVerifiedCol.identifier(),
|
||||||
|
HumanPhoneCol.identifier(),
|
||||||
|
HumanIsPhoneVerifiedCol.identifier(),
|
||||||
|
MachineUserIDCol.identifier(),
|
||||||
|
MachineNameCol.identifier(),
|
||||||
|
MachineDescriptionCol.identifier(),
|
||||||
|
MachineSecretCol.identifier(),
|
||||||
|
MachineAccessTokenTypeCol.identifier(),
|
||||||
|
countColumn.identifier(),
|
||||||
|
).
|
||||||
|
From(userTable.identifier()).
|
||||||
|
LeftJoin(join(HumanUserIDCol, UserIDCol)).
|
||||||
|
LeftJoin(join(MachineUserIDCol, UserIDCol)).
|
||||||
|
LeftJoin("("+loginNamesQuery+") AS "+userLoginNamesTable.alias+" ON "+
|
||||||
|
userLoginNamesUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||||
|
userLoginNamesInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier(),
|
||||||
|
loginNamesArgs...).
|
||||||
|
LeftJoin("("+preferredLoginNameQuery+") AS "+userPreferredLoginNameTable.alias+" ON "+
|
||||||
|
userPreferredLoginNameUserIDCol.identifier()+" = "+UserIDCol.identifier()+" AND "+
|
||||||
|
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier()+db.Timetravel(call.Took(ctx)),
|
||||||
|
preferredLoginNameArgs...).
|
||||||
|
PlaceholderFormat(sq.Dollar),
|
||||||
|
scanUser
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareProfileQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*Profile, error)) {
|
func prepareProfileQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (*Profile, error)) {
|
||||||
@@ -1055,7 +1121,10 @@ func prepareNotifyUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
|||||||
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier()+db.Timetravel(call.Took(ctx)),
|
userPreferredLoginNameInstanceIDCol.identifier()+" = "+UserInstanceIDCol.identifier()+db.Timetravel(call.Took(ctx)),
|
||||||
preferredLoginNameArgs...).
|
preferredLoginNameArgs...).
|
||||||
PlaceholderFormat(sq.Dollar),
|
PlaceholderFormat(sq.Dollar),
|
||||||
func(row *sql.Row) (*NotifyUser, error) {
|
scanNotifyUser
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanNotifyUser(row *sql.Row) (*NotifyUser, error) {
|
||||||
u := new(NotifyUser)
|
u := new(NotifyUser)
|
||||||
var count int
|
var count int
|
||||||
loginNames := database.TextArray[string]{}
|
loginNames := database.TextArray[string]{}
|
||||||
@@ -1136,7 +1205,6 @@ func prepareNotifyUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectB
|
|||||||
u.PasswordSet = notifyPasswordSet.Bool
|
u.PasswordSet = notifyPasswordSet.Bool
|
||||||
|
|
||||||
return u, nil
|
return u, nil
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func prepareUserUniqueQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
func prepareUserUniqueQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder, func(*sql.Row) (bool, error)) {
|
||||||
|
81
internal/query/user_by_id.sql
Normal file
81
internal/query/user_by_id.sql
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
WITH login_names AS (SELECT
|
||||||
|
u.id user_id
|
||||||
|
, u.instance_id
|
||||||
|
, u.resource_owner
|
||||||
|
, u.user_name
|
||||||
|
, d.name domain_name
|
||||||
|
, d.is_primary
|
||||||
|
, p.must_be_domain
|
||||||
|
, CASE WHEN p.must_be_domain
|
||||||
|
THEN concat(u.user_name, '@', d.name)
|
||||||
|
ELSE u.user_name
|
||||||
|
END login_name
|
||||||
|
FROM
|
||||||
|
projections.login_names3_users u
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
u.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $2)
|
||||||
|
OR (p.instance_id = $2 AND p.resource_owner = u.resource_owner)
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
u.id = $1
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
u.instance_id = d.instance_id
|
||||||
|
AND u.resource_owner = d.resource_owner
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
, u.creation_date
|
||||||
|
, u.change_date
|
||||||
|
, u.resource_owner
|
||||||
|
, u.sequence
|
||||||
|
, u.state
|
||||||
|
, u.type
|
||||||
|
, u.username
|
||||||
|
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln GROUP BY ln.user_id, ln.instance_id) login_names
|
||||||
|
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE ln.is_primary IS TRUE) preferred_login_name
|
||||||
|
, h.user_id
|
||||||
|
, h.first_name
|
||||||
|
, h.last_name
|
||||||
|
, h.nick_name
|
||||||
|
, h.display_name
|
||||||
|
, h.preferred_language
|
||||||
|
, h.gender
|
||||||
|
, h.avatar_key
|
||||||
|
, h.email
|
||||||
|
, h.is_email_verified
|
||||||
|
, h.phone
|
||||||
|
, h.is_phone_verified
|
||||||
|
, m.user_id
|
||||||
|
, m.name
|
||||||
|
, m.description
|
||||||
|
, m.secret
|
||||||
|
, m.access_token_type
|
||||||
|
, count(*) OVER ()
|
||||||
|
FROM projections.users10 u
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_humans h
|
||||||
|
ON
|
||||||
|
u.id = h.user_id
|
||||||
|
AND u.instance_id = h.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_machines m
|
||||||
|
ON
|
||||||
|
u.id = m.user_id
|
||||||
|
AND u.instance_id = m.instance_id
|
||||||
|
WHERE
|
||||||
|
u.id = $1
|
||||||
|
AND u.instance_id = $2
|
||||||
|
LIMIT 1
|
||||||
|
;
|
115
internal/query/user_by_login_name.sql
Normal file
115
internal/query/user_by_login_name.sql
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
WITH found_users AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
u.id
|
||||||
|
, u.instance_id
|
||||||
|
, u.resource_owner
|
||||||
|
, u.user_name
|
||||||
|
FROM
|
||||||
|
projections.login_names3_users u
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
u.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $4)
|
||||||
|
OR (p.instance_id = $4 AND p.resource_owner = u.resource_owner)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(p.must_be_domain IS TRUE AND user_name_lower = $1)
|
||||||
|
OR (p.must_be_domain IS FALSE AND user_name_lower = $3)
|
||||||
|
)
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
u.instance_id = d.instance_id
|
||||||
|
AND u.resource_owner = d.resource_owner
|
||||||
|
AND CASE WHEN p.must_be_domain THEN d.name_lower = $2 ELSE TRUE END
|
||||||
|
),
|
||||||
|
login_names AS (SELECT
|
||||||
|
fu.id user_id
|
||||||
|
, fu.instance_id
|
||||||
|
, fu.resource_owner
|
||||||
|
, fu.user_name
|
||||||
|
, d.name domain_name
|
||||||
|
, d.is_primary
|
||||||
|
, p.must_be_domain
|
||||||
|
, CASE WHEN p.must_be_domain
|
||||||
|
THEN concat(fu.user_name, '@', d.name)
|
||||||
|
ELSE fu.user_name
|
||||||
|
END login_name
|
||||||
|
FROM
|
||||||
|
found_users fu
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
fu.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $4)
|
||||||
|
OR (p.instance_id = $4 AND p.resource_owner = fu.resource_owner)
|
||||||
|
)
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
fu.instance_id = d.instance_id
|
||||||
|
AND fu.resource_owner = d.resource_owner
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
, u.creation_date
|
||||||
|
, u.change_date
|
||||||
|
, u.resource_owner
|
||||||
|
, u.sequence
|
||||||
|
, u.state
|
||||||
|
, u.type
|
||||||
|
, u.username
|
||||||
|
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln WHERE fu.id = ln.user_id GROUP BY ln.user_id, ln.instance_id) login_names
|
||||||
|
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE fu.id = ln.user_id AND ln.is_primary IS TRUE) preferred_login_name
|
||||||
|
, h.user_id
|
||||||
|
, h.first_name
|
||||||
|
, h.last_name
|
||||||
|
, h.nick_name
|
||||||
|
, h.display_name
|
||||||
|
, h.preferred_language
|
||||||
|
, h.gender
|
||||||
|
, h.avatar_key
|
||||||
|
, h.email
|
||||||
|
, h.is_email_verified
|
||||||
|
, h.phone
|
||||||
|
, h.is_phone_verified
|
||||||
|
, m.user_id
|
||||||
|
, m.name
|
||||||
|
, m.description
|
||||||
|
, m.secret
|
||||||
|
, m.access_token_type
|
||||||
|
, count(*) OVER ()
|
||||||
|
FROM found_users fu
|
||||||
|
JOIN
|
||||||
|
projections.users10 u
|
||||||
|
ON
|
||||||
|
fu.id = u.id
|
||||||
|
AND fu.instance_id = u.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_humans h
|
||||||
|
ON
|
||||||
|
fu.id = h.user_id
|
||||||
|
AND fu.instance_id = h.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_machines m
|
||||||
|
ON
|
||||||
|
fu.id = m.user_id
|
||||||
|
AND fu.instance_id = m.instance_id
|
||||||
|
WHERE
|
||||||
|
u.instance_id = $4
|
||||||
|
;
|
79
internal/query/user_notify_by_id.sql
Normal file
79
internal/query/user_notify_by_id.sql
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
WITH login_names AS (
|
||||||
|
SELECT
|
||||||
|
u.id user_id
|
||||||
|
, u.instance_id
|
||||||
|
, u.resource_owner
|
||||||
|
, u.user_name
|
||||||
|
, d.name domain_name
|
||||||
|
, d.is_primary
|
||||||
|
, p.must_be_domain
|
||||||
|
, CASE WHEN p.must_be_domain
|
||||||
|
THEN concat(u.user_name, '@', d.name)
|
||||||
|
ELSE u.user_name
|
||||||
|
END login_name
|
||||||
|
FROM
|
||||||
|
projections.login_names3_users u
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
u.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $2)
|
||||||
|
OR (p.instance_id = $2 AND p.resource_owner = u.resource_owner)
|
||||||
|
)
|
||||||
|
AND
|
||||||
|
u.id = $1
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
u.instance_id = d.instance_id
|
||||||
|
AND u.resource_owner = d.resource_owner
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
, u.creation_date
|
||||||
|
, u.change_date
|
||||||
|
, u.resource_owner
|
||||||
|
, u.sequence
|
||||||
|
, u.state
|
||||||
|
, u.type
|
||||||
|
, u.username
|
||||||
|
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln GROUP BY ln.user_id, ln.instance_id) login_names
|
||||||
|
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE ln.is_primary IS TRUE) preferred_login_name
|
||||||
|
, h.user_id
|
||||||
|
, h.first_name
|
||||||
|
, h.last_name
|
||||||
|
, h.nick_name
|
||||||
|
, h.display_name
|
||||||
|
, h.preferred_language
|
||||||
|
, h.gender
|
||||||
|
, h.avatar_key
|
||||||
|
, n.user_id
|
||||||
|
, n.last_email
|
||||||
|
, n.verified_email
|
||||||
|
, n.last_phone
|
||||||
|
, n.verified_phone
|
||||||
|
, n.password_set
|
||||||
|
, count(*) OVER ()
|
||||||
|
FROM projections.users10 u
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_humans h
|
||||||
|
ON
|
||||||
|
u.id = h.user_id
|
||||||
|
AND u.instance_id = h.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_notifications n
|
||||||
|
ON
|
||||||
|
u.id = n.user_id
|
||||||
|
AND u.instance_id = n.instance_id
|
||||||
|
WHERE
|
||||||
|
u.id = $1
|
||||||
|
AND u.instance_id = $2
|
||||||
|
LIMIT 1
|
||||||
|
;
|
112
internal/query/user_notify_by_login_name.sql
Normal file
112
internal/query/user_notify_by_login_name.sql
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
WITH found_users AS (
|
||||||
|
SELECT DISTINCT
|
||||||
|
u.id
|
||||||
|
, u.instance_id
|
||||||
|
, u.resource_owner
|
||||||
|
, u.user_name
|
||||||
|
FROM
|
||||||
|
projections.login_names3_users u
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
u.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $4)
|
||||||
|
OR (p.instance_id = $4 AND p.resource_owner = u.resource_owner)
|
||||||
|
)
|
||||||
|
AND (
|
||||||
|
(p.must_be_domain IS TRUE AND u.user_name_lower = $1)
|
||||||
|
OR (p.must_be_domain IS FALSE AND u.user_name_lower = $3)
|
||||||
|
)
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
u.instance_id = d.instance_id
|
||||||
|
AND u.resource_owner = d.resource_owner
|
||||||
|
AND CASE WHEN p.must_be_domain THEN d.name_lower = $2 ELSE TRUE END
|
||||||
|
),
|
||||||
|
login_names AS (SELECT
|
||||||
|
fu.id user_id
|
||||||
|
, fu.instance_id
|
||||||
|
, fu.resource_owner
|
||||||
|
, fu.user_name
|
||||||
|
, d.name domain_name
|
||||||
|
, d.is_primary
|
||||||
|
, p.must_be_domain
|
||||||
|
, CASE WHEN p.must_be_domain
|
||||||
|
THEN concat(fu.user_name, '@', d.name)
|
||||||
|
ELSE fu.user_name
|
||||||
|
END login_name
|
||||||
|
FROM
|
||||||
|
found_users fu
|
||||||
|
JOIN lateral (
|
||||||
|
SELECT
|
||||||
|
p.must_be_domain
|
||||||
|
FROM
|
||||||
|
projections.login_names3_policies p
|
||||||
|
WHERE
|
||||||
|
fu.instance_id = p.instance_id
|
||||||
|
AND (
|
||||||
|
(p.is_default IS TRUE AND p.instance_id = $4)
|
||||||
|
OR (p.instance_id = $4 AND p.resource_owner = fu.resource_owner)
|
||||||
|
)
|
||||||
|
ORDER BY is_default
|
||||||
|
LIMIT 1
|
||||||
|
) p ON TRUE
|
||||||
|
JOIN
|
||||||
|
projections.login_names3_domains d
|
||||||
|
ON
|
||||||
|
fu.instance_id = d.instance_id
|
||||||
|
AND fu.resource_owner = d.resource_owner
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
u.id
|
||||||
|
, u.creation_date
|
||||||
|
, u.change_date
|
||||||
|
, u.resource_owner
|
||||||
|
, u.sequence
|
||||||
|
, u.state
|
||||||
|
, u.type
|
||||||
|
, u.username
|
||||||
|
, (SELECT array_agg(ln.login_name)::TEXT[] login_names FROM login_names ln WHERE fu.id = ln.user_id GROUP BY ln.user_id, ln.instance_id) login_names
|
||||||
|
, (SELECT ln.login_name login_names_lower FROM login_names ln WHERE fu.id = ln.user_id AND ln.is_primary IS TRUE) preferred_login_name
|
||||||
|
, h.user_id
|
||||||
|
, h.first_name
|
||||||
|
, h.last_name
|
||||||
|
, h.nick_name
|
||||||
|
, h.display_name
|
||||||
|
, h.preferred_language
|
||||||
|
, h.gender
|
||||||
|
, h.avatar_key
|
||||||
|
, n.user_id
|
||||||
|
, n.last_email
|
||||||
|
, n.verified_email
|
||||||
|
, n.last_phone
|
||||||
|
, n.verified_phone
|
||||||
|
, n.password_set
|
||||||
|
, count(*) OVER ()
|
||||||
|
FROM found_users fu
|
||||||
|
JOIN
|
||||||
|
projections.users10 u
|
||||||
|
ON
|
||||||
|
fu.id = u.id
|
||||||
|
AND fu.instance_id = u.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_humans h
|
||||||
|
ON
|
||||||
|
fu.id = h.user_id
|
||||||
|
AND fu.instance_id = h.instance_id
|
||||||
|
LEFT JOIN
|
||||||
|
projections.users10_notifications n
|
||||||
|
ON
|
||||||
|
fu.id = n.user_id
|
||||||
|
AND fu.instance_id = n.instance_id
|
||||||
|
WHERE
|
||||||
|
u.instance_id = $4
|
||||||
|
;
|
Reference in New Issue
Block a user