mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-03-05 05:13:21 +00:00
Merge pull request #160 from Fr1ngg/codex/fix-bot-start-error-due-to-missing-column-anc9e0
Expand universal migration to provision promo groups
This commit is contained in:
@@ -74,6 +74,104 @@ async def check_column_exists(table_name: str, column_name: str) -> bool:
|
||||
logger.error(f"Ошибка проверки существования колонки {column_name}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def check_constraint_exists(table_name: str, constraint_name: str) -> bool:
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
db_type = await get_database_type()
|
||||
|
||||
if db_type == "postgresql":
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = :table_name
|
||||
AND constraint_name = :constraint_name
|
||||
"""
|
||||
),
|
||||
{"table_name": table_name, "constraint_name": constraint_name},
|
||||
)
|
||||
return result.fetchone() is not None
|
||||
|
||||
if db_type == "mysql":
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM information_schema.table_constraints
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = :table_name
|
||||
AND constraint_name = :constraint_name
|
||||
"""
|
||||
),
|
||||
{"table_name": table_name, "constraint_name": constraint_name},
|
||||
)
|
||||
return result.fetchone() is not None
|
||||
|
||||
if db_type == "sqlite":
|
||||
result = await conn.execute(text(f"PRAGMA foreign_key_list({table_name})"))
|
||||
rows = result.fetchall()
|
||||
return any(row[5] == constraint_name for row in rows)
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка проверки существования ограничения {constraint_name} для {table_name}: {e}"
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
async def check_index_exists(table_name: str, index_name: str) -> bool:
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
db_type = await get_database_type()
|
||||
|
||||
if db_type == "postgresql":
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM pg_indexes
|
||||
WHERE schemaname = 'public'
|
||||
AND tablename = :table_name
|
||||
AND indexname = :index_name
|
||||
"""
|
||||
),
|
||||
{"table_name": table_name, "index_name": index_name},
|
||||
)
|
||||
return result.fetchone() is not None
|
||||
|
||||
if db_type == "mysql":
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM information_schema.statistics
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = :table_name
|
||||
AND index_name = :index_name
|
||||
"""
|
||||
),
|
||||
{"table_name": table_name, "index_name": index_name},
|
||||
)
|
||||
return result.fetchone() is not None
|
||||
|
||||
if db_type == "sqlite":
|
||||
result = await conn.execute(text(f"PRAGMA index_list({table_name})"))
|
||||
rows = result.fetchall()
|
||||
return any(row[1] == index_name for row in rows)
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(
|
||||
f"Ошибка проверки существования индекса {index_name} для {table_name}: {e}"
|
||||
)
|
||||
return False
|
||||
|
||||
async def create_cryptobot_payments_table():
|
||||
table_exists = await check_table_exists('cryptobot_payments')
|
||||
if table_exists:
|
||||
@@ -248,6 +346,276 @@ async def create_user_messages_table():
|
||||
logger.error(f"Ошибка создания таблицы user_messages: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def ensure_promo_groups_setup():
|
||||
logger.info("=== НАСТРОЙКА ПРОМО ГРУПП ===")
|
||||
|
||||
try:
|
||||
promo_table_exists = await check_table_exists("promo_groups")
|
||||
|
||||
async with engine.begin() as conn:
|
||||
db_type = await get_database_type()
|
||||
|
||||
if not promo_table_exists:
|
||||
if db_type == "sqlite":
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS promo_groups (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
server_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
traffic_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
device_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
is_default BOOLEAN NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
await conn.execute(
|
||||
text(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS uq_promo_groups_name ON promo_groups(name)"
|
||||
)
|
||||
)
|
||||
elif db_type == "postgresql":
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS promo_groups (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
server_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
traffic_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
device_discount_percent INTEGER NOT NULL DEFAULT 0,
|
||||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
CONSTRAINT uq_promo_groups_name UNIQUE (name)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
elif db_type == "mysql":
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS promo_groups (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
server_discount_percent INT NOT NULL DEFAULT 0,
|
||||
traffic_discount_percent INT NOT NULL DEFAULT 0,
|
||||
device_discount_percent INT NOT NULL DEFAULT 0,
|
||||
is_default TINYINT(1) NOT NULL DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
UNIQUE KEY uq_promo_groups_name (name)
|
||||
) ENGINE=InnoDB
|
||||
"""
|
||||
)
|
||||
)
|
||||
else:
|
||||
logger.error(f"Неподдерживаемый тип БД для promo_groups: {db_type}")
|
||||
return False
|
||||
|
||||
logger.info("Создана таблица promo_groups")
|
||||
|
||||
if db_type == "postgresql" and not await check_constraint_exists(
|
||||
"promo_groups", "uq_promo_groups_name"
|
||||
):
|
||||
try:
|
||||
await conn.execute(
|
||||
text(
|
||||
"ALTER TABLE promo_groups ADD CONSTRAINT uq_promo_groups_name UNIQUE (name)"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось добавить уникальное ограничение uq_promo_groups_name: {e}"
|
||||
)
|
||||
|
||||
column_exists = await check_column_exists("users", "promo_group_id")
|
||||
|
||||
if not column_exists:
|
||||
if db_type == "sqlite":
|
||||
await conn.execute(text("ALTER TABLE users ADD COLUMN promo_group_id INTEGER"))
|
||||
elif db_type == "postgresql":
|
||||
await conn.execute(text("ALTER TABLE users ADD COLUMN promo_group_id INTEGER"))
|
||||
elif db_type == "mysql":
|
||||
await conn.execute(text("ALTER TABLE users ADD COLUMN promo_group_id INT"))
|
||||
else:
|
||||
logger.error(f"Неподдерживаемый тип БД для promo_group_id: {db_type}")
|
||||
return False
|
||||
|
||||
logger.info("Добавлена колонка users.promo_group_id")
|
||||
|
||||
index_exists = await check_index_exists("users", "ix_users_promo_group_id")
|
||||
|
||||
if not index_exists:
|
||||
try:
|
||||
if db_type == "sqlite":
|
||||
await conn.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS ix_users_promo_group_id ON users(promo_group_id)")
|
||||
)
|
||||
elif db_type == "postgresql":
|
||||
await conn.execute(
|
||||
text("CREATE INDEX IF NOT EXISTS ix_users_promo_group_id ON users(promo_group_id)")
|
||||
)
|
||||
elif db_type == "mysql":
|
||||
await conn.execute(
|
||||
text("CREATE INDEX ix_users_promo_group_id ON users(promo_group_id)")
|
||||
)
|
||||
logger.info("Создан индекс ix_users_promo_group_id")
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось создать индекс ix_users_promo_group_id: {e}")
|
||||
|
||||
default_group_name = "Базовый юзер"
|
||||
default_group_id = None
|
||||
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"SELECT id, is_default FROM promo_groups WHERE name = :name LIMIT 1"
|
||||
),
|
||||
{"name": default_group_name},
|
||||
)
|
||||
row = result.fetchone()
|
||||
|
||||
if row:
|
||||
default_group_id = row[0]
|
||||
if not row[1]:
|
||||
await conn.execute(
|
||||
text(
|
||||
"UPDATE promo_groups SET is_default = :is_default WHERE id = :group_id"
|
||||
),
|
||||
{"is_default": True, "group_id": default_group_id},
|
||||
)
|
||||
else:
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"SELECT id FROM promo_groups WHERE is_default = :is_default LIMIT 1"
|
||||
),
|
||||
{"is_default": True},
|
||||
)
|
||||
existing_default = result.fetchone()
|
||||
|
||||
if existing_default:
|
||||
default_group_id = existing_default[0]
|
||||
else:
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
INSERT INTO promo_groups (
|
||||
name,
|
||||
server_discount_percent,
|
||||
traffic_discount_percent,
|
||||
device_discount_percent,
|
||||
is_default
|
||||
) VALUES (:name, 0, 0, 0, :is_default)
|
||||
"""
|
||||
),
|
||||
{"name": default_group_name, "is_default": True},
|
||||
)
|
||||
|
||||
result = await conn.execute(
|
||||
text(
|
||||
"SELECT id FROM promo_groups WHERE name = :name LIMIT 1"
|
||||
),
|
||||
{"name": default_group_name},
|
||||
)
|
||||
row = result.fetchone()
|
||||
default_group_id = row[0] if row else None
|
||||
|
||||
if default_group_id is None:
|
||||
logger.error("Не удалось определить идентификатор базовой промо-группы")
|
||||
return False
|
||||
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE users
|
||||
SET promo_group_id = :group_id
|
||||
WHERE promo_group_id IS NULL
|
||||
"""
|
||||
),
|
||||
{"group_id": default_group_id},
|
||||
)
|
||||
|
||||
if db_type == "postgresql":
|
||||
constraint_exists = await check_constraint_exists(
|
||||
"users", "fk_users_promo_group_id_promo_groups"
|
||||
)
|
||||
if not constraint_exists:
|
||||
try:
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT fk_users_promo_group_id_promo_groups
|
||||
FOREIGN KEY (promo_group_id)
|
||||
REFERENCES promo_groups(id)
|
||||
ON DELETE RESTRICT
|
||||
"""
|
||||
)
|
||||
)
|
||||
logger.info("Добавлен внешний ключ users -> promo_groups")
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось добавить внешний ключ users.promo_group_id: {e}"
|
||||
)
|
||||
|
||||
try:
|
||||
await conn.execute(
|
||||
text(
|
||||
"ALTER TABLE users ALTER COLUMN promo_group_id SET NOT NULL"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось сделать users.promo_group_id NOT NULL: {e}"
|
||||
)
|
||||
|
||||
elif db_type == "mysql":
|
||||
constraint_exists = await check_constraint_exists(
|
||||
"users", "fk_users_promo_group_id_promo_groups"
|
||||
)
|
||||
if not constraint_exists:
|
||||
try:
|
||||
await conn.execute(
|
||||
text(
|
||||
"""
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT fk_users_promo_group_id_promo_groups
|
||||
FOREIGN KEY (promo_group_id)
|
||||
REFERENCES promo_groups(id)
|
||||
ON DELETE RESTRICT
|
||||
"""
|
||||
)
|
||||
)
|
||||
logger.info("Добавлен внешний ключ users -> promo_groups")
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось добавить внешний ключ users.promo_group_id: {e}"
|
||||
)
|
||||
|
||||
try:
|
||||
await conn.execute(
|
||||
text(
|
||||
"ALTER TABLE users MODIFY promo_group_id INT NOT NULL"
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(
|
||||
f"Не удалось сделать users.promo_group_id NOT NULL: {e}"
|
||||
)
|
||||
|
||||
logger.info("✅ Промо группы настроены")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка настройки промо групп: {e}")
|
||||
return False
|
||||
|
||||
async def add_welcome_text_is_enabled_column():
|
||||
column_exists = await check_column_exists('welcome_texts', 'is_enabled')
|
||||
if column_exists:
|
||||
@@ -688,7 +1056,14 @@ async def run_universal_migration():
|
||||
logger.info("✅ Медиа поля в broadcast_history готовы")
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с добавлением медиа полей")
|
||||
|
||||
|
||||
logger.info("=== НАСТРОЙКА ПРОМО ГРУПП ===")
|
||||
promo_groups_ready = await ensure_promo_groups_setup()
|
||||
if promo_groups_ready:
|
||||
logger.info("✅ Промо группы готовы")
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с настройкой промо групп")
|
||||
|
||||
logger.info("=== ОБНОВЛЕНИЕ ВНЕШНИХ КЛЮЧЕЙ ===")
|
||||
fk_updated = await fix_foreign_keys_for_user_deletion()
|
||||
if fk_updated:
|
||||
@@ -756,10 +1131,12 @@ async def check_migration_status():
|
||||
"cryptobot_table": False,
|
||||
"user_messages_table": False,
|
||||
"welcome_texts_table": False,
|
||||
"welcome_texts_is_enabled_column": False,
|
||||
"broadcast_history_media_fields": False,
|
||||
"welcome_texts_is_enabled_column": False,
|
||||
"broadcast_history_media_fields": False,
|
||||
"subscription_duplicates": False,
|
||||
"subscription_conversions_table": False
|
||||
"subscription_conversions_table": False,
|
||||
"promo_groups_table": False,
|
||||
"users_promo_group_column": False
|
||||
}
|
||||
|
||||
status["has_made_first_topup_column"] = await check_column_exists('users', 'has_made_first_topup')
|
||||
@@ -768,8 +1145,10 @@ async def check_migration_status():
|
||||
status["user_messages_table"] = await check_table_exists('user_messages')
|
||||
status["welcome_texts_table"] = await check_table_exists('welcome_texts')
|
||||
status["subscription_conversions_table"] = await check_table_exists('subscription_conversions')
|
||||
|
||||
status["promo_groups_table"] = await check_table_exists('promo_groups')
|
||||
|
||||
status["welcome_texts_is_enabled_column"] = await check_column_exists('welcome_texts', 'is_enabled')
|
||||
status["users_promo_group_column"] = await check_column_exists('users', 'promo_group_id')
|
||||
|
||||
media_fields_exist = (
|
||||
await check_column_exists('broadcast_history', 'has_media') and
|
||||
@@ -797,9 +1176,11 @@ async def check_migration_status():
|
||||
"user_messages_table": "Таблица пользовательских сообщений",
|
||||
"welcome_texts_table": "Таблица приветственных текстов",
|
||||
"welcome_texts_is_enabled_column": "Поле is_enabled в welcome_texts",
|
||||
"broadcast_history_media_fields": "Медиа поля в broadcast_history",
|
||||
"broadcast_history_media_fields": "Медиа поля в broadcast_history",
|
||||
"subscription_conversions_table": "Таблица конверсий подписок",
|
||||
"subscription_duplicates": "Отсутствие дубликатов подписок"
|
||||
"subscription_duplicates": "Отсутствие дубликатов подписок",
|
||||
"promo_groups_table": "Таблица промо-групп",
|
||||
"users_promo_group_column": "Колонка promo_group_id у пользователей"
|
||||
}
|
||||
|
||||
for check_key, check_status in status.items():
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
"""add promo groups table and link users"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
PROMO_GROUPS_TABLE = "promo_groups"
|
||||
USERS_TABLE = "users"
|
||||
PROMO_GROUP_COLUMN = "promo_group_id"
|
||||
PROMO_GROUP_INDEX = "ix_users_promo_group_id"
|
||||
PROMO_GROUP_FK = "fk_users_promo_group_id_promo_groups"
|
||||
DEFAULT_PROMO_GROUP_NAME = "Базовый юзер"
|
||||
|
||||
|
||||
def _table_exists(inspector: sa.Inspector, table_name: str) -> bool:
|
||||
return table_name in inspector.get_table_names()
|
||||
|
||||
|
||||
def _column_exists(inspector: sa.Inspector, table_name: str, column_name: str) -> bool:
|
||||
return any(col["name"] == column_name for col in inspector.get_columns(table_name))
|
||||
|
||||
|
||||
def _index_exists(inspector: sa.Inspector, table_name: str, index_name: str) -> bool:
|
||||
return any(index["name"] == index_name for index in inspector.get_indexes(table_name))
|
||||
|
||||
|
||||
def _foreign_key_exists(inspector: sa.Inspector, table_name: str, fk_name: str) -> bool:
|
||||
return any(fk["name"] == fk_name for fk in inspector.get_foreign_keys(table_name))
|
||||
|
||||
revision: str = "1f5f3a3f5a4d"
|
||||
down_revision: Union[str, None] = "cbd1be472f3d"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if not _table_exists(inspector, PROMO_GROUPS_TABLE):
|
||||
op.create_table(
|
||||
PROMO_GROUPS_TABLE,
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.Column(
|
||||
"server_discount_percent",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default=sa.text("0"),
|
||||
),
|
||||
sa.Column(
|
||||
"traffic_discount_percent",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default=sa.text("0"),
|
||||
),
|
||||
sa.Column(
|
||||
"device_discount_percent",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default=sa.text("0"),
|
||||
),
|
||||
sa.Column(
|
||||
"is_default",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.text("false"),
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.UniqueConstraint("name", name="uq_promo_groups_name"),
|
||||
)
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if not _column_exists(inspector, USERS_TABLE, PROMO_GROUP_COLUMN):
|
||||
op.add_column(
|
||||
USERS_TABLE,
|
||||
sa.Column(PROMO_GROUP_COLUMN, sa.Integer(), nullable=True),
|
||||
)
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if _column_exists(inspector, USERS_TABLE, PROMO_GROUP_COLUMN):
|
||||
if not _index_exists(inspector, USERS_TABLE, PROMO_GROUP_INDEX):
|
||||
op.create_index(PROMO_GROUP_INDEX, USERS_TABLE, [PROMO_GROUP_COLUMN])
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if not _foreign_key_exists(inspector, USERS_TABLE, PROMO_GROUP_FK):
|
||||
op.create_foreign_key(
|
||||
PROMO_GROUP_FK,
|
||||
USERS_TABLE,
|
||||
PROMO_GROUPS_TABLE,
|
||||
[PROMO_GROUP_COLUMN],
|
||||
["id"],
|
||||
ondelete="RESTRICT",
|
||||
)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if not _table_exists(inspector, PROMO_GROUPS_TABLE) or not _column_exists(
|
||||
inspector, USERS_TABLE, PROMO_GROUP_COLUMN
|
||||
):
|
||||
return
|
||||
|
||||
promo_groups_table = sa.table(
|
||||
PROMO_GROUPS_TABLE,
|
||||
sa.column("id", sa.Integer()),
|
||||
sa.column("name", sa.String()),
|
||||
sa.column("server_discount_percent", sa.Integer()),
|
||||
sa.column("traffic_discount_percent", sa.Integer()),
|
||||
sa.column("device_discount_percent", sa.Integer()),
|
||||
sa.column("is_default", sa.Boolean()),
|
||||
)
|
||||
|
||||
connection = bind
|
||||
existing_named_group = (
|
||||
connection.execute(
|
||||
sa.select(
|
||||
promo_groups_table.c.id,
|
||||
promo_groups_table.c.is_default,
|
||||
)
|
||||
.where(promo_groups_table.c.name == DEFAULT_PROMO_GROUP_NAME)
|
||||
.limit(1)
|
||||
)
|
||||
.mappings()
|
||||
.first()
|
||||
)
|
||||
|
||||
if existing_named_group:
|
||||
default_group_id = existing_named_group["id"]
|
||||
if not existing_named_group["is_default"]:
|
||||
connection.execute(
|
||||
sa.update(promo_groups_table)
|
||||
.where(promo_groups_table.c.id == default_group_id)
|
||||
.values(is_default=True)
|
||||
)
|
||||
else:
|
||||
default_group_id = connection.execute(
|
||||
sa.select(promo_groups_table.c.id)
|
||||
.where(promo_groups_table.c.is_default.is_(True))
|
||||
.limit(1)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if default_group_id is None:
|
||||
default_group_id = connection.execute(
|
||||
sa.insert(promo_groups_table)
|
||||
.values(
|
||||
name=DEFAULT_PROMO_GROUP_NAME,
|
||||
server_discount_percent=0,
|
||||
traffic_discount_percent=0,
|
||||
device_discount_percent=0,
|
||||
is_default=True,
|
||||
)
|
||||
.returning(promo_groups_table.c.id)
|
||||
).scalar_one()
|
||||
|
||||
users_table = sa.table(
|
||||
USERS_TABLE,
|
||||
sa.column("promo_group_id", sa.Integer()),
|
||||
)
|
||||
connection.execute(
|
||||
sa.update(users_table)
|
||||
.where(users_table.c.promo_group_id.is_(None))
|
||||
.values(promo_group_id=default_group_id)
|
||||
)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
column_info = next(
|
||||
(col for col in inspector.get_columns(USERS_TABLE) if col["name"] == PROMO_GROUP_COLUMN),
|
||||
None,
|
||||
)
|
||||
if column_info and column_info.get("nullable", True):
|
||||
op.alter_column(
|
||||
USERS_TABLE,
|
||||
PROMO_GROUP_COLUMN,
|
||||
existing_type=sa.Integer(),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if _column_exists(inspector, USERS_TABLE, PROMO_GROUP_COLUMN):
|
||||
column_info = next(
|
||||
(
|
||||
col
|
||||
for col in inspector.get_columns(USERS_TABLE)
|
||||
if col["name"] == PROMO_GROUP_COLUMN
|
||||
),
|
||||
None,
|
||||
)
|
||||
if column_info and not column_info.get("nullable", False):
|
||||
op.alter_column(
|
||||
USERS_TABLE,
|
||||
PROMO_GROUP_COLUMN,
|
||||
existing_type=sa.Integer(),
|
||||
nullable=True,
|
||||
)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _foreign_key_exists(inspector, USERS_TABLE, PROMO_GROUP_FK):
|
||||
op.drop_constraint(PROMO_GROUP_FK, USERS_TABLE, type_="foreignkey")
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _index_exists(inspector, USERS_TABLE, PROMO_GROUP_INDEX):
|
||||
op.drop_index(PROMO_GROUP_INDEX, table_name=USERS_TABLE)
|
||||
|
||||
op.drop_column(USERS_TABLE, PROMO_GROUP_COLUMN)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _table_exists(inspector, PROMO_GROUPS_TABLE):
|
||||
op.drop_table(PROMO_GROUPS_TABLE)
|
||||
@@ -6,6 +6,21 @@ from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
CAMPAIGNS_TABLE = "advertising_campaigns"
|
||||
CAMPAIGNS_START_INDEX = "ix_advertising_campaigns_start_parameter"
|
||||
CAMPAIGNS_ID_INDEX = "ix_advertising_campaigns_id"
|
||||
REGISTRATIONS_TABLE = "advertising_campaign_registrations"
|
||||
REGISTRATIONS_ID_INDEX = "ix_advertising_campaign_registrations_id"
|
||||
|
||||
|
||||
def _table_exists(inspector: sa.Inspector, table_name: str) -> bool:
|
||||
return table_name in inspector.get_table_names()
|
||||
|
||||
|
||||
def _index_exists(inspector: sa.Inspector, table_name: str, index_name: str) -> bool:
|
||||
return any(index["name"] == index_name for index in inspector.get_indexes(table_name))
|
||||
|
||||
|
||||
revision: str = "5d1f1f8b2e9a"
|
||||
down_revision: Union[str, None] = "cbd1be472f3d"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
@@ -13,58 +28,119 @@ depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"advertising_campaigns",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.Column("start_parameter", sa.String(length=64), nullable=False),
|
||||
sa.Column("bonus_type", sa.String(length=20), nullable=False),
|
||||
sa.Column("balance_bonus_kopeks", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("subscription_duration_days", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_traffic_gb", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_device_limit", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_squads", sa.JSON(), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_by", sa.Integer(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["created_by"], ["users.id"], ondelete="SET NULL"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_advertising_campaigns_start_parameter",
|
||||
"advertising_campaigns",
|
||||
["start_parameter"],
|
||||
unique=True,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_advertising_campaigns_id",
|
||||
"advertising_campaigns",
|
||||
["id"],
|
||||
)
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
op.create_table(
|
||||
"advertising_campaign_registrations",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("campaign_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.Column("bonus_type", sa.String(length=20), nullable=False),
|
||||
sa.Column("balance_bonus_kopeks", sa.Integer(), nullable=False, server_default="0"),
|
||||
sa.Column("subscription_duration_days", sa.Integer(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
|
||||
sa.ForeignKeyConstraint(["campaign_id"], ["advertising_campaigns.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.UniqueConstraint("campaign_id", "user_id", name="uq_campaign_user"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_advertising_campaign_registrations_id",
|
||||
"advertising_campaign_registrations",
|
||||
["id"],
|
||||
)
|
||||
if not _table_exists(inspector, CAMPAIGNS_TABLE):
|
||||
op.create_table(
|
||||
CAMPAIGNS_TABLE,
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("name", sa.String(length=255), nullable=False),
|
||||
sa.Column("start_parameter", sa.String(length=64), nullable=False),
|
||||
sa.Column("bonus_type", sa.String(length=20), nullable=False),
|
||||
sa.Column(
|
||||
"balance_bonus_kopeks",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
),
|
||||
sa.Column("subscription_duration_days", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_traffic_gb", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_device_limit", sa.Integer(), nullable=True),
|
||||
sa.Column("subscription_squads", sa.JSON(), nullable=True),
|
||||
sa.Column(
|
||||
"is_active",
|
||||
sa.Boolean(),
|
||||
nullable=False,
|
||||
server_default=sa.text("true"),
|
||||
),
|
||||
sa.Column("created_by", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(["created_by"], ["users.id"], ondelete="SET NULL"),
|
||||
)
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if not _index_exists(inspector, CAMPAIGNS_TABLE, CAMPAIGNS_START_INDEX):
|
||||
op.create_index(
|
||||
CAMPAIGNS_START_INDEX,
|
||||
CAMPAIGNS_TABLE,
|
||||
["start_parameter"],
|
||||
unique=True,
|
||||
)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if not _index_exists(inspector, CAMPAIGNS_TABLE, CAMPAIGNS_ID_INDEX):
|
||||
op.create_index(CAMPAIGNS_ID_INDEX, CAMPAIGNS_TABLE, ["id"])
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if not _table_exists(inspector, REGISTRATIONS_TABLE):
|
||||
op.create_table(
|
||||
REGISTRATIONS_TABLE,
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("campaign_id", sa.Integer(), nullable=False),
|
||||
sa.Column("user_id", sa.Integer(), nullable=False),
|
||||
sa.Column("bonus_type", sa.String(length=20), nullable=False),
|
||||
sa.Column(
|
||||
"balance_bonus_kopeks",
|
||||
sa.Integer(),
|
||||
nullable=False,
|
||||
server_default="0",
|
||||
),
|
||||
sa.Column("subscription_duration_days", sa.Integer(), nullable=True),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(),
|
||||
server_default=sa.func.now(),
|
||||
nullable=False,
|
||||
),
|
||||
sa.ForeignKeyConstraint(
|
||||
["campaign_id"],
|
||||
[f"{CAMPAIGNS_TABLE}.id"],
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.UniqueConstraint("campaign_id", "user_id", name="uq_campaign_user"),
|
||||
)
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if not _index_exists(inspector, REGISTRATIONS_TABLE, REGISTRATIONS_ID_INDEX):
|
||||
op.create_index(
|
||||
REGISTRATIONS_ID_INDEX,
|
||||
REGISTRATIONS_TABLE,
|
||||
["id"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_advertising_campaign_registrations_id", table_name="advertising_campaign_registrations")
|
||||
op.drop_table("advertising_campaign_registrations")
|
||||
op.drop_index("ix_advertising_campaigns_id", table_name="advertising_campaigns")
|
||||
op.drop_index("ix_advertising_campaigns_start_parameter", table_name="advertising_campaigns")
|
||||
op.drop_table("advertising_campaigns")
|
||||
bind = op.get_bind()
|
||||
inspector = sa.inspect(bind)
|
||||
|
||||
if _index_exists(inspector, REGISTRATIONS_TABLE, REGISTRATIONS_ID_INDEX):
|
||||
op.drop_index(REGISTRATIONS_ID_INDEX, table_name=REGISTRATIONS_TABLE)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _table_exists(inspector, REGISTRATIONS_TABLE):
|
||||
op.drop_table(REGISTRATIONS_TABLE)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _index_exists(inspector, CAMPAIGNS_TABLE, CAMPAIGNS_ID_INDEX):
|
||||
op.drop_index(CAMPAIGNS_ID_INDEX, table_name=CAMPAIGNS_TABLE)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _index_exists(inspector, CAMPAIGNS_TABLE, CAMPAIGNS_START_INDEX):
|
||||
op.drop_index(CAMPAIGNS_START_INDEX, table_name=CAMPAIGNS_TABLE)
|
||||
|
||||
inspector = sa.inspect(bind)
|
||||
if _table_exists(inspector, CAMPAIGNS_TABLE):
|
||||
op.drop_table(CAMPAIGNS_TABLE)
|
||||
|
||||
Reference in New Issue
Block a user