chore: move the go code into a subfolder

This commit is contained in:
Florian Forster
2025-08-05 15:20:32 -07:00
parent 4ad22ba456
commit cd2921de26
2978 changed files with 373 additions and 300 deletions

32
apps/api/cmd/setup/01.go Normal file
View File

@@ -0,0 +1,32 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 01_sql/adminapi.sql
createAdminViews string
//go:embed 01_sql/auth.sql
createAuthViews string
//go:embed 01_sql/projections.sql
createProjections string
)
type ProjectionTable struct {
dbClient *sql.DB
}
func (mig *ProjectionTable) Execute(ctx context.Context, _ eventstore.Event) error {
stmt := createAdminViews + createAuthViews + createProjections
_, err := mig.dbClient.ExecContext(ctx, stmt)
return err
}
func (mig *ProjectionTable) String() string {
return "01_tables"
}

View File

@@ -0,0 +1,57 @@
CREATE SCHEMA adminapi;
CREATE TABLE adminapi.locks (
locker_id TEXT,
locked_until TIMESTAMPTZ(3),
view_name TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE adminapi.current_sequences (
view_name TEXT,
current_sequence BIGINT,
event_date TIMESTAMPTZ,
last_successful_spooler_run TIMESTAMPTZ,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE adminapi.failed_events (
view_name TEXT,
failed_sequence BIGINT,
failure_count SMALLINT,
err_msg TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, failed_sequence, instance_id)
);
CREATE TABLE adminapi.styling (
aggregate_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
label_policy_state INT2 NOT NULL DEFAULT 0::INT2,
sequence INT8 NULL,
primary_color TEXT NULL,
background_color TEXT NULL,
warn_color TEXT NULL,
font_color TEXT NULL,
primary_color_dark TEXT NULL,
background_color_dark TEXT NULL,
warn_color_dark TEXT NULL,
font_color_dark TEXT NULL,
logo_url TEXT NULL,
icon_url TEXT NULL,
logo_dark_url TEXT NULL,
icon_dark_url TEXT NULL,
font_url TEXT NULL,
err_msg_popup BOOL NULL,
disable_watermark BOOL NULL,
hide_login_name_suffix BOOL NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (aggregate_id, label_policy_state, instance_id)
);

View File

@@ -0,0 +1,229 @@
CREATE SCHEMA auth;
CREATE TABLE auth.locks (
locker_id TEXT,
locked_until TIMESTAMPTZ(3),
view_name TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE auth.current_sequences (
view_name TEXT,
current_sequence BIGINT,
event_date TIMESTAMPTZ,
last_successful_spooler_run TIMESTAMPTZ,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, instance_id)
);
CREATE TABLE auth.failed_events (
view_name TEXT,
failed_sequence BIGINT,
failure_count SMALLINT,
err_msg TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (view_name, failed_sequence, instance_id)
);
CREATE TABLE auth.users (
id TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner TEXT NULL,
user_state INT2 NULL,
password_set BOOL NULL,
password_change_required BOOL NULL,
password_change TIMESTAMPTZ NULL,
last_login TIMESTAMPTZ NULL,
user_name TEXT NULL,
login_names TEXT[] NULL,
preferred_login_name TEXT NULL,
first_name TEXT NULL,
last_name TEXT NULL,
nick_name TEXT NULL,
display_name TEXT NULL,
preferred_language TEXT NULL,
gender INT2 NULL,
email TEXT NULL,
is_email_verified BOOL NULL,
phone TEXT NULL,
is_phone_verified BOOL NULL,
country TEXT NULL,
locality TEXT NULL,
postal_code TEXT NULL,
region TEXT NULL,
street_address TEXT NULL,
otp_state INT2 NULL,
mfa_max_set_up INT2 NULL,
mfa_init_skipped TIMESTAMPTZ NULL,
sequence INT8 NULL,
init_required BOOL NULL,
username_change_required BOOL NULL,
machine_name TEXT NULL,
machine_description TEXT NULL,
user_type TEXT NULL,
u2f_tokens BYTEA NULL,
passwordless_tokens BYTEA NULL,
avatar_key TEXT NULL,
passwordless_init_required BOOL NULL,
password_init_required BOOL NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id)
);
CREATE TABLE auth.user_sessions (
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner TEXT NULL,
state INT2 NULL,
user_agent_id TEXT NULL,
user_id TEXT NULL,
user_name TEXT NULL,
password_verification TIMESTAMPTZ NULL,
second_factor_verification TIMESTAMPTZ NULL,
multi_factor_verification TIMESTAMPTZ NULL,
sequence INT8 NULL,
second_factor_verification_type INT2 NULL,
multi_factor_verification_type INT2 NULL,
user_display_name TEXT NULL,
login_name TEXT NULL,
external_login_verification TIMESTAMPTZ NULL,
selected_idp_config_id TEXT NULL,
passwordless_verification TIMESTAMPTZ NULL,
avatar_key TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (user_agent_id, user_id, instance_id)
);
CREATE TABLE auth.user_external_idps (
external_user_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
user_id TEXT NULL,
idp_name TEXT NULL,
user_display_name TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
resource_owner TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (external_user_id, idp_config_id, instance_id)
);
CREATE TABLE auth.tokens (
id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner TEXT NULL,
application_id TEXT NULL,
user_agent_id TEXT NULL,
user_id TEXT NULL,
expiration TIMESTAMPTZ NULL,
sequence INT8 NULL,
scopes TEXT[] NULL,
audience TEXT[] NULL,
preferred_language TEXT NULL,
refresh_token_id TEXT NULL,
is_pat BOOL NOT NULL DEFAULT false,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id)
);
CREATE INDEX user_user_agent_idx ON auth.tokens (user_id, user_agent_id);
CREATE TABLE auth.refresh_tokens (
id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner TEXT NULL,
token TEXT NULL,
client_id TEXT NOT NULL,
user_agent_id TEXT NOT NULL,
user_id TEXT NOT NULL,
auth_time TIMESTAMPTZ NULL,
idle_expiration TIMESTAMPTZ NULL,
expiration TIMESTAMPTZ NULL,
sequence INT8 NULL,
scopes TEXT[] NULL,
audience TEXT[] NULL,
amr TEXT[] NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id)
);
CREATE UNIQUE INDEX unique_client_user_index ON auth.refresh_tokens (client_id, user_agent_id, user_id);
CREATE TABLE auth.org_project_mapping (
org_id TEXT NOT NULL,
project_id TEXT NOT NULL,
project_grant_id TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (org_id, project_id, instance_id)
);
CREATE TABLE auth.idp_providers (
aggregate_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
name TEXT NULL,
idp_config_type INT2 NULL,
idp_provider_type INT2 NULL,
idp_state INT2 NULL,
styling_type INT2 NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (aggregate_id, idp_config_id, instance_id)
);
CREATE TABLE auth.idp_configs (
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
aggregate_id TEXT NULL,
name TEXT NULL,
idp_state INT2 NULL,
idp_provider_type INT2 NULL,
is_oidc BOOL NULL,
oidc_client_id TEXT NULL,
oidc_client_secret JSONB NULL,
oidc_issuer TEXT NULL,
oidc_scopes TEXT[] NULL,
oidc_idp_display_name_mapping INT2 NULL,
oidc_idp_username_mapping INT2 NULL,
styling_type INT2 NULL,
oauth_authorization_endpoint TEXT NULL,
oauth_token_endpoint TEXT NULL,
auto_register BOOL NULL,
jwt_endpoint TEXT NULL,
jwt_keys_endpoint TEXT NULL,
jwt_header_name TEXT NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (idp_config_id, instance_id)
);
CREATE TABLE auth.auth_requests (
id TEXT NOT NULL,
request JSONB NULL,
code TEXT NULL,
request_type INT2 NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
instance_id TEXT NOT NULL,
PRIMARY KEY (id, instance_id)
);
CREATE INDEX auth_code_idx ON auth.auth_requests (code);

View File

@@ -0,0 +1,28 @@
CREATE TABLE projections.locks (
locker_id TEXT,
locked_until TIMESTAMPTZ(3),
projection_name TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (projection_name, instance_id)
);
CREATE TABLE projections.current_sequences (
projection_name TEXT,
aggregate_type TEXT,
current_sequence BIGINT,
instance_id TEXT NOT NULL,
timestamp TIMESTAMPTZ,
PRIMARY KEY (projection_name, aggregate_type, instance_id)
);
CREATE TABLE projections.failed_events (
projection_name TEXT,
failed_sequence BIGINT,
failure_count SMALLINT,
error TEXT,
instance_id TEXT NOT NULL,
PRIMARY KEY (projection_name, failed_sequence, instance_id)
);

38
apps/api/cmd/setup/02.go Normal file
View File

@@ -0,0 +1,38 @@
package setup
import (
"context"
"database/sql"
"github.com/zitadel/zitadel/internal/eventstore"
)
const (
createAssets = `
CREATE TABLE system.assets (
instance_id TEXT,
asset_type TEXT,
resource_owner TEXT,
name TEXT,
content_type TEXT,
hash TEXT GENERATED ALWAYS AS (md5(data)) STORED,
data BYTEA,
updated_at TIMESTAMPTZ,
PRIMARY KEY (instance_id, resource_owner, name)
);
`
)
type AssetTable struct {
dbClient *sql.DB
}
func (mig *AssetTable) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, createAssets)
return err
}
func (mig *AssetTable) String() string {
return "02_assets"
}

206
apps/api/cmd/setup/03.go Normal file
View File

@@ -0,0 +1,206 @@
package setup
import (
"context"
"fmt"
"os"
"strings"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/cache/connector"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/crypto"
crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
)
type FirstInstance struct {
InstanceName string
DefaultLanguage language.Tag
Org command.InstanceOrgSetup
MachineKeyPath string
PatPath string
LoginClientPatPath string
Features *command.InstanceFeatures
Skip bool
instanceSetup command.InstanceSetup
userEncryptionKey *crypto.KeyConfig
smtpEncryptionKey *crypto.KeyConfig
oidcEncryptionKey *crypto.KeyConfig
masterKey string
db *database.DB
es *eventstore.Eventstore
defaults systemdefaults.SystemDefaults
zitadelRoles []authz.RoleMapping
externalDomain string
externalSecure bool
externalPort uint16
domain string
}
func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error {
if mig.Skip {
return nil
}
keyStorage, err := mig.verifyEncryptionKeys(ctx)
if err != nil {
return err
}
userAlg, err := crypto.NewAESCrypto(mig.userEncryptionKey, keyStorage)
if err != nil {
return err
}
smtpEncryption, err := crypto.NewAESCrypto(mig.smtpEncryptionKey, keyStorage)
if err != nil {
return err
}
oidcEncryption, err := crypto.NewAESCrypto(mig.oidcEncryptionKey, keyStorage)
if err != nil {
return err
}
cmd, err := command.StartCommands(ctx,
mig.es,
connector.Connectors{},
mig.defaults,
mig.zitadelRoles,
nil,
nil,
mig.externalDomain,
mig.externalSecure,
mig.externalPort,
nil,
nil,
smtpEncryption,
nil,
userAlg,
nil,
oidcEncryption,
nil,
nil,
nil,
nil,
nil,
0,
0,
0,
nil,
)
if err != nil {
return err
}
mig.instanceSetup.InstanceName = mig.InstanceName
mig.instanceSetup.CustomDomain = mig.externalDomain
mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage
mig.instanceSetup.Org = mig.Org
// check if username is email style or else append @<orgname>.<custom-domain>
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
if err != nil {
return err
}
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + orgDomain
}
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
if mig.instanceSetup.Org.Human.Email.Address == "" {
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
if err != nil {
return err
}
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + orgDomain)
}
}
_, token, key, loginClientToken, _, err := cmd.SetUpInstance(ctx, &mig.instanceSetup)
if err != nil {
return err
}
if (mig.instanceSetup.Org.Machine != nil &&
((mig.instanceSetup.Org.Machine.Pat != nil && token == "") ||
(mig.instanceSetup.Org.Machine.MachineKey != nil && key == nil))) ||
(mig.instanceSetup.Org.LoginClient != nil &&
(mig.instanceSetup.Org.LoginClient.Pat != nil && loginClientToken == "")) {
return err
}
return mig.outputMachineAuthentication(key, token, loginClientToken)
}
func (mig *FirstInstance) verifyEncryptionKeys(ctx context.Context) (*crypto_db.Database, error) {
keyStorage, err := crypto_db.NewKeyStorage(mig.db, mig.masterKey)
if err != nil {
return nil, fmt.Errorf("cannot start key storage: %w", err)
}
if err = verifyKey(ctx, mig.userEncryptionKey, keyStorage); err != nil {
return nil, err
}
if err = verifyKey(ctx, mig.smtpEncryptionKey, keyStorage); err != nil {
return nil, err
}
if err = verifyKey(ctx, mig.oidcEncryptionKey, keyStorage); err != nil {
return nil, err
}
return keyStorage, nil
}
func (mig *FirstInstance) outputMachineAuthentication(key *command.MachineKey, token, loginClientToken string) error {
if key != nil {
keyDetails, err := key.Detail()
if err != nil {
return err
}
if err := outputStdoutOrPath(mig.MachineKeyPath, string(keyDetails)); err != nil {
return err
}
}
if token != "" {
if err := outputStdoutOrPath(mig.PatPath, token); err != nil {
return err
}
}
if loginClientToken != "" {
if err := outputStdoutOrPath(mig.LoginClientPatPath, loginClientToken); err != nil {
return err
}
}
return nil
}
func outputStdoutOrPath(path string, content string) (err error) {
f := os.Stdout
if path != "" {
f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
}
_, err = fmt.Fprintln(f, content)
return err
}
func (mig *FirstInstance) String() string {
return "03_default_instance"
}
func verifyKey(ctx context.Context, key *crypto.KeyConfig, storage crypto.KeyStorage) (err error) {
_, err = crypto.LoadKey(key.EncryptionKeyID, storage)
if err == nil {
return nil
}
k, err := crypto.NewKey(key.EncryptionKeyID)
if err != nil {
return err
}
return storage.CreateKeys(ctx, k)
}

27
apps/api/cmd/setup/05.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 05.sql
lastFailedStmts string
)
type LastFailed struct {
dbClient *sql.DB
}
func (mig *LastFailed) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, lastFailedStmts)
return err
}
func (mig *LastFailed) String() string {
return "05_last_failed"
}

11
apps/api/cmd/setup/05.sql Normal file
View File

@@ -0,0 +1,11 @@
CREATE INDEX IF NOT EXISTS current_sequences_instance_id_idx ON adminapi.current_sequences (instance_id);
CREATE INDEX IF NOT EXISTS current_sequences_instance_id_idx ON auth.current_sequences (instance_id);
CREATE INDEX IF NOT EXISTS current_sequences_instance_id_idx ON projections.current_sequences (instance_id);
CREATE INDEX IF NOT EXISTS failed_events_instance_id_idx ON adminapi.failed_events (instance_id);
CREATE INDEX IF NOT EXISTS failed_events_instance_id_idx ON auth.failed_events (instance_id);
CREATE INDEX IF NOT EXISTS failed_events_instance_id_idx ON projections.failed_events (instance_id);
ALTER TABLE adminapi.failed_events ADD COLUMN IF NOT EXISTS last_failed TIMESTAMPTZ;
ALTER TABLE auth.failed_events ADD COLUMN IF NOT EXISTS last_failed TIMESTAMPTZ;
ALTER TABLE projections.failed_events ADD COLUMN IF NOT EXISTS last_failed TIMESTAMPTZ;

30
apps/api/cmd/setup/06.go Normal file
View File

@@ -0,0 +1,30 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 06/adminapi.sql
createAdminViews06 string
//go:embed 06/auth.sql
createAuthViews06 string
)
type OwnerRemoveColumns struct {
dbClient *sql.DB
}
func (mig *OwnerRemoveColumns) Execute(ctx context.Context, _ eventstore.Event) error {
stmt := createAdminViews06 + createAuthViews06
_, err := mig.dbClient.ExecContext(ctx, stmt)
return err
}
func (mig *OwnerRemoveColumns) String() string {
return "06_resource_owner_columns"
}

View File

@@ -0,0 +1,30 @@
CREATE TABLE adminapi.styling2 (
aggregate_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
label_policy_state INT2 NOT NULL DEFAULT 0::INT2,
sequence INT8 NULL,
primary_color TEXT NULL,
background_color TEXT NULL,
warn_color TEXT NULL,
font_color TEXT NULL,
primary_color_dark TEXT NULL,
background_color_dark TEXT NULL,
warn_color_dark TEXT NULL,
font_color_dark TEXT NULL,
logo_url TEXT NULL,
icon_url TEXT NULL,
logo_dark_url TEXT NULL,
icon_dark_url TEXT NULL,
font_url TEXT NULL,
err_msg_popup BOOL NULL,
disable_watermark BOOL NULL,
hide_login_name_suffix BOOL NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, aggregate_id, label_policy_state)
);
CREATE INDEX st2_owner_removed_idx ON adminapi.styling2 (owner_removed);

View File

@@ -0,0 +1,124 @@
CREATE TABLE auth.users2 (
id TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
resource_owner TEXT NULL,
user_state INT2 NULL,
password_set BOOL NULL,
password_change_required BOOL NULL,
password_change TIMESTAMPTZ NULL,
last_login TIMESTAMPTZ NULL,
user_name TEXT NULL,
login_names TEXT[] NULL,
preferred_login_name TEXT NULL,
first_name TEXT NULL,
last_name TEXT NULL,
nick_name TEXT NULL,
display_name TEXT NULL,
preferred_language TEXT NULL,
gender INT2 NULL,
email TEXT NULL,
is_email_verified BOOL NULL,
phone TEXT NULL,
is_phone_verified BOOL NULL,
country TEXT NULL,
locality TEXT NULL,
postal_code TEXT NULL,
region TEXT NULL,
street_address TEXT NULL,
otp_state INT2 NULL,
mfa_max_set_up INT2 NULL,
mfa_init_skipped TIMESTAMPTZ NULL,
sequence INT8 NULL,
init_required BOOL NULL,
username_change_required BOOL NULL,
machine_name TEXT NULL,
machine_description TEXT NULL,
user_type TEXT NULL,
u2f_tokens BYTEA NULL,
passwordless_tokens BYTEA NULL,
avatar_key TEXT NULL,
passwordless_init_required BOOL NULL,
password_init_required BOOL NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, id)
);
CREATE INDEX u2_owner_removed_idx ON auth.users2 (owner_removed);
CREATE TABLE auth.user_external_idps2 (
external_user_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
user_id TEXT NULL,
idp_name TEXT NULL,
user_display_name TEXT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
resource_owner TEXT NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, external_user_id, idp_config_id)
);
CREATE INDEX ext_idps2_owner_removed_idx ON auth.user_external_idps2 (owner_removed);
CREATE TABLE auth.org_project_mapping2 (
org_id TEXT NOT NULL,
project_id TEXT NOT NULL,
project_grant_id TEXT NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, org_id, project_id)
);
CREATE INDEX org_proj_m2_owner_removed_idx ON auth.org_project_mapping2 (owner_removed);
CREATE TABLE auth.idp_providers2 (
aggregate_id TEXT NOT NULL,
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
name TEXT NULL,
idp_config_type INT2 NULL,
idp_provider_type INT2 NULL,
idp_state INT2 NULL,
styling_type INT2 NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, aggregate_id, idp_config_id)
);
CREATE INDEX idp_prov2_owner_removed_idx ON auth.idp_providers2 (owner_removed);
CREATE TABLE auth.idp_configs2 (
idp_config_id TEXT NOT NULL,
creation_date TIMESTAMPTZ NULL,
change_date TIMESTAMPTZ NULL,
sequence INT8 NULL,
aggregate_id TEXT NULL,
name TEXT NULL,
idp_state INT2 NULL,
idp_provider_type INT2 NULL,
is_oidc BOOL NULL,
oidc_client_id TEXT NULL,
oidc_client_secret JSONB NULL,
oidc_issuer TEXT NULL,
oidc_scopes TEXT[] NULL,
oidc_idp_display_name_mapping INT2 NULL,
oidc_idp_username_mapping INT2 NULL,
styling_type INT2 NULL,
oauth_authorization_endpoint TEXT NULL,
oauth_token_endpoint TEXT NULL,
auto_register BOOL NULL,
jwt_endpoint TEXT NULL,
jwt_keys_endpoint TEXT NULL,
jwt_header_name TEXT NULL,
instance_id TEXT NOT NULL,
owner_removed BOOL DEFAULT false,
PRIMARY KEY (instance_id, idp_config_id)
);
CREATE INDEX idp_conf2_owner_removed_idx ON auth.idp_configs2 (owner_removed);

34
apps/api/cmd/setup/07.go Normal file
View File

@@ -0,0 +1,34 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"strings"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 07/logstore.sql
createLogstoreSchema07 string
//go:embed 07/access.sql
createAccessLogsTable07 string
//go:embed 07/execution.sql
createExecutionLogsTable07 string
)
type LogstoreTables struct {
dbClient *sql.DB
username string
}
func (mig *LogstoreTables) Execute(ctx context.Context, _ eventstore.Event) error {
stmt := strings.ReplaceAll(createLogstoreSchema07, "%[1]s", mig.username) + createAccessLogsTable07 + createExecutionLogsTable07
_, err := mig.dbClient.ExecContext(ctx, stmt)
return err
}
func (mig *LogstoreTables) String() string {
return "07_logstore"
}

View File

@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS logstore.access (
log_date TIMESTAMPTZ NOT NULL
, protocol INT NOT NULL
, request_url TEXT NOT NULL
, response_status INT NOT NULL
, request_headers JSONB
, response_headers JSONB
, instance_id TEXT NOT NULL
, project_id TEXT NOT NULL
, requested_domain TEXT
, requested_host TEXT
);
CREATE INDEX protocol_date_desc ON logstore.access (instance_id, protocol, log_date DESC) INCLUDE (request_url, response_status, request_headers);

View File

@@ -0,0 +1,11 @@
CREATE TABLE IF NOT EXISTS logstore.execution (
log_date TIMESTAMPTZ NOT NULL
, took INTERVAL
, message TEXT NOT NULL
, loglevel INT NOT NULL
, instance_id TEXT NOT NULL
, action_id TEXT NOT NULL
, metadata JSONB
);
CREATE INDEX log_date_desc ON logstore.execution (instance_id, log_date DESC) INCLUDE (took);

View File

@@ -0,0 +1,3 @@
CREATE SCHEMA IF NOT EXISTS logstore;
GRANT ALL ON ALL TABLES IN SCHEMA logstore TO "%[1]s";

27
apps/api/cmd/setup/08.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 08/08.sql
tokenIndexes08 string
)
type AuthTokenIndexes struct {
dbClient *database.DB
}
func (mig *AuthTokenIndexes) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, tokenIndexes08)
return err
}
func (mig *AuthTokenIndexes) String() string {
return "08_auth_token_indexes"
}

View File

@@ -0,0 +1,5 @@
CREATE INDEX IF NOT EXISTS inst_refresh_tkn_idx ON auth.tokens(instance_id, refresh_token_id);
CREATE INDEX IF NOT EXISTS inst_app_tkn_idx ON auth.tokens(instance_id, application_id);
CREATE INDEX IF NOT EXISTS inst_ro_tkn_idx ON auth.tokens(instance_id, resource_owner);
DROP INDEX IF EXISTS auth.user_user_agent_idx;
CREATE INDEX IF NOT EXISTS inst_usr_agnt_tkn_idx ON auth.tokens(instance_id, user_id, user_agent_id);

79
apps/api/cmd/setup/10.go Normal file
View File

@@ -0,0 +1,79 @@
package setup
import (
"context"
"database/sql"
_ "embed"
"time"
"github.com/cockroachdb/cockroach-go/v2/crdb"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 10/10_create_temp_table.sql
correctCreationDate10CreateTable string
//go:embed 10/10_fill_table.sql
correctCreationDate10FillTable string
//go:embed 10/10_update.sql
correctCreationDate10Update string
//go:embed 10/10_count_wrong_events.sql
correctCreationDate10CountWrongEvents string
//go:embed 10/10_empty_table.sql
correctCreationDate10Truncate string
)
type CorrectCreationDate struct {
dbClient *database.DB
FailAfter time.Duration
}
func (mig *CorrectCreationDate) Execute(ctx context.Context, _ eventstore.Event) (err error) {
ctx, cancel := context.WithTimeout(ctx, mig.FailAfter)
defer cancel()
for i := 0; ; i++ {
logging.WithFields("mig", mig.String(), "iteration", i).Debug("start iteration")
var affected int64
err = crdb.ExecuteTx(ctx, mig.dbClient.DB, nil, func(tx *sql.Tx) error {
_, err := tx.ExecContext(ctx, correctCreationDate10CreateTable)
if err != nil {
return err
}
logging.WithFields("mig", mig.String(), "iteration", i).Debug("temp table created")
_, err = tx.ExecContext(ctx, correctCreationDate10Truncate)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx, correctCreationDate10FillTable)
if err != nil {
return err
}
logging.WithFields("mig", mig.String(), "iteration", i).Debug("temp table filled")
res := tx.QueryRowContext(ctx, correctCreationDate10CountWrongEvents)
if err := res.Scan(&affected); err != nil || affected == 0 {
return err
}
_, err = tx.ExecContext(ctx, correctCreationDate10Update)
if err != nil {
return err
}
logging.WithFields("mig", mig.String(), "iteration", i, "count", affected).Debug("creation dates updated")
return nil
})
logging.WithFields("mig", mig.String(), "iteration", i).Debug("end iteration")
if affected == 0 || err != nil {
return err
}
}
}
func (mig *CorrectCreationDate) String() string {
return "10_correct_creation_date"
}

View File

@@ -0,0 +1 @@
SELECT COUNT(*) FROM wrong_events

View File

@@ -0,0 +1,6 @@
CREATE TEMPORARY TABLE IF NOT EXISTS wrong_events (
instance_id TEXT
, event_sequence BIGINT
, current_cd TIMESTAMPTZ
, next_cd TIMESTAMPTZ
);

View File

@@ -0,0 +1 @@
TRUNCATE wrong_events

View File

@@ -0,0 +1,19 @@
INSERT INTO wrong_events (
SELECT * FROM (
SELECT
instance_id
, event_sequence
, creation_date AS current_cd
, lead(creation_date) OVER (
PARTITION BY instance_id
ORDER BY event_sequence DESC
) AS next_cd
FROM
eventstore.events
WHERE
"position" IS NULL
) sub WHERE
current_cd < next_cd
ORDER BY
event_sequence DESC
);

View File

@@ -0,0 +1,10 @@
UPDATE
eventstore.events e
SET
creation_date = we.next_cd
, "position" = (EXTRACT(EPOCH FROM we.next_cd))
FROM
wrong_events we
WHERE
e.event_sequence = we.event_sequence
AND e.instance_id = we.instance_id;

27
apps/api/cmd/setup/12.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 12/12_add_otp_columns.sql
addOTPColumns string
)
type AddOTPColumns struct {
dbClient *database.DB
}
func (mig *AddOTPColumns) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addOTPColumns)
return err
}
func (mig *AddOTPColumns) String() string {
return "12_auth_users_otp_columns"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE auth.users2 ADD COLUMN otp_sms_added BOOL DEFAULT false;
ALTER TABLE auth.users2 ADD COLUMN otp_email_added BOOL DEFAULT false;

27
apps/api/cmd/setup/13.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 13/13_fix_quota_constraints.sql
fixQuotaConstraints string
)
type FixQuotaConstraints struct {
dbClient *database.DB
}
func (mig *FixQuotaConstraints) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, fixQuotaConstraints)
return err
}
func (mig *FixQuotaConstraints) String() string {
return "13_fix_quota_constraints"
}

View File

@@ -0,0 +1,4 @@
ALTER TABLE IF EXISTS projections.quotas ALTER COLUMN from_anchor DROP NOT NULL;
ALTER TABLE IF EXISTS projections.quotas ALTER COLUMN amount DROP NOT NULL;
ALTER TABLE IF EXISTS projections.quotas ALTER COLUMN interval DROP NOT NULL;
ALTER TABLE IF EXISTS projections.quotas ALTER COLUMN limit_usage DROP NOT NULL;

67
apps/api/cmd/setup/14.go Normal file
View File

@@ -0,0 +1,67 @@
package setup
import (
"context"
"database/sql"
"embed"
"errors"
"strings"
"github.com/jackc/pgx/v5/pgconn"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 14/*.sql
newEventsTable embed.FS
)
type NewEventsTable struct {
dbClient *database.DB
}
func (mig *NewEventsTable) Execute(ctx context.Context, _ eventstore.Event) error {
// if events already exists events2 is created during a setup job
var count int
err := mig.dbClient.QueryRowContext(ctx,
func(row *sql.Row) error {
if err := row.Scan(&count); err != nil {
return err
}
return row.Err()
},
"SELECT count(*) FROM information_schema.tables WHERE table_schema = 'eventstore' AND table_name like 'events2'",
)
if err != nil || count == 1 {
return err
}
statements, err := readStatements(newEventsTable, "14")
if err != nil {
return err
}
for _, stmt := range statements {
stmt.query = strings.ReplaceAll(stmt.query, "{{.username}}", mig.dbClient.Username())
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
_, err = mig.dbClient.ExecContext(ctx, stmt.query)
if err != nil {
return err
}
}
return nil
}
func (mig *NewEventsTable) String() string {
return "14_events_push"
}
func (mig *NewEventsTable) ContinueOnErr(err error) bool {
pgErr := new(pgconn.PgError)
if errors.As(err, &pgErr) {
return pgErr.Code == "42P01"
}
return false
}

View File

@@ -0,0 +1 @@
ALTER TABLE eventstore.events RENAME TO events_old;

View File

@@ -0,0 +1,31 @@
CREATE TABLE eventstore.events2 (
instance_id,
aggregate_type,
aggregate_id,
event_type,
"sequence",
revision,
created_at,
payload,
creator,
"owner",
"position",
in_tx_order
) AS SELECT
instance_id,
aggregate_type,
aggregate_id,
event_type,
event_sequence,
substr(aggregate_version, 2)::SMALLINT,
creation_date,
event_data,
editor_user,
resource_owner,
EXTRACT(EPOCH FROM creation_date),
event_sequence
FROM eventstore.events_old;

View File

@@ -0,0 +1,4 @@
BEGIN;
ALTER TABLE eventstore.events2 DROP CONSTRAINT IF EXISTS events2_pkey;
ALTER TABLE eventstore.events2 ADD PRIMARY KEY (instance_id, aggregate_type, aggregate_id, "sequence");
COMMIT;

View File

@@ -0,0 +1,7 @@
ALTER TABLE eventstore.events2 ALTER COLUMN event_type SET NOT NULL,
ALTER COLUMN revision SET NOT NULL,
ALTER COLUMN created_at SET NOT NULL,
ALTER COLUMN creator SET NOT NULL,
ALTER COLUMN "owner" SET NOT NULL,
ALTER COLUMN "position" SET NOT NULL,
ALTER COLUMN in_tx_order SET NOT NULL;

View File

@@ -0,0 +1,3 @@
CREATE INDEX IF NOT EXISTS es_active_instances ON eventstore.events2 (created_at DESC, instance_id);
CREATE INDEX IF NOT EXISTS es_wm ON eventstore.events2 (aggregate_id, instance_id, aggregate_type, event_type);
CREATE INDEX IF NOT EXISTS es_projection ON eventstore.events2 (instance_id, aggregate_type, event_type, "position");

39
apps/api/cmd/setup/15.go Normal file
View File

@@ -0,0 +1,39 @@
package setup
import (
"context"
"embed"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 15/*.sql
currentProjectionState embed.FS
)
type CurrentProjectionState struct {
dbClient *database.DB
}
func (mig *CurrentProjectionState) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(currentProjectionState, "15")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
_, err = mig.dbClient.ExecContext(ctx, stmt.query)
if err != nil {
return err
}
}
return nil
}
func (mig *CurrentProjectionState) String() string {
return "15_current_projection_state"
}

View File

@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS projections.failed_events2 (
projection_name TEXT NOT NULL
, instance_id TEXT NOT NULL
, aggregate_type TEXT NOT NULL
, aggregate_id TEXT NOT NULL
, event_creation_date TIMESTAMPTZ NOT NULL
, failed_sequence INT8 NOT NULL
, failure_count INT2 NULL DEFAULT 0
, error TEXT
, last_failed TIMESTAMPTZ
, PRIMARY KEY (projection_name, instance_id, aggregate_type, aggregate_id, failed_sequence)
);
CREATE INDEX IF NOT EXISTS fe2_instance_id_idx on projections.failed_events2 (instance_id);

View File

@@ -0,0 +1,26 @@
INSERT INTO projections.failed_events2 (
projection_name
, instance_id
, aggregate_type
, aggregate_id
, event_creation_date
, failed_sequence
, failure_count
, error
, last_failed
) SELECT
fe.projection_name
, fe.instance_id
, e.aggregate_type
, e.aggregate_id
, e.created_at
, e.sequence
, fe.failure_count
, fe.error
, fe.last_failed
FROM
projections.failed_events fe
JOIN eventstore.events2 e ON
e.instance_id = fe.instance_id
AND e.sequence = fe.failed_sequence
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,26 @@
INSERT INTO projections.failed_events2 (
projection_name
, instance_id
, aggregate_type
, aggregate_id
, event_creation_date
, failed_sequence
, failure_count
, error
, last_failed
) SELECT
fe.view_name
, fe.instance_id
, e.aggregate_type
, e.aggregate_id
, e.created_at
, e.sequence
, fe.failure_count
, fe.err_msg
, fe.last_failed
FROM
adminapi.failed_events fe
JOIN eventstore.events2 e ON
e.instance_id = fe.instance_id
AND e.sequence = fe.failed_sequence
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,26 @@
INSERT INTO projections.failed_events2 (
projection_name
, instance_id
, aggregate_type
, aggregate_id
, event_creation_date
, failed_sequence
, failure_count
, error
, last_failed
) SELECT
fe.view_name
, fe.instance_id
, e.aggregate_type
, e.aggregate_id
, e.created_at
, e.sequence
, fe.failure_count
, fe.err_msg
, fe.last_failed
FROM
auth.failed_events fe
JOIN eventstore.events2 e ON
e.instance_id = fe.instance_id
AND e.sequence = fe.failed_sequence
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS projections.current_states (
projection_name TEXT NOT NULL
, instance_id TEXT NOT NULL
, last_updated TIMESTAMPTZ
, aggregate_id TEXT
, aggregate_type TEXT
, "sequence" INT8
, event_date TIMESTAMPTZ
, "position" DECIMAL
, PRIMARY KEY (projection_name, instance_id)
);
CREATE INDEX IF NOT EXISTS cs_instance_id_idx ON projections.current_states (instance_id);

View File

@@ -0,0 +1,28 @@
INSERT INTO projections.current_states (
projection_name
, instance_id
, event_date
, "position"
, last_updated
) SELECT
cs.projection_name
, cs.instance_id
, e.created_at
, e.position
, cs.timestamp
FROM
projections.current_sequences cs
JOIN eventstore.events2 e ON
e.instance_id = cs.instance_id
AND e.aggregate_type = cs.aggregate_type
AND e.sequence = cs.current_sequence
AND cs.current_sequence = (
SELECT
MAX(cs2.current_sequence)
FROM
projections.current_sequences cs2
WHERE
cs.projection_name = cs2.projection_name
AND cs.instance_id = cs2.instance_id
)
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,27 @@
INSERT INTO projections.current_states (
projection_name
, instance_id
, event_date
, "position"
, last_updated
) SELECT
cs.view_name
, cs.instance_id
, e.created_at
, e.position
, cs.last_successful_spooler_run
FROM
adminapi.current_sequences cs
JOIN eventstore.events2 e ON
e.instance_id = cs.instance_id
AND e.sequence = cs.current_sequence
AND cs.current_sequence = (
SELECT
MAX(cs2.current_sequence)
FROM
adminapi.current_sequences cs2
WHERE
cs.view_name = cs2.view_name
AND cs.instance_id = cs2.instance_id
)
ON CONFLICT DO NOTHING;

View File

@@ -0,0 +1,27 @@
INSERT INTO projections.current_states (
projection_name
, instance_id
, event_date
, "position"
, last_updated
) SELECT
cs.view_name
, cs.instance_id
, e.created_at
, e.position
, cs.last_successful_spooler_run
FROM
auth.current_sequences cs
JOIN eventstore.events2 e ON
e.instance_id = cs.instance_id
AND e.sequence = cs.current_sequence
AND cs.current_sequence = (
SELECT
MAX(cs2.current_sequence)
FROM
auth.current_sequences cs2
WHERE
cs.view_name = cs2.view_name
AND cs.instance_id = cs2.instance_id
)
ON CONFLICT DO NOTHING;

34
apps/api/cmd/setup/16.go Normal file
View File

@@ -0,0 +1,34 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 16.sql
uniqueConstraintLower string
)
type UniqueConstraintToLower struct {
dbClient *database.DB
}
func (mig *UniqueConstraintToLower) Execute(ctx context.Context, _ eventstore.Event) error {
res, err := mig.dbClient.ExecContext(ctx, uniqueConstraintLower)
if err != nil {
return err
}
count, err := res.RowsAffected()
logging.WithFields("count", count).Info("unique constraints updated")
return err
}
func (mig *UniqueConstraintToLower) String() string {
return "16_unique_constraint_lower"
}

13
apps/api/cmd/setup/16.sql Normal file
View File

@@ -0,0 +1,13 @@
WITH casesensitive as (
SELECT instance_id, unique_type, lower(unique_field)
FROM eventstore.unique_constraints
GROUP BY instance_id, unique_type, lower(unique_field)
HAVING count(unique_field) < 2
)
UPDATE eventstore.unique_constraints c
SET unique_field = casesensitive.lower
FROM casesensitive
WHERE c.instance_id = casesensitive.instance_id
AND c.unique_type = casesensitive.unique_type
AND lower(c.unique_field) = casesensitive.lower
AND c.unique_field <> casesensitive.lower;

27
apps/api/cmd/setup/17.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 17.sql
addOffsetField string
)
type AddOffsetToCurrentStates struct {
dbClient *database.DB
}
func (mig *AddOffsetToCurrentStates) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addOffsetField)
return err
}
func (mig *AddOffsetToCurrentStates) String() string {
return "17_add_offset_col_to_current_states"
}

View File

@@ -0,0 +1 @@
ALTER TABLE projections.current_states ADD filter_offset INTEGER;

27
apps/api/cmd/setup/18.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 18.sql
addLowerFieldsToLoginNames string
)
type AddLowerFieldsToLoginNames struct {
dbClient *database.DB
}
func (mig *AddLowerFieldsToLoginNames) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addLowerFieldsToLoginNames)
return err
}
func (mig *AddLowerFieldsToLoginNames) String() string {
return "18_add_lower_fields_to_login_names"
}

View 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);

27
apps/api/cmd/setup/19.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 19.sql
addCurrentSequencesIndex string
)
type AddCurrentSequencesIndex struct {
dbClient *database.DB
}
func (mig *AddCurrentSequencesIndex) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addCurrentSequencesIndex)
return err
}
func (mig *AddCurrentSequencesIndex) String() string {
return "19_add_current_sequences_index"
}

View File

@@ -0,0 +1 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS events2_current_sequence ON eventstore.events2 ("sequence" DESC, aggregate_id, aggregate_type, instance_id);

27
apps/api/cmd/setup/20.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 20.sql
addByUserIndexToSession string
)
type AddByUserIndexToSession struct {
dbClient *database.DB
}
func (mig *AddByUserIndexToSession) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addByUserIndexToSession)
return err
}
func (mig *AddByUserIndexToSession) String() string {
return "20_add_by_user_index_on_session"
}

View File

@@ -0,0 +1 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS user_sessions_by_user ON auth.user_sessions (instance_id, user_id);

27
apps/api/cmd/setup/21.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 21.sql
addBlockFieldToLimits string
)
type AddBlockFieldToLimits struct {
dbClient *database.DB
}
func (mig *AddBlockFieldToLimits) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addBlockFieldToLimits)
return err
}
func (mig *AddBlockFieldToLimits) String() string {
return "21_add_block_field_to_limits"
}

View File

@@ -0,0 +1 @@
ALTER TABLE IF EXISTS projections.limits ADD COLUMN IF NOT EXISTS block BOOLEAN;

27
apps/api/cmd/setup/22.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 22.sql
activeInstanceEvents string
)
type ActiveInstanceEvents struct {
dbClient *database.DB
}
func (mig *ActiveInstanceEvents) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, activeInstanceEvents)
return err
}
func (mig *ActiveInstanceEvents) String() string {
return "22_active_instance_events_index"
}

View File

@@ -0,0 +1 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS active_instances_events ON eventstore.events2 (aggregate_type, event_type) WHERE aggregate_type = 'instance' AND event_type IN ('instance.added', 'instance.removed');

27
apps/api/cmd/setup/23.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 23.sql
correctGlobalUniqueConstraints string
)
type CorrectGlobalUniqueConstraints struct {
dbClient *database.DB
}
func (mig *CorrectGlobalUniqueConstraints) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, correctGlobalUniqueConstraints)
return err
}
func (mig *CorrectGlobalUniqueConstraints) String() string {
return "23_correct_global_unique_constraints"
}

View File

@@ -0,0 +1 @@
UPDATE eventstore.unique_constraints SET instance_id = '' WHERE unique_type = 'instance_domain';

27
apps/api/cmd/setup/24.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 24.sql
addTokenActor string
)
type AddActorToAuthTokens struct {
dbClient *database.DB
}
func (mig *AddActorToAuthTokens) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addTokenActor)
return err
}
func (mig *AddActorToAuthTokens) String() string {
return "24_add_actor_col_to_auth_tokens"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE auth.tokens ADD COLUMN actor jsonb;
ALTER TABLE auth.refresh_tokens ADD COLUMN actor jsonb;

27
apps/api/cmd/setup/25.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 25.sql
addLowerFieldsToVerifiedEmail string
)
type User11AddLowerFieldsToVerifiedEmail struct {
dbClient *database.DB
}
func (mig *User11AddLowerFieldsToVerifiedEmail) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addLowerFieldsToVerifiedEmail)
return err
}
func (mig *User11AddLowerFieldsToVerifiedEmail) String() string {
return "25_user14_add_lower_fields_to_verified_email"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS projections.users14_notifications ADD COLUMN IF NOT EXISTS verified_email_lower TEXT GENERATED ALWAYS AS (lower(verified_email)) STORED;
CREATE INDEX IF NOT EXISTS users14_notifications_email_search ON projections.users14_notifications (instance_id, verified_email_lower);

27
apps/api/cmd/setup/26.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 26.sql
authUsers3 string
)
type AuthUsers3 struct {
dbClient *database.DB
}
func (mig *AuthUsers3) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, authUsers3)
return err
}
func (mig *AuthUsers3) String() string {
return "26_auth_users3"
}

16
apps/api/cmd/setup/26.sql Normal file
View File

@@ -0,0 +1,16 @@
CREATE TABLE IF NOT EXISTS auth.users3 (
instance_id TEXT NOT NULL,
id TEXT NOT NULL,
resource_owner TEXT NOT NULL,
change_date TIMESTAMPTZ NULL,
password_set BOOL NULL,
password_change TIMESTAMPTZ NULL,
last_login TIMESTAMPTZ NULL,
init_required BOOL NULL,
mfa_init_skipped TIMESTAMPTZ NULL,
username_change_required BOOL NULL,
passwordless_init_required BOOL NULL,
password_init_required BOOL NULL,
PRIMARY KEY (instance_id, id)
)

27
apps/api/cmd/setup/27.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 27.sql
addSAMLNameIDFormat string
)
type IDPTemplate6SAMLNameIDFormat struct {
dbClient *database.DB
}
func (mig *IDPTemplate6SAMLNameIDFormat) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addSAMLNameIDFormat)
return err
}
func (mig *IDPTemplate6SAMLNameIDFormat) String() string {
return "27_idp_templates6_add_saml_name_id_format"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS projections.idp_templates6_saml ADD COLUMN IF NOT EXISTS name_id_format SMALLINT;
ALTER TABLE IF EXISTS projections.idp_templates6_saml ADD COLUMN IF NOT EXISTS transient_mapping_attribute_name TEXT;

27
apps/api/cmd/setup/28.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 28.sql
addFieldTable string
)
type AddFieldTable struct {
dbClient *database.DB
}
func (mig *AddFieldTable) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addFieldTable)
return err
}
func (mig *AddFieldTable) String() string {
return "28_add_search_table"
}

64
apps/api/cmd/setup/28.sql Normal file
View File

@@ -0,0 +1,64 @@
CREATE TABLE eventstore.fields (
id TEXT NOT NULL DEFAULT gen_random_uuid()
, instance_id TEXT NOT NULL
, resource_owner TEXT NOT NULL
, aggregate_type TEXT NOT NULL
, aggregate_id TEXT NOT NULL
, object_type TEXT NOT NULL
, object_id TEXT NOT NULL
, object_revision INT2
, field_name TEXT NOT NULL
-- all the values of fields are inserted into value column as jsonb and if we need to index something we store it to the type specific column additionally
, "value" JSONB NOT NULL
, number_value NUMERIC GENERATED ALWAYS AS (CASE WHEN should_index AND JSONB_TYPEOF("value") = 'number' THEN "value"::NUMERIC ELSE NULL END) STORED
, text_value TEXT GENERATED ALWAYS AS (CASE WHEN should_index AND JSONB_TYPEOF("value") = 'string' THEN "value" #>> '{}' ELSE NULL END) STORED
, bool_value BOOLEAN GENERATED ALWAYS AS (CASE WHEN should_index AND JSONB_TYPEOF("value") = 'boolean' THEN "value"::BOOLEAN ELSE NULL END) STORED
-- if true the value must be unique within an instance
, value_must_be_unique BOOLEAN
-- if set to true the primitive value is indexed
, should_index BOOLEAN
, PRIMARY KEY (instance_id, id)
-- TODO: create issue to enable the foreign key as soon as the objects table is implemented
-- , CONSTRAINT f_objects_fk FOREIGN KEY (instance_id, resource_owner, object_type, object_id, object_revision) REFERENCES eventstore.objects (instance_id, resource_owner, object_type, object_id, object_revision) ON DELETE CASCADE
-- the constraint ensures that a primitive value is set if the value must be unique
, CONSTRAINT primitive_value_for_unique_check CHECK (
CASE
WHEN value_must_be_unique THEN num_nonnulls(number_value, text_value, bool_value) = 1
ELSE true
END
)
-- the constraint ensures that a primitive value is set if the value must be indexed
, CONSTRAINT primitive_value_for_index CHECK (
CASE
WHEN should_index THEN num_nonnulls(number_value, text_value, bool_value) = 1
ELSE true
END
)
);
-- unique constraints for primitive values
CREATE UNIQUE INDEX IF NOT EXISTS f_number_unique_idx ON eventstore.fields (instance_id, field_name, number_value) WHERE value_must_be_unique;
CREATE UNIQUE INDEX IF NOT EXISTS f_text_unique_idx ON eventstore.fields (instance_id, field_name, text_value) WHERE value_must_be_unique;
CREATE UNIQUE INDEX IF NOT EXISTS f_bool_unique_idx ON eventstore.fields (instance_id, field_name, bool_value) WHERE value_must_be_unique;
-- search index for primitive values
CREATE INDEX IF NOT EXISTS f_number_value_idx ON eventstore.fields (instance_id, object_type, field_name, number_value)
INCLUDE (resource_owner, object_id, object_revision, "value")
WHERE number_value IS NOT NULL ;
CREATE INDEX IF NOT EXISTS f_text_value_idx ON eventstore.fields (instance_id, object_type, field_name, text_value)
INCLUDE (resource_owner, object_id, object_revision, "value")
WHERE text_value IS NOT NULL ;
CREATE INDEX IF NOT EXISTS f_bool_value_idx ON eventstore.fields (instance_id, object_type, field_name, bool_value)
INCLUDE (resource_owner, object_id, object_revision, "value")
WHERE bool_value IS NOT NULL ;
-- search index for object by id
CREATE INDEX IF NOT EXISTS f_object_idx ON eventstore.fields (instance_id, object_type, object_id, object_revision)
INCLUDE (resource_owner, field_name, "value") ;

40
apps/api/cmd/setup/29.go Normal file
View File

@@ -0,0 +1,40 @@
package setup
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/instance"
)
type FillFieldsForProjectGrant struct {
eventstore *eventstore.Eventstore
}
func (mig *FillFieldsForProjectGrant) Execute(ctx context.Context, _ eventstore.Event) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes("instance").
EventTypes(instance.InstanceAddedEventType).
Builder(),
)
if err != nil {
return err
}
for _, instance := range instances {
ctx := authz.WithInstanceID(ctx, instance)
if err := projection.ProjectGrantFields.Trigger(ctx); err != nil {
return err
}
}
return nil
}
func (mig *FillFieldsForProjectGrant) String() string {
return "29_init_fields_for_project_grant"
}

40
apps/api/cmd/setup/30.go Normal file
View File

@@ -0,0 +1,40 @@
package setup
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/instance"
)
type FillFieldsForOrgDomainVerified struct {
eventstore *eventstore.Eventstore
}
func (mig *FillFieldsForOrgDomainVerified) Execute(ctx context.Context, _ eventstore.Event) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes("instance").
EventTypes(instance.InstanceAddedEventType).
Builder(),
)
if err != nil {
return err
}
for _, instance := range instances {
ctx := authz.WithInstanceID(ctx, instance)
if err := projection.OrgDomainVerifiedFields.Trigger(ctx); err != nil {
return err
}
}
return nil
}
func (mig *FillFieldsForOrgDomainVerified) String() string {
return "30_fill_fields_for_org_domain_verified"
}

27
apps/api/cmd/setup/31.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 31.sql
addAggregateIndexToFields string
)
type AddAggregateIndexToFields struct {
dbClient *database.DB
}
func (mig *AddAggregateIndexToFields) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addAggregateIndexToFields)
return err
}
func (mig *AddAggregateIndexToFields) String() string {
return "31_add_aggregate_index_to_fields"
}

View File

@@ -0,0 +1 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS f_aggregate_object_type_idx ON eventstore.fields (aggregate_type, aggregate_id, object_type);

27
apps/api/cmd/setup/32.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 32.sql
addAuthSessionID string
)
type AddAuthSessionID struct {
dbClient *database.DB
}
func (mig *AddAuthSessionID) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addAuthSessionID)
return err
}
func (mig *AddAuthSessionID) String() string {
return "32_add_auth_sessionID"
}

View File

@@ -0,0 +1,3 @@
ALTER TABLE IF EXISTS auth.user_sessions ADD COLUMN IF NOT EXISTS id TEXT;
CREATE INDEX IF NOT EXISTS user_session_id ON auth.user_sessions (id, instance_id);

27
apps/api/cmd/setup/33.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 33.sql
addTwilioVerifyServiceSID string
)
type SMSConfigs3TwilioAddVerifyServiceSid struct {
dbClient *database.DB
}
func (mig *SMSConfigs3TwilioAddVerifyServiceSid) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addTwilioVerifyServiceSID)
return err
}
func (mig *SMSConfigs3TwilioAddVerifyServiceSid) String() string {
return "33_sms_configs3_twilio_add_verification_sid"
}

View File

@@ -0,0 +1 @@
ALTER TABLE IF EXISTS projections.sms_configs3_twilio ADD COLUMN IF NOT EXISTS verify_service_sid TEXT;

27
apps/api/cmd/setup/34.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 34/34_cache_schema.sql
addCacheSchema string
)
type AddCacheSchema struct {
dbClient *database.DB
}
func (mig *AddCacheSchema) Execute(ctx context.Context, _ eventstore.Event) (err error) {
_, err = mig.dbClient.ExecContext(ctx, addCacheSchema)
return err
}
func (mig *AddCacheSchema) String() string {
return "34_add_cache_schema"
}

View File

@@ -0,0 +1,29 @@
create schema if not exists cache;
create unlogged table if not exists cache.objects (
cache_name varchar not null,
id uuid not null default gen_random_uuid(),
created_at timestamptz not null default now(),
last_used_at timestamptz not null default now(),
payload jsonb not null,
primary key(cache_name, id)
)
partition by list (cache_name);
create unlogged table if not exists cache.string_keys(
cache_name varchar not null check (cache_name <> ''),
index_id integer not null check (index_id > 0),
index_key varchar not null check (index_key <> ''),
object_id uuid not null,
primary key (cache_name, index_id, index_key),
constraint fk_object
foreign key(cache_name, object_id)
references cache.objects(cache_name, id)
on delete cascade
)
partition by list (cache_name);
create index if not exists string_keys_object_id_idx
on cache.string_keys (cache_name, object_id); -- for delete cascade

39
apps/api/cmd/setup/35.go Normal file
View File

@@ -0,0 +1,39 @@
package setup
import (
"context"
"embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 35/*.sql
addPositionToEsWmIndex embed.FS
)
type AddPositionToIndexEsWm struct {
dbClient *database.DB
}
func (mig *AddPositionToIndexEsWm) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(addPositionToEsWmIndex, "35")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
}
func (mig *AddPositionToIndexEsWm) String() string {
return "35_add_position_to_index_es_wm"
}

View File

@@ -0,0 +1,2 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS es_wm_temp
ON eventstore.events2 (instance_id, aggregate_id, aggregate_type, event_type, "position");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS eventstore.es_wm;

View File

@@ -0,0 +1 @@
ALTER INDEX eventstore.es_wm_temp RENAME TO es_wm;

118
apps/api/cmd/setup/36.go Normal file
View File

@@ -0,0 +1,118 @@
package setup
import (
"context"
_ "embed"
"errors"
"fmt"
"slices"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/milestone"
)
var (
//go:embed 36.sql
getProjectedMilestones string
)
type FillV3Milestones struct {
dbClient *database.DB
eventstore *eventstore.Eventstore
}
type instanceMilestone struct {
Type milestone.Type
Reached time.Time
Pushed *time.Time
}
func (mig *FillV3Milestones) Execute(ctx context.Context, _ eventstore.Event) error {
im, err := mig.getProjectedMilestones(ctx)
if err != nil {
return err
}
return mig.pushEventsByInstance(ctx, im)
}
func (mig *FillV3Milestones) getProjectedMilestones(ctx context.Context) (map[string][]instanceMilestone, error) {
type row struct {
InstanceID string
Type milestone.Type
Reached time.Time
Pushed *time.Time
}
rows, _ := mig.dbClient.Pool.Query(ctx, getProjectedMilestones)
scanned, err := pgx.CollectRows(rows, pgx.RowToStructByPos[row])
var pgError *pgconn.PgError
// catch ERROR: relation "projections.milestones" does not exist
if errors.As(err, &pgError) && pgError.SQLState() == "42P01" {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("milestones get: %w", err)
}
milestoneMap := make(map[string][]instanceMilestone)
for _, s := range scanned {
milestoneMap[s.InstanceID] = append(milestoneMap[s.InstanceID], instanceMilestone{
Type: s.Type,
Reached: s.Reached,
Pushed: s.Pushed,
})
}
return milestoneMap, nil
}
// pushEventsByInstance creates the v2 milestone events by instance.
// This prevents we will try to push 6*N(instance) events in one push.
func (mig *FillV3Milestones) pushEventsByInstance(ctx context.Context, milestoneMap map[string][]instanceMilestone) error {
// keep a deterministic order by instance ID.
order := make([]string, 0, len(milestoneMap))
for k := range milestoneMap {
order = append(order, k)
}
slices.Sort(order)
for i, instanceID := range order {
logging.WithFields("instance_id", instanceID, "migration", mig.String(), "progress", fmt.Sprintf("%d/%d", i+1, len(order))).Info("filter existing milestone events")
// because each Push runs in a separate TX, we need to make sure that events
// from a partially executed migration are pushed again.
model := command.NewMilestonesReachedWriteModel(instanceID)
if err := mig.eventstore.FilterToQueryReducer(ctx, model); err != nil {
return fmt.Errorf("milestones filter: %w", err)
}
if model.InstanceCreated {
logging.WithFields("instance_id", instanceID, "migration", mig.String()).Info("milestone events already migrated")
continue // This instance was migrated, skip
}
logging.WithFields("instance_id", instanceID, "migration", mig.String()).Info("push milestone events")
aggregate := milestone.NewInstanceAggregate(instanceID)
cmds := make([]eventstore.Command, 0, len(milestoneMap[instanceID])*2)
for _, m := range milestoneMap[instanceID] {
cmds = append(cmds, milestone.NewReachedEventWithDate(ctx, aggregate, m.Type, &m.Reached))
if m.Pushed != nil {
cmds = append(cmds, milestone.NewPushedEventWithDate(ctx, aggregate, m.Type, nil, "", m.Pushed))
}
}
if _, err := mig.eventstore.Push(ctx, cmds...); err != nil {
return fmt.Errorf("milestones push: %w", err)
}
}
return nil
}
func (mig *FillV3Milestones) String() string {
return "36_fill_v3_milestones"
}

View File

@@ -0,0 +1,4 @@
SELECT instance_id, type, reached_date, last_pushed_date
FROM projections.milestones
WHERE reached_date IS NOT NULL
ORDER BY instance_id, reached_date;

27
apps/api/cmd/setup/37.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 37.sql
addBackChannelLogoutURI string
)
type Apps7OIDConfigsBackChannelLogoutURI struct {
dbClient *database.DB
}
func (mig *Apps7OIDConfigsBackChannelLogoutURI) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addBackChannelLogoutURI)
return err
}
func (mig *Apps7OIDConfigsBackChannelLogoutURI) String() string {
return "37_apps7_oidc_configs_add_back_channel_logout_uri"
}

View File

@@ -0,0 +1 @@
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS back_channel_logout_uri TEXT;

27
apps/api/cmd/setup/38.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 38.sql
backChannelLogoutCurrentState string
)
type BackChannelLogoutNotificationStart struct {
dbClient *database.DB
}
func (mig *BackChannelLogoutNotificationStart) Execute(ctx context.Context, e eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, backChannelLogoutCurrentState, e.Sequence(), e.CreatedAt(), e.Position())
return err
}
func (mig *BackChannelLogoutNotificationStart) String() string {
return "38_back_channel_logout_notification_start_"
}

20
apps/api/cmd/setup/38.sql Normal file
View File

@@ -0,0 +1,20 @@
INSERT INTO projections.current_states (
instance_id
, projection_name
, last_updated
, sequence
, event_date
, position
, filter_offset
)
SELECT instance_id
, 'projections.notifications_back_channel_logout'
, now()
, $1
, $2
, $3
, 0
FROM eventstore.events2
WHERE aggregate_type = 'instance'
AND event_type = 'instance.added'
ON CONFLICT DO NOTHING;

51
apps/api/cmd/setup/39.go Normal file
View File

@@ -0,0 +1,51 @@
package setup
import (
"context"
_ "embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/instance"
)
var (
//go:embed 39.sql
deleteStaleOrgFields string
)
type DeleteStaleOrgFields struct {
eventstore *eventstore.Eventstore
}
func (mig *DeleteStaleOrgFields) Execute(ctx context.Context, _ eventstore.Event) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes("instance").
EventTypes(instance.InstanceAddedEventType).
Builder(),
)
if err != nil {
return err
}
for i, instance := range instances {
logging.WithFields("instance_id", instance, "migration", mig.String(), "progress", fmt.Sprintf("%d/%d", i+1, len(instances))).Info("execute delete query")
if _, err := mig.eventstore.Client().ExecContext(ctx, deleteStaleOrgFields, instance); err != nil {
return err
}
}
return nil
}
func (*DeleteStaleOrgFields) Check(map[string]any) bool {
return true
}
func (*DeleteStaleOrgFields) String() string {
return "repeatable_delete_stale_org_fields"
}

View File

@@ -0,0 +1,9 @@
DELETE FROM eventstore.fields
WHERE aggregate_type = 'org'
AND aggregate_id IN (
SELECT aggregate_id
FROM eventstore.events2
WHERE instance_id = $1
AND aggregate_type = 'org'
AND event_type = 'org.removed'
);

115
apps/api/cmd/setup/40.go Normal file
View File

@@ -0,0 +1,115 @@
package setup
import (
"context"
"database/sql"
"embed"
"fmt"
"io/fs"
"path"
"strings"
"text/template"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
// query filenames
const (
fileInTxOrderType = "00_in_tx_order_type.sql"
fileType = "01_type.sql"
fileFunc = "02_func.sql"
)
var (
//go:embed 40/*.sql
initPushFunc embed.FS
)
type InitPushFunc struct {
dbClient *database.DB
}
func (mig *InitPushFunc) Execute(ctx context.Context, _ eventstore.Event) (err error) {
conn, err := mig.dbClient.Conn(ctx)
if err != nil {
return err
}
defer func() {
closeErr := conn.Close()
logging.OnError(closeErr).Debug("failed to release connection")
// Force the pool to reopen connections to apply the new types
mig.dbClient.Pool.Reset()
}()
statements, err := mig.prepareStatements(ctx)
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := conn.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
}
func (mig *InitPushFunc) String() string {
return "40_init_push_func_v4"
}
func (mig *InitPushFunc) prepareStatements(ctx context.Context) ([]statement, error) {
funcTmpl, err := template.ParseFS(initPushFunc, mig.filePath(fileFunc))
if err != nil {
return nil, fmt.Errorf("prepare steps: %w", err)
}
typeName, err := mig.inTxOrderType(ctx)
if err != nil {
return nil, fmt.Errorf("prepare steps: %w", err)
}
var funcStep strings.Builder
err = funcTmpl.Execute(&funcStep, struct {
InTxOrderType string
}{
InTxOrderType: typeName,
})
if err != nil {
return nil, fmt.Errorf("prepare steps: %w", err)
}
typeStatement, err := fs.ReadFile(initPushFunc, mig.filePath(fileType))
if err != nil {
return nil, fmt.Errorf("prepare steps: %w", err)
}
return []statement{
{
file: fileType,
query: string(typeStatement),
},
{
file: fileFunc,
query: funcStep.String(),
},
}, nil
}
func (mig *InitPushFunc) inTxOrderType(ctx context.Context) (typeName string, err error) {
query, err := fs.ReadFile(initPushFunc, mig.filePath(fileInTxOrderType))
if err != nil {
return "", fmt.Errorf("get in_tx_order_type: %w", err)
}
err = mig.dbClient.QueryRowContext(ctx, func(row *sql.Row) error {
return row.Scan(&typeName)
}, string(query))
if err != nil {
return "", fmt.Errorf("get in_tx_order_type: %w", err)
}
return typeName, nil
}
func (mig *InitPushFunc) filePath(fileName string) string {
return path.Join("40", fileName)
}

View File

@@ -0,0 +1,5 @@
SELECT data_type
FROM information_schema.columns
WHERE table_schema = 'eventstore'
AND table_name = 'events2'
AND column_name = 'in_tx_order';

View File

@@ -0,0 +1,15 @@
-- represents an event to be created.
DO $$ BEGIN
CREATE TYPE eventstore.command AS (
instance_id TEXT
, aggregate_type TEXT
, aggregate_id TEXT
, command_type TEXT
, revision INT2
, payload JSONB
, creator TEXT
, owner TEXT
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

View File

@@ -0,0 +1,92 @@
CREATE OR REPLACE FUNCTION eventstore.latest_aggregate_state(
instance_id TEXT
, aggregate_type TEXT
, aggregate_id TEXT
, sequence OUT BIGINT
, owner OUT TEXT
)
LANGUAGE 'plpgsql'
STABLE PARALLEL SAFE
AS $$
BEGIN
SELECT
COALESCE(e.sequence, 0) AS sequence
, e.owner
INTO
sequence
, owner
FROM
eventstore.events2 e
WHERE
e.instance_id = $1
AND e.aggregate_type = $2
AND e.aggregate_id = $3
ORDER BY
e.sequence DESC
LIMIT 1;
RETURN;
END;
$$;
CREATE OR REPLACE FUNCTION eventstore.commands_to_events(commands eventstore.command[])
RETURNS SETOF eventstore.events2
LANGUAGE 'plpgsql'
STABLE PARALLEL SAFE
ROWS 10
AS $$
DECLARE
"aggregate" RECORD;
current_sequence BIGINT;
current_owner TEXT;
BEGIN
FOR "aggregate" IN
SELECT DISTINCT
instance_id
, aggregate_type
, aggregate_id
FROM UNNEST(commands)
LOOP
SELECT
*
INTO
current_sequence
, current_owner
FROM eventstore.latest_aggregate_state(
"aggregate".instance_id
, "aggregate".aggregate_type
, "aggregate".aggregate_id
);
RETURN QUERY
SELECT
c.instance_id
, c.aggregate_type
, c.aggregate_id
, c.command_type -- AS event_type
, COALESCE(current_sequence, 0) + ROW_NUMBER() OVER () -- AS sequence
, c.revision
, NOW() -- AS created_at
, c.payload
, c.creator
, COALESCE(current_owner, c.owner) -- AS owner
, EXTRACT(EPOCH FROM NOW()) -- AS position
, c.ordinality::{{ .InTxOrderType }} -- AS in_tx_order
FROM
UNNEST(commands) WITH ORDINALITY AS c
WHERE
c.instance_id = aggregate.instance_id
AND c.aggregate_type = aggregate.aggregate_type
AND c.aggregate_id = aggregate.aggregate_id;
END LOOP;
RETURN;
END;
$$;
CREATE OR REPLACE FUNCTION eventstore.push(commands eventstore.command[]) RETURNS SETOF eventstore.events2 VOLATILE AS $$
INSERT INTO eventstore.events2
SELECT * FROM eventstore.commands_to_events(commands)
ORDER BY in_tx_order
RETURNING *
$$ LANGUAGE SQL;

44
apps/api/cmd/setup/41.go Normal file
View File

@@ -0,0 +1,44 @@
package setup
import (
"context"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/query/projection"
"github.com/zitadel/zitadel/internal/repository/instance"
)
type FillFieldsForInstanceDomains struct {
eventstore *eventstore.Eventstore
}
func (mig *FillFieldsForInstanceDomains) Execute(ctx context.Context, _ eventstore.Event) error {
instances, err := mig.eventstore.InstanceIDs(
ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsInstanceIDs).
OrderDesc().
AddQuery().
AggregateTypes("instance").
EventTypes(instance.InstanceAddedEventType).
Builder(),
)
if err != nil {
return err
}
for _, instance := range instances {
ctx := authz.WithInstanceID(ctx, instance)
if err := projection.InstanceDomainFields.Trigger(ctx); err != nil {
return err
}
}
return nil
}
func (mig *FillFieldsForInstanceDomains) String() string {
return "repeatable_fill_fields_for_instance_domains"
}
func (f *FillFieldsForInstanceDomains) Check(lastRun map[string]interface{}) bool {
return true
}

27
apps/api/cmd/setup/42.go Normal file
View File

@@ -0,0 +1,27 @@
package setup
import (
"context"
_ "embed"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 42.sql
addOIDCAppLoginVersion string
)
type Apps7OIDCConfigsLoginVersion struct {
dbClient *database.DB
}
func (mig *Apps7OIDCConfigsLoginVersion) Execute(ctx context.Context, _ eventstore.Event) error {
_, err := mig.dbClient.ExecContext(ctx, addOIDCAppLoginVersion)
return err
}
func (mig *Apps7OIDCConfigsLoginVersion) String() string {
return "40_apps7_oidc_configs_login_version"
}

View File

@@ -0,0 +1,2 @@
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS login_version SMALLINT;
ALTER TABLE IF EXISTS projections.apps7_oidc_configs ADD COLUMN IF NOT EXISTS login_base_uri TEXT;

39
apps/api/cmd/setup/43.go Normal file
View File

@@ -0,0 +1,39 @@
package setup
import (
"context"
"embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 43/*.sql
createFieldsDomainIndex embed.FS
)
type CreateFieldsDomainIndex struct {
dbClient *database.DB
}
func (mig *CreateFieldsDomainIndex) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(createFieldsDomainIndex, "43")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
}
func (mig *CreateFieldsDomainIndex) String() string {
return "43_create_fields_domain_index"
}

View File

@@ -0,0 +1,3 @@
CREATE INDEX CONCURRENTLY IF NOT EXISTS fields_instance_domains_idx
ON eventstore.fields (object_id) INCLUDE (instance_id)
WHERE object_type = 'instance_domain' AND field_name = 'domain';

39
apps/api/cmd/setup/44.go Normal file
View File

@@ -0,0 +1,39 @@
package setup
import (
"context"
"embed"
"fmt"
"github.com/zitadel/logging"
"github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/eventstore"
)
var (
//go:embed 44/*.sql
replaceCurrentSequencesIndex embed.FS
)
type ReplaceCurrentSequencesIndex struct {
dbClient *database.DB
}
func (mig *ReplaceCurrentSequencesIndex) Execute(ctx context.Context, _ eventstore.Event) error {
statements, err := readStatements(replaceCurrentSequencesIndex, "44")
if err != nil {
return err
}
for _, stmt := range statements {
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
}
}
return nil
}
func (mig *ReplaceCurrentSequencesIndex) String() string {
return "44_replace_current_sequences_index"
}

Some files were not shown because too many files have changed in this diff Show More