"""initial Revision ID: 05beabc436ad Revises: Create Date: 2026-09-17 22:02:22.073683 """ from __future__ import annotations from collections.abc import Sequence import sqlalchemy as sa from alembic import op from sqlalchemy.dialects import postgresql revision: str = "05beabc436ad" down_revision: str | None = None branch_labels: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( "account", sa.Column("id", sa.Integer(), nullable=False), sa.Column( "kind", sa.Enum( "zm_cash", "zm_card", "zm_checking", "zm_deposit", "zm_loan", "zm_emoney", "zm_debt", "broker", "manual_asset", name="account_kind", ), nullable=False, ), sa.Column("source", sa.String(length=32), nullable=False), sa.Column("source_id", sa.String(length=128), nullable=False), sa.Column( "broker", sa.Enum("tinvest", "sber", "vtb", "other", name="broker"), nullable=True ), sa.Column("name", sa.String(length=256), nullable=False), sa.Column("currency", sa.String(length=3), nullable=False), sa.Column("include_in_net_worth", sa.Boolean(), nullable=False), sa.Column( "role", sa.Enum("liquid", "savings", "investment", "debt", name="account_role"), nullable=False, ), sa.Column("mirror_of_account_id", sa.Integer(), nullable=True), sa.Column( "primary_event_source", sa.Enum("tinvest_api", "report_sber", "report_vtb", "manual", name="event_source"), nullable=True, ), sa.Column("opened_at", sa.Date(), nullable=True), sa.Column("archived", sa.Boolean(), nullable=False), sa.Column("deposit_terms", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.ForeignKeyConstraint( ["mirror_of_account_id"], ["account.id"], name=op.f("fk_account_mirror_of_account_id_account"), ondelete="SET NULL", ), sa.PrimaryKeyConstraint("id", name=op.f("pk_account")), sa.UniqueConstraint("source", "source_id", name=op.f("uq_account_source_source_id")), ) op.create_table( "app_user", sa.Column("id", sa.Integer(), nullable=False), sa.Column("email", sa.String(length=320), nullable=False), sa.Column("password_hash", sa.String(length=512), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("id", name=op.f("pk_app_user")), sa.UniqueConstraint("email", name=op.f("uq_app_user_email")), ) op.create_table( "instrument", sa.Column("id", sa.Integer(), nullable=False), sa.Column( "asset_class", sa.Enum( "share", "bond", "etf", "fund", "currency", "index", "deposit", "real_estate", "crypto", "custom", name="asset_class", ), nullable=False, ), sa.Column("isin", sa.String(length=12), nullable=True), sa.Column("figi", sa.String(length=12), nullable=True), sa.Column("tinvest_uid", sa.String(length=64), nullable=True), sa.Column("ticker", sa.String(length=32), nullable=True), sa.Column("board", sa.String(length=16), nullable=True), sa.Column("exchange", sa.String(length=32), nullable=True), sa.Column("name", sa.String(length=256), nullable=False), sa.Column("issuer", sa.String(length=256), nullable=True), sa.Column("currency", sa.String(length=3), nullable=False), sa.Column("lot", sa.Integer(), nullable=False), sa.Column("nominal", sa.Numeric(precision=24, scale=10), nullable=True), sa.Column("nominal_currency", sa.String(length=3), nullable=True), sa.Column("maturity_date", sa.Date(), nullable=True), sa.Column("sector", sa.String(length=64), nullable=True), sa.Column("country", sa.String(length=2), nullable=True), sa.Column("is_active", sa.Boolean(), nullable=False), sa.Column("meta", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("id", name=op.f("pk_instrument")), ) op.create_index( "uq_instrument_figi", "instrument", ["figi"], unique=True, postgresql_where=sa.text("figi IS NOT NULL"), ) op.create_index( "uq_instrument_isin", "instrument", ["isin"], unique=True, postgresql_where=sa.text("isin IS NOT NULL"), ) op.create_index( "uq_instrument_ticker_board", "instrument", ["ticker", "board"], unique=True, postgresql_where=sa.text("ticker IS NOT NULL AND board IS NOT NULL"), ) op.create_index( "uq_instrument_tinvest_uid", "instrument", ["tinvest_uid"], unique=True, postgresql_where=sa.text("tinvest_uid IS NOT NULL"), ) op.create_table( "portfolio", sa.Column("id", sa.Integer(), nullable=False), sa.Column("name", sa.String(length=128), nullable=False), sa.Column("is_default", sa.Boolean(), nullable=False), sa.Column("base_currency", sa.String(length=3), nullable=False), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("id", name=op.f("pk_portfolio")), sa.UniqueConstraint("name", name=op.f("uq_portfolio_name")), ) op.create_table( "source_credential", sa.Column("source", sa.String(length=64), nullable=False), sa.Column("payload", postgresql.JSONB(astext_type=sa.Text()), nullable=False), sa.Column( "updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.PrimaryKeyConstraint("source", name=op.f("pk_source_credential")), ) op.create_table( "sync_run", sa.Column("id", sa.Uuid(), nullable=False), sa.Column("source", sa.String(length=64), nullable=False), sa.Column("status", sa.Enum("running", "ok", "error", name="run_status"), nullable=False), sa.Column("triggered_by", sa.String(length=32), nullable=False), sa.Column( "started_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True), sa.Column("cursor_before", sa.Text(), nullable=True), sa.Column("cursor_after", sa.Text(), nullable=True), sa.Column("counts", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column("warnings", postgresql.JSONB(astext_type=sa.Text()), nullable=True), sa.Column("error", sa.Text(), nullable=True), sa.PrimaryKeyConstraint("id", name=op.f("pk_sync_run")), ) op.create_index(op.f("ix_sync_run_source"), "sync_run", ["source"], unique=False) op.create_table( "sync_state", sa.Column("source", sa.String(length=64), nullable=False), sa.Column("cursor", sa.Text(), nullable=True), sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True), sa.Column("last_success_at", sa.DateTime(timezone=True), nullable=True), sa.PrimaryKeyConstraint("source", name=op.f("pk_sync_state")), ) op.create_table( "account_link", sa.Column("zm_account_id", sa.Integer(), nullable=False), sa.Column("broker_account_id", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( ["broker_account_id"], ["account.id"], name=op.f("fk_account_link_broker_account_id_account"), ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["zm_account_id"], ["account.id"], name=op.f("fk_account_link_zm_account_id_account"), ondelete="CASCADE", ), sa.PrimaryKeyConstraint("zm_account_id", name=op.f("pk_account_link")), sa.UniqueConstraint("broker_account_id", name=op.f("uq_account_link_broker_account_id")), ) op.create_table( "instrument_alias", sa.Column("id", sa.Integer(), nullable=False), sa.Column("instrument_id", sa.Integer(), nullable=False), sa.Column("source", sa.String(length=32), nullable=False), sa.Column("source_key", sa.String(length=256), nullable=False), sa.ForeignKeyConstraint( ["instrument_id"], ["instrument.id"], name=op.f("fk_instrument_alias_instrument_id_instrument"), ondelete="CASCADE", ), sa.PrimaryKeyConstraint("id", name=op.f("pk_instrument_alias")), sa.UniqueConstraint( "source", "source_key", name=op.f("uq_instrument_alias_source_source_key") ), ) op.create_index( op.f("ix_instrument_alias_instrument_id"), "instrument_alias", ["instrument_id"], unique=False, ) op.create_table( "portfolio_account", sa.Column("portfolio_id", sa.Integer(), nullable=False), sa.Column("account_id", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( ["account_id"], ["account.id"], name=op.f("fk_portfolio_account_account_id_account"), ondelete="CASCADE", ), sa.ForeignKeyConstraint( ["portfolio_id"], ["portfolio.id"], name=op.f("fk_portfolio_account_portfolio_id_portfolio"), ondelete="CASCADE", ), sa.PrimaryKeyConstraint("portfolio_id", "account_id", name=op.f("pk_portfolio_account")), ) op.create_table( "refresh_token", sa.Column("id", sa.Uuid(), nullable=False), sa.Column("user_id", sa.Integer(), nullable=False), sa.Column("token_hash", sa.String(length=64), nullable=False), sa.Column("expires_at", sa.DateTime(timezone=True), nullable=False), sa.Column("revoked_at", sa.DateTime(timezone=True), nullable=True), sa.Column( "created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.ForeignKeyConstraint( ["user_id"], ["app_user.id"], name=op.f("fk_refresh_token_user_id_app_user"), ondelete="CASCADE", ), sa.PrimaryKeyConstraint("id", name=op.f("pk_refresh_token")), sa.UniqueConstraint("token_hash", name=op.f("uq_refresh_token_token_hash")), ) op.create_index(op.f("ix_refresh_token_user_id"), "refresh_token", ["user_id"], unique=False) op.create_table( "sync_job", sa.Column("id", sa.Uuid(), nullable=False), sa.Column("source", sa.String(length=64), nullable=False), sa.Column( "status", sa.Enum("queued", "running", "done", "error", name="job_status"), nullable=False, ), sa.Column( "requested_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False, ), sa.Column("started_at", sa.DateTime(timezone=True), nullable=True), sa.Column("finished_at", sa.DateTime(timezone=True), nullable=True), sa.Column("run_id", sa.Uuid(), nullable=True), sa.Column("error", sa.Text(), nullable=True), sa.ForeignKeyConstraint( ["run_id"], ["sync_run.id"], name=op.f("fk_sync_job_run_id_sync_run"), ondelete="SET NULL", ), sa.PrimaryKeyConstraint("id", name=op.f("pk_sync_job")), ) op.create_index(op.f("ix_sync_job_source"), "sync_job", ["source"], unique=False) op.create_index(op.f("ix_sync_job_status"), "sync_job", ["status"], unique=False) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_index(op.f("ix_sync_job_status"), table_name="sync_job") op.drop_index(op.f("ix_sync_job_source"), table_name="sync_job") op.drop_table("sync_job") op.drop_index(op.f("ix_refresh_token_user_id"), table_name="refresh_token") op.drop_table("refresh_token") op.drop_table("portfolio_account") op.drop_index(op.f("ix_instrument_alias_instrument_id"), table_name="instrument_alias") op.drop_table("instrument_alias") op.drop_table("account_link") op.drop_table("sync_state") op.drop_index(op.f("ix_sync_run_source"), table_name="sync_run") op.drop_table("sync_run") op.drop_table("source_credential") op.drop_table("portfolio") op.drop_index( "uq_instrument_tinvest_uid", table_name="instrument", postgresql_where=sa.text("tinvest_uid IS NOT NULL"), ) op.drop_index( "uq_instrument_ticker_board", table_name="instrument", postgresql_where=sa.text("ticker IS NOT NULL AND board IS NOT NULL"), ) op.drop_index( "uq_instrument_isin", table_name="instrument", postgresql_where=sa.text("isin IS NOT NULL") ) op.drop_index( "uq_instrument_figi", table_name="instrument", postgresql_where=sa.text("figi IS NOT NULL") ) op.drop_table("instrument") op.drop_table("app_user") op.drop_table("account") # ### end Alembic commands ### for enum_name in ( "account_kind", "broker", "account_role", "event_source", "asset_class", "run_status", "job_status", ): sa.Enum(name=enum_name).drop(op.get_bind(), checkfirst=True)