This commit is contained in:
2025-12-16 22:43:03 +07:00
parent 696dc714c4
commit 895e296f44
4 changed files with 66 additions and 18 deletions

View File

@@ -354,7 +354,8 @@ async def create_dispute(
db, db,
user_id=assignment.participant.user_id, user_id=assignment.participant.user_id,
marathon_title=marathon.title, marathon_title=marathon.title,
challenge_title=assignment.challenge.title challenge_title=assignment.challenge.title,
assignment_id=assignment_id
) )
# Load relationships for response # Load relationships for response

View File

@@ -23,6 +23,9 @@ class Settings(BaseSettings):
TELEGRAM_BOT_USERNAME: str = "" TELEGRAM_BOT_USERNAME: str = ""
TELEGRAM_LINK_TOKEN_EXPIRE_MINUTES: int = 10 TELEGRAM_LINK_TOKEN_EXPIRE_MINUTES: int = 10
# Frontend
FRONTEND_URL: str = "http://localhost:3000"
# Uploads # Uploads
UPLOAD_DIR: str = "uploads" UPLOAD_DIR: str = "uploads"
MAX_IMAGE_SIZE: int = 15 * 1024 * 1024 # 15 MB MAX_IMAGE_SIZE: int = 15 * 1024 * 1024 # 15 MB

View File

@@ -1,6 +1,13 @@
import logging
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
# Configure logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from pathlib import Path from pathlib import Path

View File

@@ -22,7 +22,8 @@ class TelegramNotifier:
self, self,
chat_id: int, chat_id: int,
text: str, text: str,
parse_mode: str = "HTML" parse_mode: str = "HTML",
reply_markup: dict | None = None
) -> bool: ) -> bool:
"""Send a message to a Telegram chat.""" """Send a message to a Telegram chat."""
if not self.bot_token: if not self.bot_token:
@@ -31,13 +32,17 @@ class TelegramNotifier:
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
payload = {
"chat_id": chat_id,
"text": text,
"parse_mode": parse_mode
}
if reply_markup:
payload["reply_markup"] = reply_markup
response = await client.post( response = await client.post(
f"{self.api_url}/sendMessage", f"{self.api_url}/sendMessage",
json={ json=payload,
"chat_id": chat_id,
"text": text,
"parse_mode": parse_mode
},
timeout=10.0 timeout=10.0
) )
if response.status_code == 200: if response.status_code == 200:
@@ -53,7 +58,8 @@ class TelegramNotifier:
self, self,
db: AsyncSession, db: AsyncSession,
user_id: int, user_id: int,
message: str message: str,
reply_markup: dict | None = None
) -> bool: ) -> bool:
"""Send notification to a user by user_id.""" """Send notification to a user by user_id."""
result = await db.execute( result = await db.execute(
@@ -61,10 +67,16 @@ class TelegramNotifier:
) )
user = result.scalar_one_or_none() user = result.scalar_one_or_none()
if not user or not user.telegram_id: if not user:
logger.warning(f"[Notify] User {user_id} not found")
return False return False
return await self.send_message(user.telegram_id, message) if not user.telegram_id:
logger.warning(f"[Notify] User {user_id} ({user.nickname}) has no telegram_id")
return False
logger.info(f"[Notify] Sending to user {user.nickname} (telegram_id={user.telegram_id})")
return await self.send_message(user.telegram_id, message, reply_markup=reply_markup)
async def notify_marathon_participants( async def notify_marathon_participants(
self, self,
@@ -171,16 +183,41 @@ class TelegramNotifier:
db: AsyncSession, db: AsyncSession,
user_id: int, user_id: int,
marathon_title: str, marathon_title: str,
challenge_title: str challenge_title: str,
assignment_id: int
) -> bool: ) -> bool:
"""Notify user about dispute raised on their assignment.""" """Notify user about dispute raised on their assignment."""
message = ( logger.info(f"[Dispute] Sending notification to user_id={user_id} for assignment_id={assignment_id}")
f"⚠️ <b>На твоё задание подан спор</b>\n\n"
f"Марафон: {marathon_title}\n" dispute_url = f"{settings.FRONTEND_URL}/assignments/{assignment_id}"
f"Задание: {challenge_title}\n\n" logger.info(f"[Dispute] URL: {dispute_url}")
f"Зайди на сайт, чтобы ответить на спор."
) # Telegram requires HTTPS for inline keyboard URLs
return await self.notify_user(db, user_id, message) use_inline_button = dispute_url.startswith("https://")
if use_inline_button:
message = (
f"⚠️ <b>На твоё задание подан спор</b>\n\n"
f"Марафон: {marathon_title}\n"
f"Задание: {challenge_title}"
)
reply_markup = {
"inline_keyboard": [[
{"text": "Открыть спор", "url": dispute_url}
]]
}
else:
message = (
f"⚠️ <b>На твоё задание подан спор</b>\n\n"
f"Марафон: {marathon_title}\n"
f"Задание: {challenge_title}\n\n"
f"🔗 {dispute_url}"
)
reply_markup = None
result = await self.notify_user(db, user_id, message, reply_markup=reply_markup)
logger.info(f"[Dispute] Notification result: {result}")
return result
async def notify_dispute_resolved( async def notify_dispute_resolved(
self, self,