Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/bot/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Telegram Bot module for Findar fraud detection system.

This module provides Telegram bot functionality for user interaction,
registration verification, and notification delivery.
"""

from .app import start_bot

__all__ = ["start_bot"]
96 changes: 96 additions & 0 deletions src/bot/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
Telegram bot message handlers.

Contains handlers for bot commands and messages.
"""

from aiogram import Router
from aiogram.filters import CommandStart
from aiogram.types import Message

from src.core.logging import get_logger
from src.modules.users.repository import UserRepository
from src.storage.sql import get_async_session

logger = get_logger("bot.handlers")

# Create router for handlers
router = Router()


@router.message(CommandStart())
async def cmd_start(message: Message) -> None:
"""
Handle /start command.

Checks if user is registered and provides appropriate response.
"""
telegram_id = message.from_user.id
username = message.from_user.username

logger.info(
"Received /start command",
component="telegram_bot",
telegram_id=telegram_id,
username=username,
)

try:
# Get database session
async for session in get_async_session():
user_repo = UserRepository(session)

# Check if user exists by telegram_id
user = await user_repo.get_user_by_telegram_id(telegram_id)

if user:
# User is registered
await message.answer(
f"✅ Привет, {message.from_user.first_name}!\n\n"
f"Ты уже зарегистрирован в системе Findar.\n"
f"Email: {user.email}\n\n"
f"Ты будешь получать уведомления о подозрительных транзакциях на этот Telegram аккаунт."
)
logger.info(
"User already registered",
component="telegram_bot",
telegram_id=telegram_id,
user_id=str(user.id),
)
else:
# User NOT registered
await message.answer(
f"👋 Привет, {message.from_user.first_name}!\n\n"
f"Ты ещё не зарегистрирован в системе Findar.\n\n"
f"Пожалуйста, пройди регистрацию на сайте:\n"
f"https://findar.example.com/register\n\n"
f"Затем возвращайся и снова напиши /start"
)
logger.info(
"User not registered, sent registration instructions",
component="telegram_bot",
telegram_id=telegram_id,
)

break # Exit async generator

except Exception as e:
logger.exception(
"Error handling /start command",
component="telegram_bot",
telegram_id=telegram_id,
)
await message.answer(
"❌ Произошла ошибка при обработке команды.\n"
f"Попробуй позже или обратись в поддержку. {e}"
)


@router.message()
async def handle_unknown_message(message: Message) -> None:
"""Handle all other messages."""
await message.answer(
"ℹ️ Я бот для уведомлений о фродовых транзакциях.\n\n"
"Доступные команды:\n"
"/start - Проверить статус регистрации"
)
8 changes: 7 additions & 1 deletion src/modules/rule_engine/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ class MLRuleParams(BaseModel):
Parameters for machine learning-based fraud detection rules.

ML rules use trained models to assess transaction risk with confidence scores.
The model is accessed via an endpoint URL.
The model is accessed via an endpoint URL or uploaded as a file.
"""

# Model configuration
Expand All @@ -206,6 +206,12 @@ class MLRuleParams(BaseModel):
# Endpoint configuration
endpoint_url: str = Field(description="URL of the ML model inference endpoint")

# Model file path (optional, alternative to endpoint)
model_file_path: str | None = Field(
default=None,
description="Path to uploaded ML model file (alternative to endpoint_url)",
)

@field_validator("endpoint_url")
@classmethod
def validate_endpoint_url(cls, v):
Expand Down
234 changes: 234 additions & 0 deletions src/modules/users/notifications_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
"""
User notification settings routes.

Provides API endpoints for users to manage their notification templates
and channel settings (email/telegram).
"""

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession

from src.core.logging import get_logger
from src.modules.notifications.repository import NotificationRepository
from src.modules.notifications.schemas import (
NotificationChannelsResponse,
NotificationChannelsUpdate,
TemplateFieldsUpdate,
UserNotificationTemplateResponse,
UserNotificationTemplatesResponse,
)
from src.modules.users.dependencies import get_current_user
from src.modules.users.repository import UserRepository
from src.storage.dependencies import get_db_session
from src.storage.models import User

logger = get_logger("users.notifications_routes")

router = APIRouter(prefix="/users/notifications", tags=["user-notifications"])


@router.get(
"/templates/",
response_model=UserNotificationTemplatesResponse,
summary="Get user's notification templates",
)
async def get_user_templates(
current_user: User = Depends(get_current_user),
db_session: AsyncSession = Depends(get_db_session),
) -> UserNotificationTemplatesResponse:
"""
Get current user's email and telegram notification templates.

Returns both templates with their field visibility settings.
"""
notification_repo = NotificationRepository(db_session)

# Get email template
email_template_id = getattr(current_user, "email_template_id", None)
if not email_template_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Email template not found. Please contact support.",
)

email_template = await notification_repo.get_template(email_template_id)
if not email_template:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Email template not found in database.",
)

# Get telegram template
telegram_template_id = getattr(current_user, "telegram_template_id", None)
if not telegram_template_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Telegram template not found. Please contact support.",
)

telegram_template = await notification_repo.get_template(telegram_template_id)
if not telegram_template:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Telegram template not found in database.",
)

return UserNotificationTemplatesResponse(
email_template=UserNotificationTemplateResponse.model_validate(email_template),
telegram_template=UserNotificationTemplateResponse.model_validate(
telegram_template
),
)


@router.patch(
"/templates/email",
response_model=UserNotificationTemplateResponse,
summary="Update email template fields",
)
async def update_email_template(
fields: TemplateFieldsUpdate,
current_user: User = Depends(get_current_user),
db_session: AsyncSession = Depends(get_db_session),
) -> UserNotificationTemplateResponse:
"""
Update email template field visibility settings.

Only updates the show_* fields that control which information
is included in email notifications.
"""
email_template_id = getattr(current_user, "email_template_id", None)
if not email_template_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Email template not found.",
)

notification_repo = NotificationRepository(db_session)

# Update template fields
fields_dict = fields.model_dump(exclude_unset=True)
updated_template = await notification_repo.update_template_fields(
email_template_id, fields_dict
)

if not updated_template:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Template not found or update failed.",
)

logger.info(
f"User {current_user.id} updated email template fields",
user_id=str(current_user.id),
updated_fields=list(fields_dict.keys()),
)

return UserNotificationTemplateResponse.model_validate(updated_template)


@router.patch(
"/templates/telegram",
response_model=UserNotificationTemplateResponse,
summary="Update telegram template fields",
)
async def update_telegram_template(
fields: TemplateFieldsUpdate,
current_user: User = Depends(get_current_user),
db_session: AsyncSession = Depends(get_db_session),
) -> UserNotificationTemplateResponse:
"""
Update telegram template field visibility settings.

Only updates the show_* fields that control which information
is included in telegram notifications.
"""
telegram_template_id = getattr(current_user, "telegram_template_id", None)
if not telegram_template_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Telegram template not found.",
)

notification_repo = NotificationRepository(db_session)

# Update template fields
fields_dict = fields.model_dump(exclude_unset=True)
updated_template = await notification_repo.update_template_fields(
telegram_template_id, fields_dict
)

if not updated_template:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Template not found or update failed.",
)

logger.info(
f"User {current_user.id} updated telegram template fields",
user_id=str(current_user.id),
updated_fields=list(fields_dict.keys()),
)

return UserNotificationTemplateResponse.model_validate(updated_template)


@router.get(
"/channels/",
response_model=NotificationChannelsResponse,
summary="Get notification channel settings",
)
async def get_notification_channels(
current_user: User = Depends(get_current_user),
) -> NotificationChannelsResponse:
"""
Get current user's notification channel settings.

Returns whether email and telegram notifications are enabled.
"""
return NotificationChannelsResponse(
email_enabled=getattr(current_user, "email_notifications_enabled", True),
telegram_enabled=getattr(current_user, "telegram_notifications_enabled", True),
)


@router.patch(
"/channels/",
response_model=NotificationChannelsResponse,
summary="Update notification channel settings",
)
async def update_notification_channels(
settings: NotificationChannelsUpdate,
current_user: User = Depends(get_current_user),
db_session: AsyncSession = Depends(get_db_session),
) -> NotificationChannelsResponse:
"""
Enable or disable email/telegram notification channels.

Allows users to turn on/off notifications for each channel independently.
"""
user_repo = UserRepository(db_session)

updated_user = await user_repo.update_notification_channels(
user_id=current_user.id,
email_enabled=settings.email_enabled,
telegram_enabled=settings.telegram_enabled,
)

if not updated_user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found or update failed.",
)

logger.info(
f"User {current_user.id} updated notification channels",
user_id=str(current_user.id),
email_enabled=settings.email_enabled,
telegram_enabled=settings.telegram_enabled,
)

return NotificationChannelsResponse(
email_enabled=updated_user.email_notifications_enabled,
telegram_enabled=updated_user.telegram_notifications_enabled,
)
Loading