перенос миграций с alembic
This commit is contained in:
119
alembic.ini
119
alembic.ini
@@ -1,119 +0,0 @@
|
||||
# A generic, single database configuration.
|
||||
|
||||
[alembic]
|
||||
# path to migration scripts
|
||||
# Use forward slashes (/) also on windows to provide an os agnostic path
|
||||
script_location = migration
|
||||
|
||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
|
||||
# Uncomment the line below if you want the files to be prepended with date and time
|
||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
|
||||
# for all available tokens
|
||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
|
||||
|
||||
# sys.path path, will be prepended to sys.path if present.
|
||||
# defaults to the current working directory.
|
||||
prepend_sys_path = . .
|
||||
|
||||
# timezone to use when rendering the date within the migration file
|
||||
# as well as the filename.
|
||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
|
||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
|
||||
# string value is passed to ZoneInfo()
|
||||
# leave blank for localtime
|
||||
# timezone =
|
||||
|
||||
# max length of characters to apply to the "slug" field
|
||||
# truncate_slug_length = 40
|
||||
|
||||
# set to 'true' to run the environment during
|
||||
# the 'revision' command, regardless of autogenerate
|
||||
# revision_environment = false
|
||||
|
||||
# set to 'true' to allow .pyc and .pyo files without
|
||||
# a source .py file to be detected as revisions in the
|
||||
# versions/ directory
|
||||
# sourceless = false
|
||||
|
||||
# version location specification; This defaults
|
||||
# to src/migrations/versions. When using multiple version
|
||||
# directories, initial revisions must be specified with --version-path.
|
||||
# The path separator used here should be the separator specified by "version_path_separator" below.
|
||||
# version_locations = %(here)s/bar:%(here)s/bat:src/migrations/versions
|
||||
|
||||
# version path separator; As mentioned above, this is the character used to split
|
||||
# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep.
|
||||
# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas.
|
||||
# Valid values for version_path_separator are:
|
||||
#
|
||||
# version_path_separator = :
|
||||
# version_path_separator = ;
|
||||
# version_path_separator = space
|
||||
# version_path_separator = newline
|
||||
#
|
||||
# Use os.pathsep. Default configuration used for new projects.
|
||||
version_path_separator = os
|
||||
|
||||
# set to 'true' to search source files recursively
|
||||
# in each "version_locations" directory
|
||||
# new in Alembic version 1.10
|
||||
# recursive_version_locations = false
|
||||
|
||||
# the output encoding used when revision files
|
||||
# are written from script.py.mako
|
||||
# output_encoding = utf-8
|
||||
|
||||
sqlalchemy.url = driver://user:pass@localhost/dbname
|
||||
|
||||
|
||||
[post_write_hooks]
|
||||
# post_write_hooks defines scripts or Python functions that are run
|
||||
# on newly generated revision scripts. See the documentation for further
|
||||
# detail and examples
|
||||
|
||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
|
||||
# hooks = black
|
||||
# black.type = console_scripts
|
||||
# black.entrypoint = black
|
||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
|
||||
|
||||
# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
|
||||
# hooks = ruff
|
||||
# ruff.type = exec
|
||||
# ruff.executable = %(here)s/.venv/bin/ruff
|
||||
# ruff.options = --fix REVISION_SCRIPT_FILENAME
|
||||
|
||||
# Logging configuration
|
||||
[loggers]
|
||||
keys = root,sqlalchemy,alembic
|
||||
|
||||
[handlers]
|
||||
keys = console
|
||||
|
||||
[formatters]
|
||||
keys = generic
|
||||
|
||||
[logger_root]
|
||||
level = WARNING
|
||||
handlers = console
|
||||
qualname =
|
||||
|
||||
[logger_sqlalchemy]
|
||||
level = WARNING
|
||||
handlers =
|
||||
qualname = sqlalchemy.engine
|
||||
|
||||
[logger_alembic]
|
||||
level = INFO
|
||||
handlers =
|
||||
qualname = alembic
|
||||
|
||||
[handler_console]
|
||||
class = StreamHandler
|
||||
args = (sys.stderr,)
|
||||
level = NOTSET
|
||||
formatter = generic
|
||||
|
||||
[formatter_generic]
|
||||
format = %(levelname)-5.5s [%(name)s] %(message)s
|
||||
datefmt = %H:%M:%S
|
||||
@@ -1,49 +0,0 @@
|
||||
from logging.config import fileConfig
|
||||
|
||||
from alembic import context
|
||||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from src.config import settings
|
||||
from src.domain import * # noqa
|
||||
|
||||
try:
|
||||
from src.domain import Base as SqlBase # type: ignore[attr-defined]
|
||||
except ImportError:
|
||||
SqlBase = None
|
||||
|
||||
config = context.config
|
||||
|
||||
if config.config_file_name:
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
config.set_main_option('sqlalchemy.url', f'{settings.db.URL}?async_fallback=True')
|
||||
target_metadata = SqlBase.metadata if SqlBase else None
|
||||
|
||||
|
||||
def run_migrations_offline() -> None:
|
||||
context.configure(
|
||||
url=config.get_main_option('sqlalchemy.url'),
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
dialect_opts={'paramstyle': 'named'},
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
connectable = engine_from_config(
|
||||
config.get_section(config.config_ini_section, {}),
|
||||
prefix='sqlalchemy.',
|
||||
poolclass=pool.NullPool,
|
||||
)
|
||||
with connectable.connect() as connection:
|
||||
context.configure(connection=connection, target_metadata=target_metadata)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
run_migrations_online()
|
||||
@@ -1,26 +0,0 @@
|
||||
"""${message}
|
||||
|
||||
Revision ID: ${up_revision}
|
||||
Revises: ${down_revision | comma,n}
|
||||
Create Date: ${create_date}
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
${imports if imports else ""}
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = ${repr(up_revision)}
|
||||
down_revision: Union[str, None] = ${repr(down_revision)}
|
||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
|
||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
${upgrades if upgrades else "pass"}
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
${downgrades if downgrades else "pass"}
|
||||
@@ -1,43 +0,0 @@
|
||||
"""add_placement_parser_support
|
||||
|
||||
Revision ID: c8f9a2d3e4b5
|
||||
Revises: b1d510ad3751
|
||||
Create Date: 2025-12-07 12:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'c8f9a2d3e4b5'
|
||||
down_revision: str | None = 'b1d510ad3751'
|
||||
branch_labels: str | Sequence[str] | None = None
|
||||
depends_on: str | Sequence[str] | None = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 1. Добавить поле external_channel_message_id
|
||||
op.add_column('placement', sa.Column('external_channel_message_id', sa.BigInteger(), nullable=True))
|
||||
|
||||
# 2. Добавить новые значения в enum placementstatus
|
||||
# В PostgreSQL нельзя добавлять значения в enum внутри транзакции,
|
||||
# поэтому используем autocommit_block
|
||||
with op.get_context().autocommit_block():
|
||||
op.execute("ALTER TYPE placementstatus ADD VALUE IF NOT EXISTS 'PENDING'")
|
||||
op.execute("ALTER TYPE placementstatus ADD VALUE IF NOT EXISTS 'DELETED'")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# 1. Откатить данные с новыми статусами на ACTIVE
|
||||
op.execute("UPDATE placement SET status = 'ACTIVE' WHERE status IN ('PENDING', 'DELETED')")
|
||||
|
||||
# 2. Удалить поле external_channel_message_id
|
||||
op.drop_column('placement', 'external_channel_message_id')
|
||||
|
||||
# ВАЖНО: В PostgreSQL нельзя удалить значения из enum.
|
||||
# Новые значения 'PENDING' и 'DELETED' останутся в enum даже после downgrade.
|
||||
# Это ограничение PostgreSQL. Если нужно полностью удалить значения,
|
||||
# потребуется пересоздать enum, что сложнее и требует временного столбца.
|
||||
@@ -1,65 +0,0 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: b1d510ad3751
|
||||
Revises: f05f85d10837
|
||||
Create Date: 2025-12-01 13:05:17.215374
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
subscription_status_enum = sa.Enum('ACTIVE', 'UNSUBSCRIBED', name='subscriptionstatus')
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'b1d510ad3751'
|
||||
down_revision: str | None = 'f05f85d10837'
|
||||
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(
|
||||
'telegram_state',
|
||||
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
||||
sa.Column(
|
||||
'state',
|
||||
sa.Enum(
|
||||
'CREATIVE_WAITING_CHANNEL', 'CREATIVE_WAITING_NAME', 'CREATIVE_WAITING_TEXT', name='telegramstateenum'
|
||||
),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column('context', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_telegram_state_telegram_id'), 'telegram_state', ['telegram_id'], unique=True)
|
||||
op.add_column('subscription', sa.Column('target_channel_id', sa.Uuid(), nullable=False))
|
||||
subscription_status_enum.create(op.get_bind(), checkfirst=True)
|
||||
op.add_column('subscription', sa.Column('status', subscription_status_enum, nullable=False))
|
||||
op.add_column('subscription', sa.Column('unsubscribed_at', sa.DateTime(timezone=True), nullable=True))
|
||||
op.create_index(op.f('ix_subscription_target_channel_id'), 'subscription', ['target_channel_id'], unique=False)
|
||||
fk_name = op.f('fk_subscription_target_channel_id')
|
||||
op.create_foreign_key(fk_name, 'subscription', 'target_channel', ['target_channel_id'], ['id'])
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
fk_name = op.f('fk_subscription_target_channel_id')
|
||||
op.drop_constraint(fk_name, 'subscription', type_='foreignkey')
|
||||
op.drop_index(op.f('ix_subscription_target_channel_id'), table_name='subscription')
|
||||
op.drop_column('subscription', 'unsubscribed_at')
|
||||
op.drop_column('subscription', 'status')
|
||||
subscription_status_enum.drop(op.get_bind(), checkfirst=True)
|
||||
op.drop_column('subscription', 'target_channel_id')
|
||||
op.drop_index(op.f('ix_telegram_state_telegram_id'), table_name='telegram_state')
|
||||
op.drop_table('telegram_state')
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,262 +0,0 @@
|
||||
"""init
|
||||
|
||||
Revision ID: f05f85d10837
|
||||
Revises:
|
||||
Create Date: 2025-11-13 01:06:51.623272
|
||||
|
||||
"""
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = 'f05f85d10837'
|
||||
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(
|
||||
'subscriber',
|
||||
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=True),
|
||||
sa.Column('first_name', sa.String(), nullable=True),
|
||||
sa.Column('last_name', sa.String(), nullable=True),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_subscriber_telegram_id'), 'subscriber', ['telegram_id'], unique=True)
|
||||
op.create_table(
|
||||
'user',
|
||||
sa.Column('telegram_id', sa.Integer(), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=True),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_user_telegram_id'), 'user', ['telegram_id'], unique=True)
|
||||
op.create_table(
|
||||
'external_channel',
|
||||
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('title', sa.String(), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=True),
|
||||
sa.Column('description', sa.String(), nullable=True),
|
||||
sa.Column('subscribers_count', sa.Integer(), nullable=True),
|
||||
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['user_id'],
|
||||
['user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_external_channel_telegram_id'), 'external_channel', ['telegram_id'], unique=True)
|
||||
op.create_index(op.f('ix_external_channel_user_id'), 'external_channel', ['user_id'], unique=False)
|
||||
op.create_index(op.f('ix_external_channel_username'), 'external_channel', ['username'], unique=False)
|
||||
op.create_table(
|
||||
'login_token',
|
||||
sa.Column('token', sa.String(), nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column('used_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['user_id'],
|
||||
['user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_login_token_token'), 'login_token', ['token'], unique=True)
|
||||
op.create_table(
|
||||
'target_channel',
|
||||
sa.Column('telegram_id', sa.BigInteger(), nullable=False),
|
||||
sa.Column('title', sa.String(), nullable=False),
|
||||
sa.Column('username', sa.String(), nullable=True),
|
||||
sa.Column('status', sa.Enum('ACTIVE', 'INACTIVE', 'ARCHIVED', name='targetchannelstatus'), nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['user_id'],
|
||||
['user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_target_channel_telegram_id'), 'target_channel', ['telegram_id'], unique=True)
|
||||
op.create_index(op.f('ix_target_channel_user_id'), 'target_channel', ['user_id'], unique=False)
|
||||
op.create_index(op.f('ix_target_channel_username'), 'target_channel', ['username'], unique=False)
|
||||
op.create_table(
|
||||
'creative',
|
||||
sa.Column('name', sa.String(), nullable=False),
|
||||
sa.Column('text', sa.String(), nullable=False),
|
||||
sa.Column('status', sa.Enum('ACTIVE', 'ARCHIVED', name='creativestatus'), nullable=False),
|
||||
sa.Column('placements_count', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('target_channel_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['target_channel_id'],
|
||||
['target_channel.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['user_id'],
|
||||
['user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_creative_target_channel_id'), 'creative', ['target_channel_id'], unique=False)
|
||||
op.create_index(op.f('ix_creative_user_id'), 'creative', ['user_id'], unique=False)
|
||||
op.create_table(
|
||||
'target_external_channel',
|
||||
sa.Column('target_channel_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('external_channel_id', sa.Uuid(), nullable=False),
|
||||
sa.ForeignKeyConstraint(['external_channel_id'], ['external_channel.id'], ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['target_channel_id'], ['target_channel.id'], ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('target_channel_id', 'external_channel_id'),
|
||||
)
|
||||
op.create_table(
|
||||
'placement',
|
||||
sa.Column('target_channel_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('external_channel_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('creative_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('user_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('placement_date', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column('cost', sa.Float(), nullable=True),
|
||||
sa.Column('comment', sa.String(), nullable=True),
|
||||
sa.Column('ad_post_url', sa.String(), nullable=True),
|
||||
sa.Column('invite_link_type', sa.Enum('PUBLIC', 'APPROVAL', name='invitelinktype'), nullable=False),
|
||||
sa.Column('invite_link', sa.String(), nullable=False),
|
||||
sa.Column('status', sa.Enum('ACTIVE', 'ARCHIVED', name='placementstatus'), nullable=False),
|
||||
sa.Column('subscriptions_count', sa.Integer(), server_default='0', nullable=False),
|
||||
sa.Column('views_count', sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
'views_availability',
|
||||
sa.Enum('UNKNOWN', 'AVAILABLE', 'UNAVAILABLE', 'MANUAL', name='postviewsavailability'),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column('last_views_fetch_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['creative_id'],
|
||||
['creative.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['external_channel_id'],
|
||||
['external_channel.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['target_channel_id'],
|
||||
['target_channel.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['user_id'],
|
||||
['user.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_placement_creative_id'), 'placement', ['creative_id'], unique=False)
|
||||
op.create_index(op.f('ix_placement_external_channel_id'), 'placement', ['external_channel_id'], unique=False)
|
||||
op.create_index(op.f('ix_placement_target_channel_id'), 'placement', ['target_channel_id'], unique=False)
|
||||
op.create_index(op.f('ix_placement_user_id'), 'placement', ['user_id'], unique=False)
|
||||
op.create_table(
|
||||
'placement_views_history',
|
||||
sa.Column('placement_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('views_count', sa.Integer(), nullable=False),
|
||||
sa.Column('fetched_at', sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['placement_id'],
|
||||
['placement.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_placement_views_history_fetched_at'), 'placement_views_history', ['fetched_at'], unique=False
|
||||
)
|
||||
op.create_index(
|
||||
op.f('ix_placement_views_history_placement_id'), 'placement_views_history', ['placement_id'], unique=False
|
||||
)
|
||||
op.create_table(
|
||||
'subscription',
|
||||
sa.Column('placement_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('subscriber_id', sa.Uuid(), nullable=False),
|
||||
sa.Column('invite_link', sa.String(), nullable=False),
|
||||
sa.Column('id', sa.Uuid(), 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.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
['placement_id'],
|
||||
['placement.id'],
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
['subscriber_id'],
|
||||
['subscriber.id'],
|
||||
),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index(op.f('ix_subscription_invite_link'), 'subscription', ['invite_link'], unique=False)
|
||||
op.create_index(op.f('ix_subscription_placement_id'), 'subscription', ['placement_id'], unique=False)
|
||||
op.create_index(op.f('ix_subscription_subscriber_id'), 'subscription', ['subscriber_id'], unique=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_index(op.f('ix_subscription_subscriber_id'), table_name='subscription')
|
||||
op.drop_index(op.f('ix_subscription_placement_id'), table_name='subscription')
|
||||
op.drop_index(op.f('ix_subscription_invite_link'), table_name='subscription')
|
||||
op.drop_table('subscription')
|
||||
op.drop_index(op.f('ix_placement_views_history_placement_id'), table_name='placement_views_history')
|
||||
op.drop_index(op.f('ix_placement_views_history_fetched_at'), table_name='placement_views_history')
|
||||
op.drop_table('placement_views_history')
|
||||
op.drop_index(op.f('ix_placement_user_id'), table_name='placement')
|
||||
op.drop_index(op.f('ix_placement_target_channel_id'), table_name='placement')
|
||||
op.drop_index(op.f('ix_placement_external_channel_id'), table_name='placement')
|
||||
op.drop_index(op.f('ix_placement_creative_id'), table_name='placement')
|
||||
op.drop_table('placement')
|
||||
op.drop_table('target_external_channel')
|
||||
op.drop_index(op.f('ix_creative_user_id'), table_name='creative')
|
||||
op.drop_index(op.f('ix_creative_target_channel_id'), table_name='creative')
|
||||
op.drop_table('creative')
|
||||
op.drop_index(op.f('ix_target_channel_username'), table_name='target_channel')
|
||||
op.drop_index(op.f('ix_target_channel_user_id'), table_name='target_channel')
|
||||
op.drop_index(op.f('ix_target_channel_telegram_id'), table_name='target_channel')
|
||||
op.drop_table('target_channel')
|
||||
op.drop_index(op.f('ix_login_token_token'), table_name='login_token')
|
||||
op.drop_table('login_token')
|
||||
op.drop_index(op.f('ix_external_channel_username'), table_name='external_channel')
|
||||
op.drop_index(op.f('ix_external_channel_user_id'), table_name='external_channel')
|
||||
op.drop_index(op.f('ix_external_channel_telegram_id'), table_name='external_channel')
|
||||
op.drop_table('external_channel')
|
||||
op.drop_index(op.f('ix_user_telegram_id'), table_name='user')
|
||||
op.drop_table('user')
|
||||
op.drop_index(op.f('ix_subscriber_telegram_id'), table_name='subscriber')
|
||||
op.drop_table('subscriber')
|
||||
# ### end Alembic commands ###
|
||||
221
migrations/models/0_20251211144442_init.py
Normal file
221
migrations/models/0_20251211144442_init.py
Normal file
@@ -0,0 +1,221 @@
|
||||
from tortoise import BaseDBAsyncClient
|
||||
|
||||
RUN_IN_TRANSACTION = True
|
||||
|
||||
|
||||
async def upgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
CREATE TABLE IF NOT EXISTS "subscriber" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"username" VARCHAR(255),
|
||||
"first_name" VARCHAR(255),
|
||||
"last_name" VARCHAR(255)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_subscriber_telegra_5eef32" ON "subscriber" ("telegram_id");
|
||||
CREATE TABLE IF NOT EXISTS "telegram_state" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"state" VARCHAR(24) NOT NULL,
|
||||
"context" JSONB NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_telegram_st_telegra_750503" ON "telegram_state" ("telegram_id");
|
||||
COMMENT ON COLUMN "telegram_state"."state" IS 'CREATIVE_WAITING_CHANNEL: creative_waiting_channel\nCREATIVE_WAITING_NAME: creative_waiting_name\nCREATIVE_WAITING_TEXT: creative_waiting_text';
|
||||
CREATE TABLE IF NOT EXISTS "user" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"username" VARCHAR(255)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_user_telegra_66ffbd" ON "user" ("telegram_id");
|
||||
CREATE TABLE IF NOT EXISTS "external_channel" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"username" VARCHAR(255),
|
||||
"description" TEXT,
|
||||
"subscribers_count" INT,
|
||||
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_external_ch_telegra_13cca5" ON "external_channel" ("telegram_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_external_ch_usernam_9b9fb4" ON "external_channel" ("username");
|
||||
CREATE INDEX IF NOT EXISTS "idx_external_ch_user_id_dd2b1c" ON "external_channel" ("user_id");
|
||||
CREATE TABLE IF NOT EXISTS "login_token" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"token" VARCHAR(255) NOT NULL UNIQUE,
|
||||
"expires_at" TIMESTAMPTZ NOT NULL,
|
||||
"used_at" TIMESTAMPTZ,
|
||||
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_login_token_token_2582d2" ON "login_token" ("token");
|
||||
CREATE TABLE IF NOT EXISTS "target_channel" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"telegram_id" BIGINT NOT NULL UNIQUE,
|
||||
"title" VARCHAR(255) NOT NULL,
|
||||
"username" VARCHAR(255),
|
||||
"status" VARCHAR(8) NOT NULL DEFAULT 'active',
|
||||
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_target_chan_telegra_ab5a54" ON "target_channel" ("telegram_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_target_chan_usernam_a6cf8b" ON "target_channel" ("username");
|
||||
CREATE INDEX IF NOT EXISTS "idx_target_chan_user_id_c3bdc7" ON "target_channel" ("user_id");
|
||||
COMMENT ON COLUMN "target_channel"."status" IS 'ACTIVE: active\nINACTIVE: inactive\nARCHIVED: archived';
|
||||
CREATE TABLE IF NOT EXISTS "creative" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"name" VARCHAR(255) NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
"status" VARCHAR(8) NOT NULL DEFAULT 'active',
|
||||
"placements_count" INT NOT NULL DEFAULT 0,
|
||||
"target_channel_id" UUID NOT NULL REFERENCES "target_channel" ("id") ON DELETE CASCADE,
|
||||
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_creative_target__8e3b64" ON "creative" ("target_channel_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_creative_user_id_a27cfc" ON "creative" ("user_id");
|
||||
COMMENT ON COLUMN "creative"."status" IS 'ACTIVE: active\nARCHIVED: archived';
|
||||
CREATE TABLE IF NOT EXISTS "placement" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"placement_date" TIMESTAMPTZ NOT NULL,
|
||||
"cost" DOUBLE PRECISION,
|
||||
"comment" TEXT,
|
||||
"ad_post_url" VARCHAR(512),
|
||||
"invite_link_type" VARCHAR(8) NOT NULL,
|
||||
"invite_link" VARCHAR(512) NOT NULL,
|
||||
"external_channel_message_id" INT,
|
||||
"status" VARCHAR(8) NOT NULL DEFAULT 'active',
|
||||
"subscriptions_count" INT NOT NULL DEFAULT 0,
|
||||
"views_count" INT,
|
||||
"views_availability" VARCHAR(11) NOT NULL DEFAULT 'unknown',
|
||||
"last_views_fetch_at" TIMESTAMPTZ,
|
||||
"creative_id" UUID NOT NULL REFERENCES "creative" ("id") ON DELETE CASCADE,
|
||||
"external_channel_id" UUID NOT NULL REFERENCES "external_channel" ("id") ON DELETE CASCADE,
|
||||
"target_channel_id" UUID NOT NULL REFERENCES "target_channel" ("id") ON DELETE CASCADE,
|
||||
"user_id" UUID NOT NULL REFERENCES "user" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_creativ_32111d" ON "placement" ("creative_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_externa_5b96c7" ON "placement" ("external_channel_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_target__ed9070" ON "placement" ("target_channel_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_user_id_7abaf9" ON "placement" ("user_id");
|
||||
COMMENT ON COLUMN "placement"."invite_link_type" IS 'PUBLIC: public\nAPPROVAL: approval';
|
||||
COMMENT ON COLUMN "placement"."status" IS 'PENDING: pending\nACTIVE: active\nDELETED: deleted\nARCHIVED: archived';
|
||||
COMMENT ON COLUMN "placement"."views_availability" IS 'UNKNOWN: unknown\nAVAILABLE: available\nUNAVAILABLE: unavailable\nMANUAL: manual';
|
||||
CREATE TABLE IF NOT EXISTS "placement_views_history" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"views_count" INT NOT NULL,
|
||||
"fetched_at" TIMESTAMPTZ NOT NULL,
|
||||
"placement_id" UUID NOT NULL REFERENCES "placement" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_v_fetched_4c8f51" ON "placement_views_history" ("fetched_at");
|
||||
CREATE INDEX IF NOT EXISTS "idx_placement_v_placeme_89e0f4" ON "placement_views_history" ("placement_id");
|
||||
CREATE TABLE IF NOT EXISTS "subscription" (
|
||||
"created_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted_at" TIMESTAMPTZ,
|
||||
"id" UUID NOT NULL PRIMARY KEY,
|
||||
"invite_link" VARCHAR(512) NOT NULL,
|
||||
"status" VARCHAR(12) NOT NULL DEFAULT 'active',
|
||||
"unsubscribed_at" TIMESTAMPTZ,
|
||||
"placement_id" UUID NOT NULL REFERENCES "placement" ("id") ON DELETE CASCADE,
|
||||
"subscriber_id" UUID NOT NULL REFERENCES "subscriber" ("id") ON DELETE CASCADE,
|
||||
"target_channel_id" UUID NOT NULL REFERENCES "target_channel" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS "idx_subscriptio_invite__f2058d" ON "subscription" ("invite_link");
|
||||
CREATE INDEX IF NOT EXISTS "idx_subscriptio_placeme_5decb2" ON "subscription" ("placement_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_subscriptio_subscri_e75729" ON "subscription" ("subscriber_id");
|
||||
CREATE INDEX IF NOT EXISTS "idx_subscriptio_target__79117e" ON "subscription" ("target_channel_id");
|
||||
COMMENT ON COLUMN "subscription"."status" IS 'ACTIVE: active\nUNSUBSCRIBED: unsubscribed';
|
||||
CREATE TABLE IF NOT EXISTS "aerich" (
|
||||
"id" SERIAL NOT NULL PRIMARY KEY,
|
||||
"version" VARCHAR(255) NOT NULL,
|
||||
"app" VARCHAR(100) NOT NULL,
|
||||
"content" JSONB NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "target_external_channel" (
|
||||
"target_channel_id" UUID NOT NULL REFERENCES "target_channel" ("id") ON DELETE CASCADE,
|
||||
"external_channel_id" UUID NOT NULL REFERENCES "external_channel" ("id") ON DELETE CASCADE
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "uidx_target_exte_target__ba70ec" ON "target_external_channel" ("target_channel_id", "external_channel_id");"""
|
||||
|
||||
|
||||
async def downgrade(db: BaseDBAsyncClient) -> str:
|
||||
return """
|
||||
"""
|
||||
|
||||
|
||||
MODELS_STATE = (
|
||||
"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"
|
||||
)
|
||||
@@ -5,6 +5,7 @@ description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"aerich>=0.9.2",
|
||||
"aiogram>=3.16.0",
|
||||
"aiolimiter>=1.2.1",
|
||||
"asyncpg>=0.30.0",
|
||||
@@ -66,3 +67,8 @@ exclude = [
|
||||
[tool.pytest.ini_options]
|
||||
testpaths = ["tests"]
|
||||
pythonpath = ["src", "."]
|
||||
|
||||
[tool.aerich]
|
||||
tortoise_orm = "shared.datebase_base.TORTOISE_ORM"
|
||||
location = "./migrations"
|
||||
src_folder = "./."
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import os
|
||||
|
||||
import pydantic
|
||||
from tortoise import Tortoise
|
||||
|
||||
@@ -10,6 +12,19 @@ class DatabaseConfig(pydantic.BaseModel):
|
||||
MAX_OVERFLOW: int = 10
|
||||
|
||||
|
||||
TORTOISE_ORM = {
|
||||
'connections': {'default': os.getenv('DB__URL')},
|
||||
'apps': {
|
||||
'models': {
|
||||
'models': ['src.domain', 'aerich.models'],
|
||||
'default_connection': 'default',
|
||||
},
|
||||
},
|
||||
'use_tz': True,
|
||||
'timezone': 'UTC',
|
||||
}
|
||||
|
||||
|
||||
class DatabaseBase:
|
||||
def __init__(self, config: DatabaseConfig) -> None:
|
||||
self.config = config
|
||||
@@ -17,7 +32,7 @@ class DatabaseBase:
|
||||
async def connect(self) -> None:
|
||||
await Tortoise.init(
|
||||
db_url=str(self.config.URL),
|
||||
modules={'models': ['src.domain']},
|
||||
modules={'models': ['src.domain', 'aerich.models']},
|
||||
use_tz=True,
|
||||
timezone='UTC',
|
||||
)
|
||||
@@ -47,35 +62,19 @@ class DatabaseBase:
|
||||
|
||||
conn = connections.get('default')
|
||||
|
||||
# Проверяем существование таблицы alembic_version
|
||||
# Проверяем существование таблицы aerich
|
||||
result = await conn.execute_query(
|
||||
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'alembic_version')"
|
||||
"SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = 'aerich')"
|
||||
)
|
||||
if not result[1][0]['exists']:
|
||||
logging.error('Migrations not applied. Run: alembic upgrade head')
|
||||
logging.warning('Migrations not applied. Run: aerich upgrade')
|
||||
return
|
||||
|
||||
# Получаем текущую версию
|
||||
result = await conn.execute_query('SELECT version_num FROM alembic_version')
|
||||
if not result[1]:
|
||||
logging.error('No migration version found in database. Run: alembic upgrade head')
|
||||
return
|
||||
|
||||
current_version = result[1][0]['version_num']
|
||||
|
||||
# Сравниваем с head версией
|
||||
# Получаем последнюю примененную версию
|
||||
try:
|
||||
from alembic.config import Config
|
||||
from alembic.script import ScriptDirectory
|
||||
|
||||
alembic_cfg = Config('alembic.ini')
|
||||
script = ScriptDirectory.from_config(alembic_cfg)
|
||||
head_revision = script.get_current_head()
|
||||
|
||||
if head_revision and current_version != head_revision:
|
||||
logging.warning(
|
||||
f'Database version {current_version} is outdated (latest: {head_revision}). '
|
||||
f'Run: alembic upgrade head'
|
||||
)
|
||||
result = await conn.execute_query('SELECT version, app FROM aerich ORDER BY id DESC LIMIT 1')
|
||||
if result[1]:
|
||||
current_version = result[1][0]['version']
|
||||
logging.info(f'Current migration version: {current_version}')
|
||||
except Exception as e:
|
||||
logging.debug(f'Could not check migration head: {e}')
|
||||
logging.debug(f'Could not check migration version: {e}')
|
||||
|
||||
@@ -22,9 +22,8 @@ class ExternalChannel(TimestampedModel):
|
||||
'models.User', related_name='external_channels', on_delete=fields.CASCADE, index=True
|
||||
)
|
||||
|
||||
target_channels: fields.ManyToManyRelation['TargetChannel']
|
||||
|
||||
if TYPE_CHECKING:
|
||||
target_channels: fields.ManyToManyRelation['TargetChannel']
|
||||
user_id: UUID
|
||||
|
||||
class Meta:
|
||||
|
||||
@@ -32,6 +32,8 @@ class TargetChannel(TimestampedModel):
|
||||
'models.ExternalChannel',
|
||||
related_name='target_channels',
|
||||
through='target_external_channel',
|
||||
forward_key='external_channel_id',
|
||||
backward_key='target_channel_id',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
||||
38
uv.lock
generated
38
uv.lock
generated
@@ -2,6 +2,21 @@ version = 1
|
||||
revision = 3
|
||||
requires-python = ">=3.13"
|
||||
|
||||
[[package]]
|
||||
name = "aerich"
|
||||
version = "0.9.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
{ name = "asyncclick" },
|
||||
{ name = "dictdiffer" },
|
||||
{ name = "tortoise-orm" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c4/60/5d3885f531fab2cecec67510e7b821efc403940ed9eefd034b2c21350f3c/aerich-0.9.2.tar.gz", hash = "sha256:02d58658714eebe396fe7bd9f9401db3a60a44dc885910ad3990920d0357317d", size = 74231, upload-time = "2025-10-10T05:53:49.632Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/87/1a/956c6b1e35881bb9835a33c8db1565edcd133f8e45321010489092a0df40/aerich-0.9.2-py3-none-any.whl", hash = "sha256:d0f007acb21f6559f1eccd4e404fb039cf48af2689e0669afa62989389c0582d", size = 46451, upload-time = "2025-10-10T05:53:48.71Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aiofiles"
|
||||
version = "24.1.0"
|
||||
@@ -149,6 +164,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/15/b3/9b1a8074496371342ec1e796a96f99c82c945a339cd81a8e73de28b4cf9e/anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc", size = 109097, upload-time = "2025-09-23T09:19:10.601Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncclick"
|
||||
version = "8.3.0.7"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/ca/25e426d16bd0e91c1c9259112cecd17b2c2c239bdd8e5dba430f3bd5e3ef/asyncclick-8.3.0.7.tar.gz", hash = "sha256:8a80d8ac613098ee6a9a8f0248f60c66c273e22402cf3f115ed7f071acfc71d3", size = 277634, upload-time = "2025-10-11T08:35:44.841Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/d9/782ffcb4c97b889bc12d8276637d2739b99520390ee8fec77c07416c5d12/asyncclick-8.3.0.7-py3-none-any.whl", hash = "sha256:7607046de39a3f315867cad818849f973e29d350c10d92f251db3ff7600c6c7d", size = 109925, upload-time = "2025-10-11T08:35:43.378Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asyncpg"
|
||||
version = "0.30.0"
|
||||
@@ -217,6 +244,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dictdiffer"
|
||||
version = "0.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/61/7b/35cbccb7effc5d7e40f4c55e2b79399e1853041997fcda15c9ff160abba0/dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", size = 31513, upload-time = "2021-07-22T13:24:29.276Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/47/ef/4cb333825d10317a36a1154341ba37e6e9c087bac99c1990ef07ffdb376f/dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595", size = 16754, upload-time = "2021-07-22T13:24:26.783Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.121.0"
|
||||
@@ -970,6 +1006,7 @@ name = "tgex-backend"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "aerich" },
|
||||
{ name = "aiogram" },
|
||||
{ name = "aiolimiter" },
|
||||
{ name = "asyncpg" },
|
||||
@@ -999,6 +1036,7 @@ dev = [
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "aerich", specifier = ">=0.9.2" },
|
||||
{ name = "aiogram", specifier = ">=3.16.0" },
|
||||
{ name = "aiolimiter", specifier = ">=1.2.1" },
|
||||
{ name = "asyncpg", specifier = ">=0.30.0" },
|
||||
|
||||
Reference in New Issue
Block a user