✅ Регистрация и верификация жильцов ✅ Рейтинг активности (уровни) ✅ Детект шпионов (Score 0-100) ✅ Анти-мат система (3 предупреждения → бан) ✅ Админ-панель с рассылкой ✅ Учёт квартир (несколько жильцов) ✅ Телефоны экстренных служб и мастеров ✅ Еженедельный экспорт JSON ✅ Прокси (socks5) для обхода РКН Бекап: backups/versions/v1.0_mvp_2026-02-26/ Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
251 lines
7.9 KiB
Python
251 lines
7.9 KiB
Python
"""
|
||
Events Service — календарь событий дома
|
||
"""
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict, List, Optional
|
||
from sqlalchemy import select, func, delete
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
|
||
class EventsService:
|
||
"""Сервис событий"""
|
||
|
||
def __init__(self, session: AsyncSession):
|
||
self.session = session
|
||
|
||
async def create_event(
|
||
self,
|
||
title: str,
|
||
event_date: datetime,
|
||
description: str = None,
|
||
event_type: str = 'general',
|
||
location: str = None,
|
||
created_by: int = None
|
||
) -> int:
|
||
"""
|
||
Создать событие
|
||
Returns: ID события
|
||
"""
|
||
from database.models import Event
|
||
|
||
event = Event(
|
||
title=title,
|
||
description=description,
|
||
event_type=event_type,
|
||
event_date=event_date,
|
||
location=location,
|
||
created_by=created_by
|
||
)
|
||
self.session.add(event)
|
||
await self.session.commit()
|
||
await self.session.refresh(event)
|
||
|
||
logger.info(f"Событие {title} создано (ID: {event.id})")
|
||
return event.id
|
||
|
||
async def join_event(self, event_id: int, user_id: int) -> bool:
|
||
"""
|
||
Присоединиться к событию
|
||
Returns: True если успешно
|
||
"""
|
||
from database.models import EventParticipant
|
||
|
||
# Проверяем не участвует ли уже
|
||
stmt = select(EventParticipant).where(
|
||
EventParticipant.event_id == event_id,
|
||
EventParticipant.user_id == user_id
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
if result.scalar_one_or_none():
|
||
return False
|
||
|
||
participant = EventParticipant(
|
||
event_id=event_id,
|
||
user_id=user_id
|
||
)
|
||
self.session.add(participant)
|
||
await self.session.commit()
|
||
|
||
logger.info(f"Пользователь {user_id} присоединился к событию {event_id}")
|
||
return True
|
||
|
||
async def leave_event(self, event_id: int, user_id: int) -> bool:
|
||
"""
|
||
Отписаться от события
|
||
Returns: True если успешно
|
||
"""
|
||
from database.models import EventParticipant
|
||
|
||
stmt = delete(EventParticipant).where(
|
||
EventParticipant.event_id == event_id,
|
||
EventParticipant.user_id == user_id
|
||
)
|
||
await self.session.execute(stmt)
|
||
await self.session.commit()
|
||
|
||
logger.info(f"Пользователь {user_id} отписался от события {event_id}")
|
||
return True
|
||
|
||
async def get_event(self, event_id: int) -> Optional[Dict]:
|
||
"""Получить информацию о событии"""
|
||
from database.models import Event
|
||
|
||
event = await self.session.get(Event, event_id)
|
||
if not event:
|
||
return None
|
||
|
||
# Считаем участников
|
||
stmt = select(func.count()).select_from(EventParticipant).where(
|
||
EventParticipant.event_id == event_id
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
participants_count = result.scalar() or 0
|
||
|
||
return {
|
||
'id': event.id,
|
||
'title': event.title,
|
||
'description': event.description,
|
||
'event_type': event.event_type,
|
||
'event_date': event.event_date,
|
||
'location': event.location,
|
||
'created_by': event.created_by,
|
||
'participants_count': participants_count,
|
||
'is_active': event.is_active
|
||
}
|
||
|
||
async def get_upcoming_events(self, limit: int = 10) -> List[Dict]:
|
||
"""Получить предстоящие события"""
|
||
from database.models import Event
|
||
|
||
stmt = (
|
||
select(Event)
|
||
.where(Event.is_active == True)
|
||
.where(Event.event_date >= datetime.utcnow())
|
||
.order_by(Event.event_date)
|
||
.limit(limit)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
events = list(result.scalars().all())
|
||
|
||
return [
|
||
{
|
||
'id': e.id,
|
||
'title': e.title,
|
||
'event_date': e.event_date,
|
||
'location': e.location,
|
||
'event_type': e.event_type
|
||
}
|
||
for e in events
|
||
]
|
||
|
||
async def get_user_events(self, user_id: int) -> List[Dict]:
|
||
"""Получить события пользователя"""
|
||
from database.models import Event, EventParticipant
|
||
|
||
stmt = (
|
||
select(Event)
|
||
.join(EventParticipant, Event.id == EventParticipant.event_id)
|
||
.where(EventParticipant.user_id == user_id)
|
||
.where(Event.is_active == True)
|
||
.order_by(Event.event_date)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
events = list(result.scalars().all())
|
||
|
||
return [
|
||
{
|
||
'id': e.id,
|
||
'title': e.title,
|
||
'event_date': e.event_date,
|
||
'location': e.location,
|
||
'event_type': e.event_type
|
||
}
|
||
for e in events
|
||
]
|
||
|
||
async def cancel_event(self, event_id: int) -> bool:
|
||
"""Отменить событие"""
|
||
from database.models import Event
|
||
|
||
event = await self.session.get(Event, event_id)
|
||
if not event:
|
||
return False
|
||
|
||
event.is_active = False
|
||
await self.session.commit()
|
||
|
||
logger.info(f"Событие {event_id} отменено")
|
||
return True
|
||
|
||
async def get_participants(self, event_id: int) -> List[Dict]:
|
||
"""Получить участников события"""
|
||
from database.models import EventParticipant, User
|
||
|
||
stmt = (
|
||
select(User)
|
||
.join(EventParticipant, User.user_id == EventParticipant.user_id)
|
||
.where(EventParticipant.event_id == event_id)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
users = list(result.scalars().all())
|
||
|
||
return [
|
||
{
|
||
'user_id': u.user_id,
|
||
'name': u.full_name or u.username or f'user{u.user_id}',
|
||
'apartment': u.apartment
|
||
}
|
||
for u in users
|
||
]
|
||
|
||
async def get_events_for_reminder(self, hours_ahead: int = 24) -> List[Dict]:
|
||
"""Получить события для напоминания"""
|
||
from database.models import Event
|
||
|
||
now = datetime.utcnow()
|
||
future = now + timedelta(hours=hours_ahead)
|
||
|
||
stmt = (
|
||
select(Event)
|
||
.where(Event.is_active == True)
|
||
.where(Event.reminder_sent == False)
|
||
.where(Event.event_date > now)
|
||
.where(Event.event_date <= future)
|
||
)
|
||
result = await self.session.execute(stmt)
|
||
events = list(result.scalars().all())
|
||
|
||
return [
|
||
{
|
||
'id': e.id,
|
||
'title': e.title,
|
||
'event_date': e.event_date,
|
||
'location': e.location,
|
||
'description': e.description
|
||
}
|
||
for e in events
|
||
]
|
||
|
||
async def mark_reminder_sent(self, event_id: int):
|
||
"""Отметить что напоминание отправлено"""
|
||
from database.models import Event
|
||
|
||
event = await self.session.get(Event, event_id)
|
||
if event:
|
||
event.reminder_sent = True
|
||
await self.session.commit()
|
||
|
||
def get_event_type_emoji(self, event_type: str) -> str:
|
||
"""Получить emoji типа события"""
|
||
emojis = {
|
||
'meeting': '📋',
|
||
'subbotnik': '🧹',
|
||
'holiday': '🎉',
|
||
'sport': '⚽',
|
||
'kids': '🎈',
|
||
'general': '📅'
|
||
}
|
||
return emojis.get(event_type, '📅')
|