Fixes
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user