Files
remnawave-bedolaga-telegram…/app/database/crud/rules.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

165 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
import structlog
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import ServiceRule
logger = structlog.get_logger(__name__)
async def get_rules_by_language(db: AsyncSession, language: str = 'ru') -> ServiceRule | None:
result = await db.execute(
select(ServiceRule)
.where(ServiceRule.language == language, ServiceRule.is_active == True)
.order_by(ServiceRule.order, ServiceRule.created_at.desc())
.limit(1)
)
return result.scalar_one_or_none()
async def create_or_update_rules(
db: AsyncSession, content: str, language: str = 'ru', title: str = 'Правила сервиса'
) -> ServiceRule:
existing_rules_result = await db.execute(
select(ServiceRule).where(ServiceRule.language == language, ServiceRule.is_active == True)
)
existing_rules = existing_rules_result.scalars().all()
for rule in existing_rules:
rule.is_active = False
rule.updated_at = datetime.utcnow()
new_rules = ServiceRule(title=title, content=content, language=language, is_active=True, order=0)
db.add(new_rules)
await db.commit()
await db.refresh(new_rules)
logger.info('✅ Правила для языка обновлены (ID: )', language=language, new_rules_id=new_rules.id)
return new_rules
async def clear_all_rules(db: AsyncSession, language: str = 'ru') -> bool:
try:
result = await db.execute(
update(ServiceRule)
.where(ServiceRule.language == language, ServiceRule.is_active == True)
.values(is_active=False, updated_at=datetime.utcnow())
)
await db.commit()
rows_affected = result.rowcount
logger.info(
'✅ Очищены правила для языка . Деактивировано записей', language=language, rows_affected=rows_affected
)
return rows_affected > 0
except Exception as e:
logger.error('❌ Ошибка при очистке правил для языка', language=language, error=e)
await db.rollback()
raise
async def get_current_rules_content(db: AsyncSession, language: str = 'ru') -> str:
rules = await get_rules_by_language(db, language)
if rules:
return rules.content
return """
🔒 <b>Правила использования сервиса</b>
1. Сервис предоставляется "как есть" без каких-либо гарантий.
2. Запрещается использование сервиса для незаконных действий.
3. Администрация оставляет за собой право заблокировать доступ пользователя при нарушении правил.
4. Возврат средств осуществляется в соответствии с политикой возврата.
5. Пользователь несет полную ответственность за безопасность своего аккаунта.
6. При возникновении вопросов обращайтесь в техническую поддержку.
Используя сервис, вы соглашаетесь с данными правилами.
"""
async def get_all_rules_versions(db: AsyncSession, language: str = 'ru', limit: int = 10) -> list[ServiceRule]:
result = await db.execute(
select(ServiceRule).where(ServiceRule.language == language).order_by(ServiceRule.created_at.desc()).limit(limit)
)
return result.scalars().all()
async def restore_rules_version(db: AsyncSession, rule_id: int, language: str = 'ru') -> ServiceRule | None:
try:
result = await db.execute(
select(ServiceRule).where(ServiceRule.id == rule_id, ServiceRule.language == language)
)
rule_to_restore = result.scalar_one_or_none()
if not rule_to_restore:
logger.warning('Правило с ID не найдено для языка', rule_id=rule_id, language=language)
return None
await db.execute(
update(ServiceRule)
.where(ServiceRule.language == language, ServiceRule.is_active == True)
.values(is_active=False, updated_at=datetime.utcnow())
)
restored_rule = ServiceRule(
title=rule_to_restore.title, content=rule_to_restore.content, language=language, is_active=True, order=0
)
db.add(restored_rule)
await db.commit()
await db.refresh(restored_rule)
logger.info(
'✅ Восстановлена версия правил ID как новое правило ID', rule_id=rule_id, restored_rule_id=restored_rule.id
)
return restored_rule
except Exception as e:
logger.error('❌ Ошибка при восстановлении правил ID', rule_id=rule_id, error=e)
await db.rollback()
raise
async def get_rules_statistics(db: AsyncSession) -> dict:
try:
active_result = await db.execute(select(ServiceRule).where(ServiceRule.is_active == True))
active_rules = active_result.scalars().all()
all_result = await db.execute(select(ServiceRule))
all_rules = all_result.scalars().all()
languages_stats = {}
for rule in active_rules:
lang = rule.language
if lang not in languages_stats:
languages_stats[lang] = {'active_count': 0, 'last_updated': None, 'content_length': 0}
languages_stats[lang]['active_count'] += 1
languages_stats[lang]['content_length'] = len(rule.content)
if not languages_stats[lang]['last_updated'] or rule.updated_at > languages_stats[lang]['last_updated']:
languages_stats[lang]['last_updated'] = rule.updated_at
return {
'total_active': len(active_rules),
'total_all_time': len(all_rules),
'languages': languages_stats,
'total_languages': len(languages_stats),
}
except Exception as e:
logger.error('❌ Ошибка при получении статистики правил', error=e)
return {'total_active': 0, 'total_all_time': 0, 'languages': {}, 'total_languages': 0, 'error': str(e)}