Files
Fringg eb18994b7d fix: complete datetime.utcnow() → datetime.now(UTC) migration
- Migrate 660+ datetime.utcnow() across 153 files to datetime.now(UTC)
- Migrate 30+ datetime.now() without UTC to datetime.now(UTC)
- Convert all 170 DateTime columns to DateTime(timezone=True)
- Add migrate_datetime_to_timestamptz() in universal_migration with SET LOCAL timezone='UTC' safety
- Remove 70+ .replace(tzinfo=None) workarounds
- Fix utcfromtimestamp → fromtimestamp(..., tz=UTC)
- Fix fromtimestamp() without tz= (system_logs, backup_service, referral_diagnostics)
- Fix fromisoformat/isoparse to ensure aware output (platega, yookassa, wata, miniapp, nalogo)
- Fix strptime() to add .replace(tzinfo=UTC) (backup_service, referral_diagnostics)
- Fix datetime.combine() to include tzinfo=UTC (remnawave_sync, traffic_monitoring)
- Fix datetime.max/datetime.min sentinels with .replace(tzinfo=UTC)
- Rename panel_datetime_to_naive_utc → panel_datetime_to_utc
- Remove DTZ003 from ruff ignore list
2026-02-17 04:45:40 +03:00

166 lines
4.9 KiB
Python

import random
from datetime import UTC, 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.now(UTC)
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.now(UTC)
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,
}