init3
This commit is contained in:
parent
d3dc448552
commit
5986f420b1
10 changed files with 196 additions and 44 deletions
|
|
@ -3,7 +3,12 @@ from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
|
|
||||||
from admin.auth import require_admin
|
from admin.auth import require_admin
|
||||||
from admin.templates_env import templates
|
from admin.templates_env import templates
|
||||||
from database.queries.bot_users import get_all_bot_users, unblock_bot_user
|
from database.queries.bot_users import (
|
||||||
|
delete_bot_user,
|
||||||
|
get_all_bot_users,
|
||||||
|
reset_bot_user,
|
||||||
|
unblock_bot_user,
|
||||||
|
)
|
||||||
from database.queries.organizations import get_all_organizations
|
from database.queries.organizations import get_all_organizations
|
||||||
|
|
||||||
router = APIRouter(prefix="/bot-users", dependencies=[Depends(require_admin)])
|
router = APIRouter(prefix="/bot-users", dependencies=[Depends(require_admin)])
|
||||||
|
|
@ -39,3 +44,17 @@ async def list_bot_users(
|
||||||
async def unblock_user(user_id: int, org_id: int = Form(...)):
|
async def unblock_user(user_id: int, org_id: int = Form(...)):
|
||||||
await unblock_bot_user(user_id=user_id, org_id=org_id)
|
await unblock_bot_user(user_id=user_id, org_id=org_id)
|
||||||
return RedirectResponse("/admin/bot-users", status_code=303)
|
return RedirectResponse("/admin/bot-users", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{user_id}/reset")
|
||||||
|
async def reset_user(user_id: int):
|
||||||
|
"""Сбрасывает авторизацию — пользователь должен заново авторизоваться через бот."""
|
||||||
|
await reset_bot_user(user_id=user_id)
|
||||||
|
return RedirectResponse("/admin/bot-users", status_code=303)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{user_id}/delete")
|
||||||
|
async def delete_user(user_id: int):
|
||||||
|
"""Удаляет пользователя бота полностью."""
|
||||||
|
await delete_bot_user(user_id=user_id)
|
||||||
|
return RedirectResponse("/admin/bot-users", status_code=303)
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>#</th><th>MAX ID</th><th>Имя</th><th>Телефон</th>
|
<th>#</th><th>MAX ID</th><th>Имя</th><th>Телефон</th>
|
||||||
<th>Организация</th><th>Статус</th><th>Дата</th><th>Разблокировать</th>
|
<th>Организация</th><th>Статус</th><th>Дата</th><th>Действия</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -48,21 +48,43 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ u.created_at[:16] }}</td>
|
<td>{{ u.created_at[:16] }}</td>
|
||||||
<td>
|
<td style="white-space:nowrap">
|
||||||
{% if u.is_blocked or not u.is_authorized %}
|
<div style="display:flex;flex-direction:column;gap:4px">
|
||||||
|
|
||||||
|
{# Разблокировать — только для заблокированных (admin задаёт org вручную) #}
|
||||||
|
{% if u.is_blocked %}
|
||||||
<form action="/admin/bot-users/{{ u.id }}/unblock" method="post"
|
<form action="/admin/bot-users/{{ u.id }}/unblock" method="post"
|
||||||
style="display:flex;gap:6px;align-items:center">
|
style="display:flex;gap:4px;align-items:center">
|
||||||
<select name="org_id" required style="width:auto">
|
<select name="org_id" required style="width:auto">
|
||||||
<option value="">— орг —</option>
|
<option value="">— орг —</option>
|
||||||
{% for org in orgs %}
|
{% for org in orgs %}
|
||||||
<option value="{{ org.id }}" {% if u.org_id == org.id %}selected{% endif %}>{{ org.name }}</option>
|
<option value="{{ org.id }}" {% if u.org_id == org.id %}selected{% endif %}>{{ org.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn btn-primary" type="submit">Разблокировать</button>
|
<button class="btn btn-primary" type="submit" title="Разблокировать и авторизовать">Разблокировать</button>
|
||||||
</form>
|
</form>
|
||||||
{% else %}
|
|
||||||
—
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{# Сбросить авторизацию — для авторизованных и заблокированных (повторная auth через бот) #}
|
||||||
|
{% if u.is_authorized or u.is_blocked %}
|
||||||
|
<form action="/admin/bot-users/{{ u.id }}/reset" method="post">
|
||||||
|
<button class="btn btn-secondary" type="submit"
|
||||||
|
onclick="return confirm('Сбросить авторизацию? Пользователь должен будет заново поделиться контактом.')"
|
||||||
|
title="Сбросить — пользователь заново пройдёт авторизацию через бот">
|
||||||
|
Сбросить auth
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Удалить полностью #}
|
||||||
|
<form action="/admin/bot-users/{{ u.id }}/delete" method="post">
|
||||||
|
<button class="btn" style="background:#dc3545;color:#fff" type="submit"
|
||||||
|
onclick="return confirm('Удалить пользователя полностью? Это действие необратимо.')">
|
||||||
|
Удалить
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,10 @@ from maxapi.types.updates.message_callback import MessageCallback
|
||||||
from maxapi.types.updates.message_created import MessageCreated
|
from maxapi.types.updates.message_created import MessageCreated
|
||||||
|
|
||||||
from bot.keyboards import done_ticket_kb, main_menu_kb
|
from bot.keyboards import done_ticket_kb, main_menu_kb
|
||||||
|
from bot.logging_setup import get_logger
|
||||||
from bot.states import TicketStates
|
from bot.states import TicketStates
|
||||||
|
|
||||||
|
logger = get_logger("bot.handlers.create_ticket")
|
||||||
from database.queries.ticket_attachments import add_attachment
|
from database.queries.ticket_attachments import add_attachment
|
||||||
from database.queries.tickets import create_ticket
|
from database.queries.tickets import create_ticket
|
||||||
import external.client as ext
|
import external.client as ext
|
||||||
|
|
@ -56,10 +59,11 @@ def _extract_attachments(event: MessageCreated) -> list[dict]:
|
||||||
|
|
||||||
@router.message_callback(F.callback.payload == "create_ticket")
|
@router.message_callback(F.callback.payload == "create_ticket")
|
||||||
async def cb_create_ticket(
|
async def cb_create_ticket(
|
||||||
event: MessageCallback, context: BaseContext
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
) -> None:
|
) -> None:
|
||||||
await context.set_state(TicketStates.waiting_description)
|
await context.set_state(TicketStates.waiting_description)
|
||||||
await context.set_data({"pending_attachments": []})
|
await context.set_data({"pending_attachments": []})
|
||||||
|
logger.info("📝 create_ticket started | user_id=%s", bot_user["max_user_id"])
|
||||||
await event.answer(notification="")
|
await event.answer(notification="")
|
||||||
|
|
||||||
chat_id = event.message.recipient.chat_id if event.message else None
|
chat_id = event.message.recipient.chat_id if event.message else None
|
||||||
|
|
@ -153,6 +157,11 @@ async def cb_done_ticket(
|
||||||
created_by=bot_user["id"],
|
created_by=bot_user["id"],
|
||||||
description=description,
|
description=description,
|
||||||
)
|
)
|
||||||
|
logger.info(
|
||||||
|
"✅ ticket created | ticket_id=%s user_id=%s org_id=%s attachments=%s",
|
||||||
|
ticket_id, bot_user["max_user_id"], bot_user["org_id"],
|
||||||
|
len(data.get("pending_attachments", [])),
|
||||||
|
)
|
||||||
|
|
||||||
for att in data.get("pending_attachments", []):
|
for att in data.get("pending_attachments", []):
|
||||||
await add_attachment(
|
await add_attachment(
|
||||||
|
|
@ -174,7 +183,8 @@ async def cb_done_ticket(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fire-and-forget: отправка во внешнюю систему
|
# Fire-and-forget: отправка во внешнюю систему
|
||||||
|
async def _sync_external():
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
asyncio.create_task(
|
await loop.run_in_executor(None, ext.sync_ticket, ticket_id)
|
||||||
loop.run_in_executor(None, ext.sync_ticket, ticket_id)
|
|
||||||
)
|
asyncio.create_task(_sync_external())
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@ from maxapi.context import BaseContext
|
||||||
from maxapi.filters.command import Command
|
from maxapi.filters.command import Command
|
||||||
|
|
||||||
from bot.keyboards import contact_request_kb, main_menu_kb
|
from bot.keyboards import contact_request_kb, main_menu_kb
|
||||||
|
from bot.logging_setup import get_logger
|
||||||
from bot.states import AuthStates
|
from bot.states import AuthStates
|
||||||
|
|
||||||
|
logger = get_logger("bot.handlers.start")
|
||||||
from database.queries.bot_users import (
|
from database.queries.bot_users import (
|
||||||
authorize_bot_user,
|
authorize_bot_user,
|
||||||
block_bot_user,
|
block_bot_user,
|
||||||
|
|
@ -49,6 +52,7 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
if bot_user["is_blocked"]:
|
if bot_user["is_blocked"]:
|
||||||
|
logger.info("🚫 bot_started blocked | user_id=%s", user.user_id)
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
text=(
|
text=(
|
||||||
|
|
@ -59,6 +63,7 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if bot_user["is_authorized"]:
|
if bot_user["is_authorized"]:
|
||||||
|
logger.info("✅ bot_started returning user | user_id=%s", user.user_id)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
|
|
@ -68,6 +73,7 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Новый пользователь — запрашиваем контакт
|
# Новый пользователь — запрашиваем контакт
|
||||||
|
logger.info("👋 bot_started new user | user_id=%s", user.user_id)
|
||||||
await context.set_state(AuthStates.waiting_contact)
|
await context.set_state(AuthStates.waiting_contact)
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
|
|
@ -174,6 +180,7 @@ async def on_contact_shared(
|
||||||
# Ищем в белом списке
|
# Ищем в белом списке
|
||||||
org_user = await find_by_phone(phone)
|
org_user = await find_by_phone(phone)
|
||||||
if org_user is None:
|
if org_user is None:
|
||||||
|
logger.warning("🚫 phone not found, blocking | user_id=%s phone=%s", user_id, phone)
|
||||||
await block_bot_user(max_user_id=user_id, phone=phone)
|
await block_bot_user(max_user_id=user_id, phone=phone)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.bot.send_message(
|
await event.bot.send_message(
|
||||||
|
|
@ -186,6 +193,7 @@ async def on_contact_shared(
|
||||||
return
|
return
|
||||||
|
|
||||||
# Авторизуем
|
# Авторизуем
|
||||||
|
logger.info("✅ authorized | user_id=%s phone=%s org_id=%s", user_id, phone, org_user["org_id"])
|
||||||
await authorize_bot_user(
|
await authorize_bot_user(
|
||||||
max_user_id=user_id,
|
max_user_id=user_id,
|
||||||
phone=phone,
|
phone=phone,
|
||||||
|
|
|
||||||
27
bot/logging_setup.py
Normal file
27
bot/logging_setup.py
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""
|
||||||
|
Настройка логирования для бота.
|
||||||
|
|
||||||
|
Формат: [время] УРОВЕНЬ [модуль] сообщение
|
||||||
|
Логи пишутся в stdout (Docker подхватывает их через docker logs).
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(level: str = "INFO") -> None:
|
||||||
|
fmt = "%(asctime)s %(levelname)-8s [%(name)s] %(message)s"
|
||||||
|
datefmt = "%Y-%m-%d %H:%M:%S"
|
||||||
|
logging.basicConfig(
|
||||||
|
level=getattr(logging, level.upper(), logging.INFO),
|
||||||
|
format=fmt,
|
||||||
|
datefmt=datefmt,
|
||||||
|
stream=sys.stdout,
|
||||||
|
force=True,
|
||||||
|
)
|
||||||
|
# Приглушаем шумные библиотеки
|
||||||
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
||||||
|
logging.getLogger("aiosqlite").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
|
def get_logger(name: str) -> logging.Logger:
|
||||||
|
return logging.getLogger(name)
|
||||||
|
|
@ -3,76 +3,117 @@ from typing import Any
|
||||||
|
|
||||||
from maxapi.filters.middleware import BaseMiddleware
|
from maxapi.filters.middleware import BaseMiddleware
|
||||||
from maxapi.types.updates.bot_started import BotStarted
|
from maxapi.types.updates.bot_started import BotStarted
|
||||||
|
from maxapi.types.updates.message_callback import MessageCallback
|
||||||
from maxapi.types.updates.message_created import MessageCreated
|
from maxapi.types.updates.message_created import MessageCreated
|
||||||
|
|
||||||
|
from bot.logging_setup import get_logger
|
||||||
from database.queries.bot_users import get_bot_user_by_max_id
|
from database.queries.bot_users import get_bot_user_by_max_id
|
||||||
from database.queries.message_log import log_message
|
from database.queries.message_log import log_message
|
||||||
|
|
||||||
|
logger = get_logger("bot.middleware")
|
||||||
|
|
||||||
|
|
||||||
|
def _user_label(bot_user: dict | None, user_id: int | None) -> str:
|
||||||
|
if bot_user:
|
||||||
|
name = bot_user.get("first_name") or bot_user.get("username") or ""
|
||||||
|
return f"user_id={bot_user['max_user_id']} ({name})" if name else f"user_id={bot_user['max_user_id']}"
|
||||||
|
return f"user_id={user_id}"
|
||||||
|
|
||||||
|
|
||||||
class AuthMiddleware(BaseMiddleware):
|
class AuthMiddleware(BaseMiddleware):
|
||||||
"""
|
|
||||||
Проверяет авторизацию пользователя перед передачей события в обработчик.
|
|
||||||
|
|
||||||
Пропускает без проверки:
|
|
||||||
- BotStarted (первый запуск)
|
|
||||||
- Сообщение с командой /start
|
|
||||||
|
|
||||||
Для остальных событий:
|
|
||||||
- Блокированные или незарегистрированные → игнорируем
|
|
||||||
- Авторизованные → добавляем bot_user в data и логируем сообщение
|
|
||||||
"""
|
|
||||||
|
|
||||||
async def __call__(
|
async def __call__(
|
||||||
self,
|
self,
|
||||||
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
handler: Callable[[Any, dict[str, Any]], Awaitable[Any]],
|
||||||
event: Any,
|
event: Any,
|
||||||
data: dict[str, Any],
|
data: dict[str, Any],
|
||||||
) -> Any:
|
) -> Any:
|
||||||
# Пропускаем bot_started — он обрабатывается без авторизации
|
update_type = getattr(event, "update_type", type(event).__name__)
|
||||||
|
|
||||||
|
# BotStarted — всегда пропускаем
|
||||||
if isinstance(event, BotStarted):
|
if isinstance(event, BotStarted):
|
||||||
|
logger.info("▶ bot_started | user_id=%s chat_id=%s", event.user.user_id, event.chat_id)
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
# Пропускаем команду /start
|
# /start — пропускаем без авторизации
|
||||||
if isinstance(event, MessageCreated):
|
if isinstance(event, MessageCreated):
|
||||||
body = event.message.body
|
body = event.message.body
|
||||||
text = (body.text or "").strip() if body else ""
|
text = (body.text or "").strip() if body else ""
|
||||||
if text.lower().startswith("/start"):
|
if text.lower().startswith("/start"):
|
||||||
|
ids = event.get_ids()
|
||||||
|
logger.info("▶ /start | user_id=%s", ids[1])
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
||||||
# Получаем user_id из события
|
# Получаем user_id
|
||||||
ids = event.get_ids()
|
ids = event.get_ids()
|
||||||
user_id = ids[1] if ids else None
|
user_id = ids[1] if ids else None
|
||||||
|
chat_id = ids[0] if ids else None
|
||||||
if user_id is None:
|
if user_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
bot_user = await get_bot_user_by_max_id(user_id)
|
bot_user = await get_bot_user_by_max_id(user_id)
|
||||||
|
|
||||||
if bot_user is None:
|
if bot_user is None:
|
||||||
# Незнакомый пользователь — игнорируем
|
logger.debug("⚠ unknown user_id=%s | %s — ignored", user_id, update_type)
|
||||||
return
|
return
|
||||||
|
|
||||||
if bot_user["is_blocked"]:
|
if bot_user["is_blocked"]:
|
||||||
|
logger.info("🚫 blocked %s | %s — ignored", _user_label(bot_user, user_id), update_type)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not bot_user["is_authorized"]:
|
if not bot_user["is_authorized"]:
|
||||||
# Ещё не авторизовался — игнорируем (повторный промпт не нужен)
|
# Пропускаем шаринг контакта — это шаг авторизации
|
||||||
|
if isinstance(event, MessageCreated):
|
||||||
|
body = event.message.body
|
||||||
|
if body and body.attachments:
|
||||||
|
from maxapi.enums.attachment import AttachmentType
|
||||||
|
for att in body.attachments:
|
||||||
|
if getattr(att, "type", None) == AttachmentType.CONTACT:
|
||||||
|
logger.info(
|
||||||
|
"📱 contact shared | %s",
|
||||||
|
_user_label(bot_user, user_id),
|
||||||
|
)
|
||||||
|
data["bot_user"] = bot_user
|
||||||
|
return await handler(event, data)
|
||||||
|
logger.debug("⚠ not authorized %s | %s — ignored", _user_label(bot_user, user_id), update_type)
|
||||||
return
|
return
|
||||||
|
|
||||||
data["bot_user"] = bot_user
|
data["bot_user"] = bot_user
|
||||||
|
|
||||||
# Логируем входящее сообщение
|
# Логируем входящие сообщения
|
||||||
if isinstance(event, MessageCreated):
|
if isinstance(event, MessageCreated):
|
||||||
chat_id, _ = event.get_ids()
|
|
||||||
body = event.message.body
|
body = event.message.body
|
||||||
|
text_preview = ""
|
||||||
atts = []
|
atts = []
|
||||||
if body and body.attachments:
|
if body:
|
||||||
|
text_preview = (body.text or "")[:80]
|
||||||
|
if body.attachments:
|
||||||
for a in body.attachments:
|
for a in body.attachments:
|
||||||
atts.append({"type": str(getattr(a, "type", ""))})
|
atts.append({"type": str(getattr(a, "type", ""))})
|
||||||
|
logger.info(
|
||||||
|
"💬 message | %s | chat=%s | text=%r | attachments=%s",
|
||||||
|
_user_label(bot_user, user_id), chat_id,
|
||||||
|
text_preview, [a["type"] for a in atts] or None,
|
||||||
|
)
|
||||||
await log_message(
|
await log_message(
|
||||||
direction="in",
|
direction="in",
|
||||||
max_user_id=user_id,
|
max_user_id=user_id,
|
||||||
max_chat_id=chat_id,
|
max_chat_id=chat_id,
|
||||||
message_id=event.message.body.mid if body else None,
|
message_id=body.mid if body else None,
|
||||||
text=body.text if body else None,
|
text=body.text if body else None,
|
||||||
attachments=atts or None,
|
attachments=atts or None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
elif isinstance(event, MessageCallback):
|
||||||
|
payload = event.callback.payload or ""
|
||||||
|
logger.info(
|
||||||
|
"🔘 callback | %s | chat=%s | payload=%r",
|
||||||
|
_user_label(bot_user, user_id), chat_id, payload,
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
logger.info(
|
||||||
|
"📨 %s | %s | chat=%s",
|
||||||
|
update_type, _user_label(bot_user, user_id), chat_id,
|
||||||
|
)
|
||||||
|
|
||||||
return await handler(event, data)
|
return await handler(event, data)
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -71,6 +71,28 @@ async def block_bot_user(max_user_id: int, phone: str | None = None) -> None:
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def reset_bot_user(user_id: int) -> None:
|
||||||
|
"""Сбрасывает авторизацию — пользователь должен заново пройти auth."""
|
||||||
|
db = get_db()
|
||||||
|
await db.execute(
|
||||||
|
"""
|
||||||
|
UPDATE bot_users
|
||||||
|
SET is_authorized = 0, is_blocked = 0, org_id = NULL, phone = NULL,
|
||||||
|
updated_at = datetime('now')
|
||||||
|
WHERE id = ?
|
||||||
|
""",
|
||||||
|
(user_id,),
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_bot_user(user_id: int) -> None:
|
||||||
|
"""Полное удаление пользователя бота (каскад удаляет заявки)."""
|
||||||
|
db = get_db()
|
||||||
|
await db.execute("DELETE FROM bot_users WHERE id = ?", (user_id,))
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
async def unblock_bot_user(user_id: int, org_id: int) -> None:
|
async def unblock_bot_user(user_id: int, org_id: int) -> None:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
await db.execute(
|
await db.execute(
|
||||||
|
|
|
||||||
3
main.py
3
main.py
|
|
@ -1,6 +1,9 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
|
||||||
|
from bot.logging_setup import setup_logging
|
||||||
|
setup_logging() # до всех остальных импортов
|
||||||
|
|
||||||
from database.connection import init_db
|
from database.connection import init_db
|
||||||
from bot.setup import create_bot_and_dispatcher
|
from bot.setup import create_bot_and_dispatcher
|
||||||
from admin.app import create_admin_app
|
from admin.app import create_admin_app
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue