From 895e296f44d1360056abaea21935b84da195b28c Mon Sep 17 00:00:00 2001 From: Oronemu Date: Tue, 16 Dec 2025 22:43:03 +0700 Subject: [PATCH] Fixes --- backend/app/api/v1/assignments.py | 3 +- backend/app/core/config.py | 3 + backend/app/main.py | 7 +++ backend/app/services/telegram_notifier.py | 71 +++++++++++++++++------ 4 files changed, 66 insertions(+), 18 deletions(-) diff --git a/backend/app/api/v1/assignments.py b/backend/app/api/v1/assignments.py index 7ce7bfb..089c545 100644 --- a/backend/app/api/v1/assignments.py +++ b/backend/app/api/v1/assignments.py @@ -354,7 +354,8 @@ async def create_dispute( db, user_id=assignment.participant.user_id, marathon_title=marathon.title, - challenge_title=assignment.challenge.title + challenge_title=assignment.challenge.title, + assignment_id=assignment_id ) # Load relationships for response diff --git a/backend/app/core/config.py b/backend/app/core/config.py index cea12b1..8d8db03 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -23,6 +23,9 @@ class Settings(BaseSettings): TELEGRAM_BOT_USERNAME: str = "" TELEGRAM_LINK_TOKEN_EXPIRE_MINUTES: int = 10 + # Frontend + FRONTEND_URL: str = "http://localhost:3000" + # Uploads UPLOAD_DIR: str = "uploads" MAX_IMAGE_SIZE: int = 15 * 1024 * 1024 # 15 MB diff --git a/backend/app/main.py b/backend/app/main.py index 4903c09..b9737e7 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,6 +1,13 @@ +import logging from contextlib import asynccontextmanager 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.staticfiles import StaticFiles from pathlib import Path diff --git a/backend/app/services/telegram_notifier.py b/backend/app/services/telegram_notifier.py index 3f3def7..eda328c 100644 --- a/backend/app/services/telegram_notifier.py +++ b/backend/app/services/telegram_notifier.py @@ -22,7 +22,8 @@ class TelegramNotifier: self, chat_id: int, text: str, - parse_mode: str = "HTML" + parse_mode: str = "HTML", + reply_markup: dict | None = None ) -> bool: """Send a message to a Telegram chat.""" if not self.bot_token: @@ -31,13 +32,17 @@ class TelegramNotifier: try: 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( f"{self.api_url}/sendMessage", - json={ - "chat_id": chat_id, - "text": text, - "parse_mode": parse_mode - }, + json=payload, timeout=10.0 ) if response.status_code == 200: @@ -53,7 +58,8 @@ class TelegramNotifier: self, db: AsyncSession, user_id: int, - message: str + message: str, + reply_markup: dict | None = None ) -> bool: """Send notification to a user by user_id.""" result = await db.execute( @@ -61,10 +67,16 @@ class TelegramNotifier: ) 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 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( self, @@ -171,16 +183,41 @@ class TelegramNotifier: db: AsyncSession, user_id: int, marathon_title: str, - challenge_title: str + challenge_title: str, + assignment_id: int ) -> bool: """Notify user about dispute raised on their assignment.""" - message = ( - f"⚠️ На твоё задание подан спор\n\n" - f"Марафон: {marathon_title}\n" - f"Задание: {challenge_title}\n\n" - f"Зайди на сайт, чтобы ответить на спор." - ) - return await self.notify_user(db, user_id, message) + logger.info(f"[Dispute] Sending notification to user_id={user_id} for assignment_id={assignment_id}") + + dispute_url = f"{settings.FRONTEND_URL}/assignments/{assignment_id}" + logger.info(f"[Dispute] URL: {dispute_url}") + + # Telegram requires HTTPS for inline keyboard URLs + use_inline_button = dispute_url.startswith("https://") + + if use_inline_button: + message = ( + f"⚠️ На твоё задание подан спор\n\n" + f"Марафон: {marathon_title}\n" + f"Задание: {challenge_title}" + ) + reply_markup = { + "inline_keyboard": [[ + {"text": "Открыть спор", "url": dispute_url} + ]] + } + else: + message = ( + f"⚠️ На твоё задание подан спор\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( self,