Files
remnawave-bedolaga-telegram…/app/database/crud/user_message.py
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
  via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
  to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
  so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
  270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
  for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
  style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
  and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
  to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
  switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
  inline.py, and admin/payments.py
2026-02-16 09:18:12 +03:00

166 lines
4.9 KiB
Python

import random
from datetime import datetime
import structlog
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import User, UserMessage
from app.utils.validators import sanitize_html, validate_html_tags
logger = structlog.get_logger(__name__)
async def create_user_message(
db: AsyncSession, message_text: str, created_by: int | None = None, is_active: bool = True, sort_order: int = 0
) -> UserMessage:
is_valid, error_message = validate_html_tags(message_text)
if not is_valid:
raise ValueError(error_message)
resolved_creator = created_by
if created_by is not None:
result = await db.execute(select(User.id).where(User.id == created_by))
resolved_creator = result.scalar_one_or_none()
message = UserMessage(
message_text=message_text,
is_active=is_active,
sort_order=sort_order,
created_by=resolved_creator,
)
db.add(message)
await db.commit()
await db.refresh(message)
logger.info('✅ Создано сообщение ID пользователем', message_id=message.id, created_by=created_by)
return message
async def get_user_message_by_id(db: AsyncSession, message_id: int) -> UserMessage | None:
result = await db.execute(select(UserMessage).where(UserMessage.id == message_id))
return result.scalar_one_or_none()
async def get_active_user_messages(db: AsyncSession) -> list[UserMessage]:
result = await db.execute(
select(UserMessage)
.where(UserMessage.is_active == True)
.order_by(UserMessage.sort_order.asc(), UserMessage.created_at.desc())
)
return result.scalars().all()
async def get_random_active_message(db: AsyncSession) -> str | None:
active_messages = await get_active_user_messages(db)
if not active_messages:
return None
random_message = random.choice(active_messages)
return sanitize_html(random_message.message_text)
async def get_all_user_messages(
db: AsyncSession,
offset: int = 0,
limit: int = 50,
include_inactive: bool = True,
) -> list[UserMessage]:
query = select(UserMessage).order_by(UserMessage.created_at.desc())
if not include_inactive:
query = query.where(UserMessage.is_active == True)
result = await db.execute(query.offset(offset).limit(limit))
return result.scalars().all()
async def get_user_messages_count(db: AsyncSession, include_inactive: bool = True) -> int:
query = select(func.count(UserMessage.id))
if not include_inactive:
query = query.where(UserMessage.is_active == True)
result = await db.execute(query)
return result.scalar()
async def update_user_message(
db: AsyncSession,
message_id: int,
message_text: str | None = None,
is_active: bool | None = None,
sort_order: int | None = None,
) -> UserMessage | None:
message = await get_user_message_by_id(db, message_id)
if not message:
return None
if message_text is not None:
is_valid, error_message = validate_html_tags(message_text)
if not is_valid:
raise ValueError(error_message)
message.message_text = message_text
if is_active is not None:
message.is_active = is_active
if sort_order is not None:
message.sort_order = sort_order
message.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(message)
logger.info('📝 Обновлено сообщение ID', message_id=message_id)
return message
async def toggle_user_message_status(db: AsyncSession, message_id: int) -> UserMessage | None:
message = await get_user_message_by_id(db, message_id)
if not message:
return None
message.is_active = not message.is_active
message.updated_at = datetime.utcnow()
await db.commit()
await db.refresh(message)
status_text = 'активировано' if message.is_active else 'деактивировано'
logger.info('🔄 Сообщение ID', message_id=message_id, status_text=status_text)
return message
async def delete_user_message(db: AsyncSession, message_id: int) -> bool:
message = await get_user_message_by_id(db, message_id)
if not message:
return False
await db.delete(message)
await db.commit()
logger.info('🗑️ Удалено сообщение ID', message_id=message_id)
return True
async def get_user_messages_stats(db: AsyncSession) -> dict:
total_result = await db.execute(select(func.count(UserMessage.id)))
total_messages = total_result.scalar()
active_result = await db.execute(select(func.count(UserMessage.id)).where(UserMessage.is_active == True))
active_messages = active_result.scalar()
return {
'total_messages': total_messages,
'active_messages': active_messages,
'inactive_messages': total_messages - active_messages,
}