mirror of
https://github.com/BEDOLAGA-DEV/remnawave-bedolaga-telegram-bot.git
synced 2026-04-28 08:41:05 +00:00
Merge pull request #306 from Fr1ngg/revert-305-nuuguw-bedolaga/add-country-settings-configuration
Revert "Restrict server access by promo group"
This commit is contained in:
@@ -44,13 +44,6 @@ async def get_promo_group_by_id(db: AsyncSession, group_id: int) -> Optional[Pro
|
||||
return await db.get(PromoGroup, group_id)
|
||||
|
||||
|
||||
async def get_all_promo_groups(db: AsyncSession) -> List[PromoGroup]:
|
||||
result = await db.execute(
|
||||
select(PromoGroup).order_by(PromoGroup.is_default.desc(), PromoGroup.name)
|
||||
)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_default_promo_group(db: AsyncSession) -> Optional[PromoGroup]:
|
||||
result = await db.execute(
|
||||
select(PromoGroup).where(PromoGroup.is_default.is_(True))
|
||||
|
||||
@@ -4,13 +4,7 @@ from sqlalchemy import select, and_, func, update, delete, text
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.database.models import (
|
||||
ServerSquad,
|
||||
SubscriptionServer,
|
||||
Subscription,
|
||||
PromoGroup,
|
||||
server_squad_promo_groups,
|
||||
)
|
||||
from app.database.models import ServerSquad, SubscriptionServer, Subscription
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -24,43 +18,9 @@ async def create_server_squad(
|
||||
price_kopeks: int = 0,
|
||||
description: str = None,
|
||||
max_users: int = None,
|
||||
is_available: bool = True,
|
||||
promo_group_ids: Optional[List[int]] = None,
|
||||
is_available: bool = True
|
||||
) -> ServerSquad:
|
||||
result = await db.execute(
|
||||
select(PromoGroup.id)
|
||||
.where(PromoGroup.is_default.is_(True))
|
||||
.limit(1)
|
||||
)
|
||||
default_group_id = result.scalar_one_or_none()
|
||||
|
||||
if promo_group_ids is None:
|
||||
promo_group_ids = []
|
||||
if default_group_id is not None:
|
||||
promo_group_ids.append(default_group_id)
|
||||
else:
|
||||
fallback_group_result = await db.execute(
|
||||
select(PromoGroup.id)
|
||||
.order_by(PromoGroup.is_default.desc(), PromoGroup.id)
|
||||
.limit(1)
|
||||
)
|
||||
fallback_group_id = fallback_group_result.scalar_one_or_none()
|
||||
if fallback_group_id is not None:
|
||||
promo_group_ids.append(fallback_group_id)
|
||||
|
||||
unique_group_ids = list(dict.fromkeys(promo_group_ids or []))
|
||||
|
||||
if not unique_group_ids:
|
||||
raise ValueError("Server squad must have at least one promo group")
|
||||
|
||||
groups_result = await db.execute(
|
||||
select(PromoGroup).where(PromoGroup.id.in_(unique_group_ids))
|
||||
)
|
||||
groups = groups_result.scalars().all()
|
||||
|
||||
if len(groups) != len(unique_group_ids):
|
||||
raise ValueError("One or more promo groups not found")
|
||||
|
||||
|
||||
server_squad = ServerSquad(
|
||||
squad_uuid=squad_uuid,
|
||||
display_name=display_name,
|
||||
@@ -71,21 +31,12 @@ async def create_server_squad(
|
||||
max_users=max_users,
|
||||
is_available=is_available
|
||||
)
|
||||
|
||||
|
||||
db.add(server_squad)
|
||||
await db.flush()
|
||||
|
||||
server_squad.promo_groups = groups
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(server_squad)
|
||||
|
||||
logger.info(
|
||||
"✅ Создан сервер %s (UUID: %s) с промогруппами: %s",
|
||||
display_name,
|
||||
squad_uuid,
|
||||
", ".join(group.name for group in groups),
|
||||
)
|
||||
|
||||
logger.info(f"✅ Создан сервер {display_name} (UUID: {squad_uuid})")
|
||||
return server_squad
|
||||
|
||||
|
||||
@@ -95,9 +46,7 @@ async def get_server_squad_by_uuid(
|
||||
) -> Optional[ServerSquad]:
|
||||
|
||||
result = await db.execute(
|
||||
select(ServerSquad)
|
||||
.options(selectinload(ServerSquad.promo_groups))
|
||||
.where(ServerSquad.squad_uuid == squad_uuid)
|
||||
select(ServerSquad).where(ServerSquad.squad_uuid == squad_uuid)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@@ -108,9 +57,7 @@ async def get_server_squad_by_id(
|
||||
) -> Optional[ServerSquad]:
|
||||
|
||||
result = await db.execute(
|
||||
select(ServerSquad)
|
||||
.options(selectinload(ServerSquad.promo_groups))
|
||||
.where(ServerSquad.id == server_id)
|
||||
select(ServerSquad).where(ServerSquad.id == server_id)
|
||||
)
|
||||
return result.scalar_one_or_none()
|
||||
|
||||
@@ -122,7 +69,7 @@ async def get_all_server_squads(
|
||||
limit: int = 50
|
||||
) -> Tuple[List[ServerSquad], int]:
|
||||
|
||||
query = select(ServerSquad).options(selectinload(ServerSquad.promo_groups))
|
||||
query = select(ServerSquad)
|
||||
|
||||
if available_only:
|
||||
query = query.where(ServerSquad.is_available == True)
|
||||
@@ -144,75 +91,16 @@ async def get_all_server_squads(
|
||||
return servers, total_count
|
||||
|
||||
|
||||
async def get_available_server_squads(
|
||||
db: AsyncSession,
|
||||
promo_group_id: Optional[int] = None,
|
||||
) -> List[ServerSquad]:
|
||||
async def get_available_server_squads(db: AsyncSession) -> List[ServerSquad]:
|
||||
|
||||
query = (
|
||||
result = await db.execute(
|
||||
select(ServerSquad)
|
||||
.options(selectinload(ServerSquad.promo_groups))
|
||||
.where(ServerSquad.is_available == True)
|
||||
.order_by(ServerSquad.sort_order, ServerSquad.display_name)
|
||||
)
|
||||
|
||||
if promo_group_id is not None:
|
||||
query = (
|
||||
query.join(
|
||||
server_squad_promo_groups,
|
||||
server_squad_promo_groups.c.server_squad_id == ServerSquad.id,
|
||||
)
|
||||
.where(server_squad_promo_groups.c.promo_group_id == promo_group_id)
|
||||
.distinct()
|
||||
)
|
||||
|
||||
query = query.order_by(ServerSquad.sort_order, ServerSquad.display_name)
|
||||
|
||||
result = await db.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def set_server_squad_promo_groups(
|
||||
db: AsyncSession,
|
||||
server_id: int,
|
||||
promo_group_ids: List[int],
|
||||
) -> Optional[ServerSquad]:
|
||||
|
||||
unique_group_ids = list(dict.fromkeys(promo_group_ids or []))
|
||||
|
||||
if not unique_group_ids:
|
||||
logger.warning("Попытка оставить сервер без промогрупп (id=%s)", server_id)
|
||||
return None
|
||||
|
||||
server = await get_server_squad_by_id(db, server_id)
|
||||
if not server:
|
||||
logger.warning("Сервер %s не найден при обновлении промогрупп", server_id)
|
||||
return None
|
||||
|
||||
groups_result = await db.execute(
|
||||
select(PromoGroup).where(PromoGroup.id.in_(unique_group_ids))
|
||||
)
|
||||
groups = groups_result.scalars().all()
|
||||
|
||||
if len(groups) != len(unique_group_ids):
|
||||
logger.warning(
|
||||
"Не все промогруппы найдены для сервера %s: %s",
|
||||
server_id,
|
||||
unique_group_ids,
|
||||
)
|
||||
return None
|
||||
|
||||
server.promo_groups = groups
|
||||
await db.commit()
|
||||
await db.refresh(server)
|
||||
|
||||
logger.info(
|
||||
"✅ Обновлены промогруппы сервера %s: %s",
|
||||
server.display_name,
|
||||
", ".join(group.name for group in groups),
|
||||
)
|
||||
return server
|
||||
|
||||
|
||||
async def update_server_squad(
|
||||
db: AsyncSession,
|
||||
server_id: int,
|
||||
|
||||
@@ -15,7 +15,6 @@ from sqlalchemy import (
|
||||
BigInteger,
|
||||
UniqueConstraint,
|
||||
Index,
|
||||
Table,
|
||||
)
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.orm import relationship, Mapped, mapped_column
|
||||
@@ -25,22 +24,6 @@ from sqlalchemy.sql import func
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
server_squad_promo_groups = Table(
|
||||
"server_squad_promo_groups",
|
||||
Base.metadata,
|
||||
Column(
|
||||
"server_squad_id",
|
||||
ForeignKey("server_squads.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
Column(
|
||||
"promo_group_id",
|
||||
ForeignKey("promo_groups.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class UserStatus(Enum):
|
||||
ACTIVE = "active"
|
||||
BLOCKED = "blocked"
|
||||
@@ -295,11 +278,6 @@ class PromoGroup(Base):
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
users = relationship("User", back_populates="promo_group")
|
||||
server_squads = relationship(
|
||||
"ServerSquad",
|
||||
secondary="server_squad_promo_groups",
|
||||
back_populates="promo_groups",
|
||||
)
|
||||
|
||||
def _get_period_discounts_map(self) -> Dict[int, int]:
|
||||
raw_discounts = self.period_discounts or {}
|
||||
@@ -854,16 +832,10 @@ class ServerSquad(Base):
|
||||
sort_order = Column(Integer, default=0)
|
||||
|
||||
max_users = Column(Integer, nullable=True)
|
||||
current_users = Column(Integer, default=0)
|
||||
|
||||
current_users = Column(Integer, default=0)
|
||||
|
||||
created_at = Column(DateTime, default=func.now())
|
||||
updated_at = Column(DateTime, default=func.now(), onupdate=func.now())
|
||||
|
||||
promo_groups = relationship(
|
||||
"PromoGroup",
|
||||
secondary="server_squad_promo_groups",
|
||||
back_populates="server_squads",
|
||||
)
|
||||
|
||||
@property
|
||||
def price_rubles(self) -> float:
|
||||
|
||||
@@ -608,96 +608,6 @@ async def create_discount_offers_table():
|
||||
logger.error(f"Ошибка создания таблицы discount_offers: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def create_server_squad_promo_groups_table():
|
||||
table_exists = await check_table_exists('server_squad_promo_groups')
|
||||
if table_exists:
|
||||
logger.info("Таблица server_squad_promo_groups уже существует")
|
||||
return True
|
||||
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
db_type = await get_database_type()
|
||||
|
||||
if db_type == 'sqlite':
|
||||
await conn.execute(text("""
|
||||
CREATE TABLE server_squad_promo_groups (
|
||||
server_squad_id INTEGER NOT NULL,
|
||||
promo_group_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (server_squad_id, promo_group_id),
|
||||
FOREIGN KEY(server_squad_id) REFERENCES server_squads(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY(promo_group_id) REFERENCES promo_groups(id) ON DELETE CASCADE
|
||||
)
|
||||
"""))
|
||||
|
||||
elif db_type == 'postgresql':
|
||||
await conn.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS server_squad_promo_groups (
|
||||
server_squad_id INTEGER NOT NULL REFERENCES server_squads(id) ON DELETE CASCADE,
|
||||
promo_group_id INTEGER NOT NULL REFERENCES promo_groups(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (server_squad_id, promo_group_id)
|
||||
)
|
||||
"""))
|
||||
|
||||
elif db_type == 'mysql':
|
||||
await conn.execute(text("""
|
||||
CREATE TABLE IF NOT EXISTS server_squad_promo_groups (
|
||||
server_squad_id INTEGER NOT NULL,
|
||||
promo_group_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (server_squad_id, promo_group_id),
|
||||
CONSTRAINT fk_sspg_server FOREIGN KEY(server_squad_id) REFERENCES server_squads(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_sspg_group FOREIGN KEY(promo_group_id) REFERENCES promo_groups(id) ON DELETE CASCADE
|
||||
)
|
||||
"""))
|
||||
|
||||
else:
|
||||
raise ValueError(f"Unsupported database type: {db_type}")
|
||||
|
||||
logger.info("✅ Таблица server_squad_promo_groups успешно создана")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка создания таблицы server_squad_promo_groups: {e}")
|
||||
return False
|
||||
|
||||
|
||||
async def ensure_server_squads_have_default_promo_group():
|
||||
try:
|
||||
async with engine.begin() as conn:
|
||||
db_type = await get_database_type()
|
||||
|
||||
default_group_sql = "SELECT id FROM promo_groups WHERE is_default IS TRUE LIMIT 1"
|
||||
if db_type in {'sqlite', 'mysql'}:
|
||||
default_group_sql = "SELECT id FROM promo_groups WHERE is_default = 1 LIMIT 1"
|
||||
|
||||
result = await conn.execute(text(default_group_sql))
|
||||
row = result.fetchone()
|
||||
|
||||
if not row:
|
||||
logger.warning("⚠️ Базовая промогруппа не найдена, пропускаем привязку серверов")
|
||||
return False
|
||||
|
||||
default_group_id = row[0]
|
||||
|
||||
await conn.execute(
|
||||
text("""
|
||||
INSERT INTO server_squad_promo_groups (server_squad_id, promo_group_id)
|
||||
SELECT ss.id, :group_id
|
||||
FROM server_squads ss
|
||||
LEFT JOIN server_squad_promo_groups spg
|
||||
ON spg.server_squad_id = ss.id
|
||||
WHERE spg.server_squad_id IS NULL
|
||||
"""),
|
||||
{"group_id": default_group_id},
|
||||
)
|
||||
|
||||
logger.info("✅ Все серверы без привязки получили базовую промогруппу")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка назначения базовой промогруппы серверам: {e}")
|
||||
return False
|
||||
|
||||
async def create_user_messages_table():
|
||||
table_exists = await check_table_exists('user_messages')
|
||||
if table_exists:
|
||||
@@ -1652,13 +1562,6 @@ async def run_universal_migration():
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с таблицей discount_offers")
|
||||
|
||||
logger.info("=== СОЗДАНИЕ СВЯЗИ SERVER_SQUAD_PROMO_GROUPS ===")
|
||||
promo_link_created = await create_server_squad_promo_groups_table()
|
||||
if promo_link_created:
|
||||
logger.info("✅ Таблица server_squad_promo_groups готова")
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с таблицей server_squad_promo_groups")
|
||||
|
||||
logger.info("=== СОЗДАНИЕ ТАБЛИЦЫ USER_MESSAGES ===")
|
||||
user_messages_created = await create_user_messages_table()
|
||||
if user_messages_created:
|
||||
@@ -1666,13 +1569,6 @@ async def run_universal_migration():
|
||||
else:
|
||||
logger.warning("⚠️ Проблемы с таблицей user_messages")
|
||||
|
||||
logger.info("=== НАЗНАЧЕНИЕ БАЗОВОЙ ПРОМОГРУППЫ СЕРВЕРАМ ===")
|
||||
default_assignment_done = await ensure_server_squads_have_default_promo_group()
|
||||
if default_assignment_done:
|
||||
logger.info("✅ Базовая промогруппа назначена серверам без связей")
|
||||
else:
|
||||
logger.warning("⚠️ Не удалось назначить базовую промогруппу серверам")
|
||||
|
||||
logger.info("=== СОЗДАНИЕ/ОБНОВЛЕНИЕ ТАБЛИЦЫ WELCOME_TEXTS ===")
|
||||
welcome_texts_created = await create_welcome_texts_table()
|
||||
if welcome_texts_created:
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import logging
|
||||
from typing import List, Set
|
||||
|
||||
from aiogram import Dispatcher, types, F
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
@@ -8,61 +6,17 @@ from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from app.states import AdminStates
|
||||
from app.database.models import User
|
||||
from app.database.crud.server_squad import (
|
||||
get_all_server_squads,
|
||||
get_server_squad_by_id,
|
||||
update_server_squad,
|
||||
delete_server_squad,
|
||||
sync_with_remnawave,
|
||||
get_server_statistics,
|
||||
create_server_squad,
|
||||
get_available_server_squads,
|
||||
set_server_squad_promo_groups,
|
||||
get_all_server_squads, get_server_squad_by_id, update_server_squad,
|
||||
delete_server_squad, sync_with_remnawave, get_server_statistics,
|
||||
create_server_squad, get_available_server_squads
|
||||
)
|
||||
from app.database.crud.promo_group import get_all_promo_groups
|
||||
from app.services.remnawave_service import RemnaWaveService
|
||||
from app.utils.decorators import admin_required, error_handler
|
||||
from app.utils.cache import invalidate_available_countries_cache
|
||||
from app.utils.cache import cache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _format_promo_group_list(promo_groups: List, selected_ids: Set[int]) -> str:
|
||||
names = [group.name for group in promo_groups if group.id in selected_ids]
|
||||
return ", ".join(names) if names else "Не выбраны"
|
||||
|
||||
|
||||
def _build_server_promo_groups_keyboard(
|
||||
server_id: int,
|
||||
promo_groups: List,
|
||||
selected_ids: Set[int],
|
||||
):
|
||||
rows = []
|
||||
|
||||
for group in promo_groups:
|
||||
emoji = "✅" if group.id in selected_ids else "⚪"
|
||||
rows.append([
|
||||
types.InlineKeyboardButton(
|
||||
text=f"{emoji} {group.name}",
|
||||
callback_data=f"admin_server_group_toggle_{server_id}_{group.id}",
|
||||
)
|
||||
])
|
||||
|
||||
rows.append([
|
||||
types.InlineKeyboardButton(
|
||||
text="💾 Сохранить",
|
||||
callback_data=f"admin_server_group_save_{server_id}",
|
||||
)
|
||||
])
|
||||
rows.append([
|
||||
types.InlineKeyboardButton(
|
||||
text="⬅️ Назад",
|
||||
callback_data=f"admin_server_edit_{server_id}",
|
||||
)
|
||||
])
|
||||
|
||||
return types.InlineKeyboardMarkup(inline_keyboard=rows)
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def show_servers_menu(
|
||||
@@ -212,7 +166,7 @@ async def sync_servers_with_remnawave(
|
||||
|
||||
created, updated, disabled = await sync_with_remnawave(db, squads)
|
||||
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
text = f"""
|
||||
✅ <b>Синхронизация завершена</b>
|
||||
@@ -269,7 +223,6 @@ async def show_server_edit_menu(
|
||||
|
||||
status_emoji = "✅ Доступен" if server.is_available else "❌ Недоступен"
|
||||
price_text = f"{int(server.price_rubles)} ₽" if server.price_kopeks > 0 else "Бесплатно"
|
||||
promo_group_names = ", ".join(group.name for group in server.promo_groups) if server.promo_groups else "Не назначены"
|
||||
|
||||
text = f"""
|
||||
🌐 <b>Редактирование сервера</b>
|
||||
@@ -286,14 +239,13 @@ async def show_server_edit_menu(
|
||||
• Код страны: {server.country_code or 'Не указан'}
|
||||
• Лимит пользователей: {server.max_users or 'Без лимита'}
|
||||
• Текущих пользователей: {server.current_users}
|
||||
• Промогруппы: {promo_group_names}
|
||||
|
||||
<b>Описание:</b>
|
||||
{server.description or 'Не указано'}
|
||||
|
||||
Выберите что изменить:
|
||||
"""
|
||||
|
||||
|
||||
keyboard = [
|
||||
[
|
||||
types.InlineKeyboardButton(text="✏️ Название", callback_data=f"admin_server_edit_name_{server.id}"),
|
||||
@@ -306,9 +258,6 @@ async def show_server_edit_menu(
|
||||
[
|
||||
types.InlineKeyboardButton(text="📝 Описание", callback_data=f"admin_server_edit_desc_{server.id}")
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(text="🎯 Промогруппы", callback_data=f"admin_server_edit_groups_{server.id}")
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="❌ Отключить" if server.is_available else "✅ Включить",
|
||||
@@ -347,7 +296,7 @@ async def toggle_server_availability(
|
||||
new_status = not server.is_available
|
||||
await update_server_squad(db, server_id, is_available=new_status)
|
||||
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
status_text = "включен" if new_status else "отключен"
|
||||
await callback.answer(f"✅ Сервер {status_text}!")
|
||||
@@ -372,7 +321,6 @@ async def toggle_server_availability(
|
||||
• Код страны: {server.country_code or 'Не указан'}
|
||||
• Лимит пользователей: {server.max_users or 'Без лимита'}
|
||||
• Текущих пользователей: {server.current_users}
|
||||
• Промогруппы: {promo_group_names}
|
||||
|
||||
<b>Описание:</b>
|
||||
{server.description or 'Не указано'}
|
||||
@@ -392,9 +340,6 @@ async def toggle_server_availability(
|
||||
[
|
||||
types.InlineKeyboardButton(text="📝 Описание", callback_data=f"admin_server_edit_desc_{server.id}")
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(text="🎯 Промогруппы", callback_data=f"admin_server_edit_groups_{server.id}")
|
||||
],
|
||||
[
|
||||
types.InlineKeyboardButton(
|
||||
text="❌ Отключить" if server.is_available else "✅ Включить",
|
||||
@@ -477,7 +422,7 @@ async def process_server_price_edit(
|
||||
if server:
|
||||
await state.clear()
|
||||
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
price_text = f"{int(price_rubles)} ₽" if price_kopeks > 0 else "Бесплатно"
|
||||
await message.answer(
|
||||
@@ -552,7 +497,7 @@ async def process_server_name_edit(
|
||||
if server:
|
||||
await state.clear()
|
||||
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
await message.answer(
|
||||
f"✅ Название сервера изменено на: <b>{new_name}</b>",
|
||||
@@ -625,7 +570,7 @@ async def delete_server_execute(
|
||||
success = await delete_server_squad(db, server_id)
|
||||
|
||||
if success:
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"✅ Сервер <b>{server.display_name}</b> успешно удален!",
|
||||
@@ -756,7 +701,7 @@ async def process_server_country_edit(
|
||||
if server:
|
||||
await state.clear()
|
||||
|
||||
await invalidate_available_countries_cache()
|
||||
await cache.delete("available_countries")
|
||||
|
||||
country_text = new_country or "Удален"
|
||||
await message.answer(
|
||||
@@ -917,137 +862,6 @@ async def process_server_description_edit(
|
||||
else:
|
||||
await message.answer("❌ Ошибка при обновлении сервера")
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def start_server_edit_promo_groups(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
):
|
||||
|
||||
server_id = int(callback.data.split('_')[-1])
|
||||
server = await get_server_squad_by_id(db, server_id)
|
||||
|
||||
if not server:
|
||||
await callback.answer("❌ Сервер не найден!", show_alert=True)
|
||||
return
|
||||
|
||||
promo_groups = await get_all_promo_groups(db)
|
||||
if not promo_groups:
|
||||
await callback.answer("⚠️ Нет доступных промогрупп", show_alert=True)
|
||||
return
|
||||
|
||||
selected_ids: Set[int] = {group.id for group in server.promo_groups}
|
||||
|
||||
await state.set_data({
|
||||
'server_id': server_id,
|
||||
'server_name': server.display_name,
|
||||
'selected_promo_groups': list(selected_ids),
|
||||
})
|
||||
await state.set_state(AdminStates.editing_server_promo_groups)
|
||||
|
||||
selected_text = _format_promo_group_list(promo_groups, selected_ids)
|
||||
text = (
|
||||
f"🎯 <b>Промогруппы сервера</b>\n\n"
|
||||
f"Сервер: <b>{server.display_name}</b>\n"
|
||||
f"Текущие промогруппы: <b>{selected_text}</b>\n\n"
|
||||
"Выберите промогруппы, которым будет доступен сервер."
|
||||
)
|
||||
|
||||
await callback.message.edit_text(
|
||||
text,
|
||||
reply_markup=_build_server_promo_groups_keyboard(server_id, promo_groups, selected_ids),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def toggle_server_promo_group(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
):
|
||||
|
||||
parts = callback.data.split('_')
|
||||
server_id = int(parts[-2])
|
||||
group_id = int(parts[-1])
|
||||
|
||||
data = await state.get_data()
|
||||
selected_ids: Set[int] = set(data.get('selected_promo_groups', []))
|
||||
|
||||
if group_id in selected_ids:
|
||||
if len(selected_ids) == 1:
|
||||
await callback.answer("⚠️ Должна быть выбрана хотя бы одна промогруппа", show_alert=True)
|
||||
return
|
||||
selected_ids.remove(group_id)
|
||||
else:
|
||||
selected_ids.add(group_id)
|
||||
|
||||
data['selected_promo_groups'] = list(selected_ids)
|
||||
await state.set_data(data)
|
||||
|
||||
promo_groups = await get_all_promo_groups(db)
|
||||
selected_text = _format_promo_group_list(promo_groups, selected_ids)
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"🎯 <b>Промогруппы сервера</b>\n\n"
|
||||
f"Сервер: <b>{data.get('server_name', 'Неизвестно')}</b>\n"
|
||||
f"Текущие промогруппы: <b>{selected_text}</b>\n\n"
|
||||
"Выберите промогруппы, которым будет доступен сервер.",
|
||||
reply_markup=_build_server_promo_groups_keyboard(server_id, promo_groups, selected_ids),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
|
||||
await callback.answer()
|
||||
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def save_server_promo_groups(
|
||||
callback: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
db_user: User,
|
||||
db: AsyncSession,
|
||||
):
|
||||
|
||||
data = await state.get_data()
|
||||
server_id_value = data.get('server_id')
|
||||
if server_id_value is None:
|
||||
await callback.answer("❌ Не удалось определить сервер", show_alert=True)
|
||||
return
|
||||
|
||||
server_id = int(server_id_value)
|
||||
selected_ids: List[int] = data.get('selected_promo_groups', [])
|
||||
|
||||
if not selected_ids:
|
||||
await callback.answer("⚠️ Выберите хотя бы одну промогруппу", show_alert=True)
|
||||
return
|
||||
|
||||
server = await set_server_squad_promo_groups(db, server_id, selected_ids)
|
||||
|
||||
if not server:
|
||||
await callback.answer("❌ Не удалось сохранить промогруппы", show_alert=True)
|
||||
return
|
||||
|
||||
await state.clear()
|
||||
await invalidate_available_countries_cache()
|
||||
|
||||
promo_group_names = ", ".join(group.name for group in server.promo_groups) if server.promo_groups else "Не назначены"
|
||||
|
||||
await callback.message.edit_text(
|
||||
f"✅ Промогруппы сервера обновлены:\n<b>{promo_group_names}</b>",
|
||||
reply_markup=types.InlineKeyboardMarkup(inline_keyboard=[
|
||||
[types.InlineKeyboardButton(text="🔙 К серверу", callback_data=f"admin_server_edit_{server_id}")]
|
||||
]),
|
||||
parse_mode="HTML",
|
||||
)
|
||||
await callback.answer()
|
||||
|
||||
@admin_required
|
||||
@error_handler
|
||||
async def sync_server_user_counts_handler(
|
||||
@@ -1126,16 +940,13 @@ def register_handlers(dp: Dispatcher):
|
||||
dp.callback_query.register(start_server_edit_name, F.data.startswith("admin_server_edit_name_"))
|
||||
dp.callback_query.register(start_server_edit_price, F.data.startswith("admin_server_edit_price_"))
|
||||
dp.callback_query.register(start_server_edit_country, F.data.startswith("admin_server_edit_country_"))
|
||||
dp.callback_query.register(start_server_edit_limit, F.data.startswith("admin_server_edit_limit_"))
|
||||
dp.callback_query.register(start_server_edit_description, F.data.startswith("admin_server_edit_desc_"))
|
||||
dp.callback_query.register(start_server_edit_promo_groups, F.data.startswith("admin_server_edit_groups_"))
|
||||
dp.callback_query.register(toggle_server_promo_group, F.data.startswith("admin_server_group_toggle_"))
|
||||
dp.callback_query.register(save_server_promo_groups, F.data.startswith("admin_server_group_save_"))
|
||||
|
||||
dp.callback_query.register(start_server_edit_limit, F.data.startswith("admin_server_edit_limit_"))
|
||||
dp.callback_query.register(start_server_edit_description, F.data.startswith("admin_server_edit_desc_"))
|
||||
|
||||
dp.message.register(process_server_name_edit, AdminStates.editing_server_name)
|
||||
dp.message.register(process_server_price_edit, AdminStates.editing_server_price)
|
||||
dp.message.register(process_server_country_edit, AdminStates.editing_server_country)
|
||||
dp.message.register(process_server_limit_edit, AdminStates.editing_server_limit)
|
||||
dp.message.register(process_server_country_edit, AdminStates.editing_server_country)
|
||||
dp.message.register(process_server_limit_edit, AdminStates.editing_server_limit)
|
||||
dp.message.register(process_server_description_edit, AdminStates.editing_server_description)
|
||||
|
||||
dp.callback_query.register(delete_server_confirm, F.data.startswith("admin_server_delete_") & ~F.data.contains("confirm"))
|
||||
|
||||
@@ -99,7 +99,7 @@ async def _prepare_subscription_summary(
|
||||
)
|
||||
|
||||
summary_data = dict(data)
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
|
||||
months_in_period = calculate_months_from_days(summary_data['period_days'])
|
||||
period_display = format_period_description(summary_data['period_days'], db_user.language)
|
||||
@@ -1003,7 +1003,7 @@ async def return_to_saved_cart(
|
||||
|
||||
from app.utils.pricing_utils import calculate_months_from_days, format_period_description
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
selected_countries_names = []
|
||||
|
||||
months_in_period = calculate_months_from_days(data['period_days'])
|
||||
@@ -1043,7 +1043,7 @@ async def handle_add_countries(
|
||||
db: AsyncSession,
|
||||
state: FSMContext
|
||||
):
|
||||
if not await _should_show_countries_management(db_user.promo_group_id):
|
||||
if not await _should_show_countries_management():
|
||||
await callback.answer("ℹ️ Управление серверами недоступно - доступен только один сервер", show_alert=True)
|
||||
return
|
||||
|
||||
@@ -1054,7 +1054,7 @@ async def handle_add_countries(
|
||||
await callback.answer("⚠ Эта функция доступна только для платных подписок", show_alert=True)
|
||||
return
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
current_countries = subscription.connected_squads
|
||||
|
||||
current_countries_names = []
|
||||
@@ -1138,26 +1138,21 @@ async def handle_manage_country(
|
||||
return
|
||||
|
||||
data = await state.get_data()
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
available_ids = {country['uuid'] for country in countries}
|
||||
|
||||
if country_uuid not in available_ids:
|
||||
await callback.answer("❌ Эта страна недоступна для вашей промогруппы", show_alert=True)
|
||||
return
|
||||
|
||||
current_selected = data.get('countries', subscription.connected_squads.copy())
|
||||
|
||||
|
||||
if country_uuid in current_selected:
|
||||
current_selected.remove(country_uuid)
|
||||
action = "removed"
|
||||
else:
|
||||
current_selected.append(country_uuid)
|
||||
action = "added"
|
||||
|
||||
|
||||
logger.info(f"🔍 Страна {country_uuid} {action}")
|
||||
|
||||
|
||||
await state.update_data(countries=current_selected)
|
||||
|
||||
countries = await _get_available_countries()
|
||||
|
||||
try:
|
||||
await callback.message.edit_reply_markup(
|
||||
reply_markup=get_manage_countries_keyboard(
|
||||
@@ -1208,7 +1203,7 @@ async def apply_countries_changes(
|
||||
|
||||
logger.info(f"🔧 Добавлено: {added}, Удалено: {removed}")
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
|
||||
months_to_pay = get_remaining_months(subscription.end_date)
|
||||
|
||||
@@ -2532,15 +2527,15 @@ async def select_period(
|
||||
)
|
||||
await state.set_state(SubscriptionStates.selecting_traffic)
|
||||
else:
|
||||
if await _should_show_countries_management(db_user.promo_group_id):
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
if await _should_show_countries_management():
|
||||
countries = await _get_available_countries()
|
||||
await callback.message.edit_text(
|
||||
texts.SELECT_COUNTRIES,
|
||||
reply_markup=get_countries_keyboard(countries, [], db_user.language)
|
||||
)
|
||||
await state.set_state(SubscriptionStates.selecting_countries)
|
||||
else:
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
available_countries = [c for c in countries if c.get('is_available', True)]
|
||||
data['countries'] = [available_countries[0]['uuid']] if available_countries else []
|
||||
await state.set_data(data)
|
||||
@@ -2608,7 +2603,7 @@ async def get_traffic_packages_info() -> str:
|
||||
async def get_subscription_info_text(subscription, texts, db_user, db: AsyncSession):
|
||||
|
||||
devices_used = await get_current_devices_count(db_user)
|
||||
countries_info = await _get_countries_info(subscription.connected_squads, db_user.promo_group_id)
|
||||
countries_info = await _get_countries_info(subscription.connected_squads)
|
||||
countries_text = ", ".join([c['name'] for c in countries_info]) if countries_info else "Нет"
|
||||
|
||||
subscription_url = getattr(subscription, 'subscription_url', None) or "Генерируется..."
|
||||
@@ -2688,15 +2683,15 @@ async def select_traffic(
|
||||
|
||||
await state.set_data(data)
|
||||
|
||||
if await _should_show_countries_management(db_user.promo_group_id):
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
if await _should_show_countries_management():
|
||||
countries = await _get_available_countries()
|
||||
await callback.message.edit_text(
|
||||
texts.SELECT_COUNTRIES,
|
||||
reply_markup=get_countries_keyboard(countries, [], db_user.language)
|
||||
)
|
||||
await state.set_state(SubscriptionStates.selecting_countries)
|
||||
else:
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
available_countries = [c for c in countries if c.get('is_available', True)]
|
||||
data['countries'] = [available_countries[0]['uuid']] if available_countries else []
|
||||
await state.set_data(data)
|
||||
@@ -2722,19 +2717,13 @@ async def select_country(
|
||||
data = await state.get_data()
|
||||
|
||||
selected_countries = data.get('countries', [])
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
available_ids = {country['uuid'] for country in countries}
|
||||
|
||||
if country_uuid not in available_ids:
|
||||
await callback.answer("❌ Эта страна недоступна для вашей промогруппы", show_alert=True)
|
||||
return
|
||||
|
||||
if country_uuid in selected_countries:
|
||||
selected_countries.remove(country_uuid)
|
||||
else:
|
||||
selected_countries.append(country_uuid)
|
||||
|
||||
countries = await _get_available_countries()
|
||||
|
||||
period_base_price = PERIOD_PRICES[data['period_days']]
|
||||
from app.utils.pricing_utils import apply_percentage_discount
|
||||
|
||||
@@ -2808,7 +2797,7 @@ async def select_devices(
|
||||
settings.get_traffic_price(data['traffic_gb'])
|
||||
)
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
countries_price = sum(
|
||||
c['price_kopeks'] for c in countries
|
||||
if c['uuid'] in data['countries']
|
||||
@@ -2877,7 +2866,7 @@ async def confirm_purchase(
|
||||
else None
|
||||
)
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
|
||||
months_in_period = data.get(
|
||||
'months_in_period', calculate_months_from_days(data['period_days'])
|
||||
@@ -3534,7 +3523,7 @@ async def handle_subscription_settings(
|
||||
Выберите что хотите изменить:
|
||||
"""
|
||||
|
||||
show_countries = await _should_show_countries_management(db_user.promo_group_id)
|
||||
show_countries = await _should_show_countries_management()
|
||||
|
||||
await callback.message.edit_text(
|
||||
settings_text,
|
||||
@@ -3647,8 +3636,8 @@ async def handle_subscription_config_back(
|
||||
await state.set_state(SubscriptionStates.selecting_period)
|
||||
|
||||
elif current_state == SubscriptionStates.selecting_devices.state:
|
||||
if await _should_show_countries_management(db_user.promo_group_id):
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
if await _should_show_countries_management():
|
||||
countries = await _get_available_countries()
|
||||
data = await state.get_data()
|
||||
selected_countries = data.get('countries', [])
|
||||
|
||||
@@ -3694,24 +3683,19 @@ async def handle_subscription_cancel(
|
||||
|
||||
await callback.answer("❌ Покупка отменена")
|
||||
|
||||
async def _get_available_countries(promo_group_id: Optional[int] = None):
|
||||
from app.utils.cache import cache, cache_key
|
||||
async def _get_available_countries():
|
||||
from app.utils.cache import cache
|
||||
from app.database.database import AsyncSessionLocal
|
||||
from app.database.crud.server_squad import get_available_server_squads
|
||||
|
||||
cache_key_name = cache_key("available_countries", promo_group_id or "all")
|
||||
|
||||
cached_countries = await cache.get(cache_key_name)
|
||||
|
||||
cached_countries = await cache.get("available_countries")
|
||||
if cached_countries:
|
||||
return cached_countries
|
||||
|
||||
|
||||
try:
|
||||
async with AsyncSessionLocal() as db:
|
||||
available_servers = await get_available_server_squads(
|
||||
db,
|
||||
promo_group_id=promo_group_id,
|
||||
)
|
||||
|
||||
available_servers = await get_available_server_squads(db)
|
||||
|
||||
countries = []
|
||||
for server in available_servers:
|
||||
countries.append({
|
||||
@@ -3750,20 +3734,20 @@ async def _get_available_countries(promo_group_id: Optional[int] = None):
|
||||
"is_available": True
|
||||
})
|
||||
|
||||
await cache.set(cache_key_name, countries, 300)
|
||||
await cache.set("available_countries", countries, 300)
|
||||
return countries
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка получения списка стран: {e}")
|
||||
fallback_countries = [
|
||||
{"uuid": "default-free", "name": "🆓 Бесплатный сервер", "price_kopeks": 0, "is_available": True},
|
||||
]
|
||||
|
||||
await cache.set(cache_key_name, fallback_countries, 60)
|
||||
|
||||
await cache.set("available_countries", fallback_countries, 60)
|
||||
return fallback_countries
|
||||
|
||||
async def _get_countries_info(squad_uuids, promo_group_id: Optional[int] = None):
|
||||
countries = await _get_available_countries(promo_group_id)
|
||||
async def _get_countries_info(squad_uuids):
|
||||
countries = await _get_available_countries()
|
||||
return [c for c in countries if c['uuid'] in squad_uuids]
|
||||
|
||||
async def handle_reset_devices(
|
||||
@@ -3792,13 +3776,8 @@ async def handle_add_country_to_subscription(
|
||||
logger.info(f"🔍 Данные состояния: {data}")
|
||||
|
||||
selected_countries = data.get('countries', [])
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
available_ids = {country['uuid'] for country in countries}
|
||||
|
||||
if country_uuid not in available_ids:
|
||||
await callback.answer("❌ Эта страна недоступна для вашей промогруппы", show_alert=True)
|
||||
return
|
||||
|
||||
countries = await _get_available_countries()
|
||||
|
||||
if country_uuid in selected_countries:
|
||||
selected_countries.remove(country_uuid)
|
||||
logger.info(f"🔍 Удалена страна: {country_uuid}")
|
||||
@@ -3829,9 +3808,9 @@ async def handle_add_country_to_subscription(
|
||||
|
||||
await callback.answer()
|
||||
|
||||
async def _should_show_countries_management(promo_group_id: Optional[int] = None) -> bool:
|
||||
async def _should_show_countries_management() -> bool:
|
||||
try:
|
||||
countries = await _get_available_countries(promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
available_countries = [c for c in countries if c.get('is_available', True)]
|
||||
return len(available_countries) > 1
|
||||
except Exception as e:
|
||||
@@ -3860,7 +3839,7 @@ async def confirm_add_countries_to_subscription(
|
||||
await callback.answer("⚠️ Изменения не обнаружены", show_alert=True)
|
||||
return
|
||||
|
||||
countries = await _get_available_countries(db_user.promo_group_id)
|
||||
countries = await _get_available_countries()
|
||||
total_price = 0
|
||||
new_countries_names = []
|
||||
removed_countries_names = []
|
||||
|
||||
@@ -95,7 +95,6 @@ class AdminStates(StatesGroup):
|
||||
editing_server_country = State()
|
||||
editing_server_limit = State()
|
||||
editing_server_description = State()
|
||||
editing_server_promo_groups = State()
|
||||
|
||||
creating_server_uuid = State()
|
||||
creating_server_name = State()
|
||||
|
||||
@@ -179,12 +179,6 @@ async def cached_function(key: str, expire: int = 300):
|
||||
return decorator
|
||||
|
||||
|
||||
async def invalidate_available_countries_cache() -> None:
|
||||
keys = await cache.get_keys("available_countries*")
|
||||
for key in keys:
|
||||
await cache.delete(key)
|
||||
|
||||
|
||||
class UserCache:
|
||||
|
||||
@staticmethod
|
||||
|
||||
Reference in New Issue
Block a user