Files
LeDiscord/backend/api/routers/events.py
2025-08-27 18:34:38 +02:00

262 lines
9.0 KiB
Python
Executable File

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session, joinedload
from typing import List
from datetime import datetime
from config.database import get_db
from models.event import Event, EventParticipation, ParticipationStatus
from models.user import User
from models.notification import Notification, NotificationType
from schemas.event import EventCreate, EventUpdate, EventResponse, ParticipationUpdate
from utils.security import get_current_active_user
from utils.email import send_event_notification
router = APIRouter()
@router.post("/", response_model=EventResponse)
async def create_event(
event_data: EventCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Create a new event."""
event = Event(
**event_data.dict(),
creator_id=current_user.id
)
db.add(event)
db.commit()
db.refresh(event)
# Create participations for all active users
users = db.query(User).filter(User.is_active == True).all()
for user in users:
participation = EventParticipation(
event_id=event.id,
user_id=user.id,
status=ParticipationStatus.PENDING
)
db.add(participation)
# Create notification
if user.id != current_user.id:
notification = Notification(
user_id=user.id,
type=NotificationType.EVENT_INVITATION,
title=f"Nouvel événement: {event.title}",
message=f"{current_user.full_name} a créé un nouvel événement",
link=f"/events/{event.id}"
)
db.add(notification)
# Send email notification (async task would be better)
try:
send_event_notification(user.email, event)
except:
pass # Don't fail if email sending fails
db.commit()
return format_event_response(event, db)
@router.get("/", response_model=List[EventResponse])
async def get_events(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user),
upcoming: bool = None
):
"""Get all events, optionally filtered by upcoming status."""
query = db.query(Event).options(joinedload(Event.creator))
if upcoming is True:
# Only upcoming events
query = query.filter(Event.date >= datetime.utcnow())
elif upcoming is False:
# Only past events
query = query.filter(Event.date < datetime.utcnow())
# If upcoming is None, return all events
events = query.order_by(Event.date.desc()).all()
return [format_event_response(event, db) for event in events]
@router.get("/upcoming", response_model=List[EventResponse])
async def get_upcoming_events(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get only upcoming events."""
events = db.query(Event).options(joinedload(Event.creator)).filter(
Event.date >= datetime.utcnow()
).order_by(Event.date).all()
return [format_event_response(event, db) for event in events]
@router.get("/past", response_model=List[EventResponse])
async def get_past_events(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get only past events."""
events = db.query(Event).options(joinedload(Event.creator)).filter(
Event.date < datetime.utcnow()
).order_by(Event.date.desc()).all()
return [format_event_response(event, db) for event in events]
@router.get("/{event_id}", response_model=EventResponse)
async def get_event(
event_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Get a specific event."""
event = db.query(Event).filter(Event.id == event_id).first()
if not event:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Event not found"
)
return format_event_response(event, db)
@router.put("/{event_id}", response_model=EventResponse)
async def update_event(
event_id: int,
event_update: EventUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Update an event."""
event = db.query(Event).filter(Event.id == event_id).first()
if not event:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Event not found"
)
if event.creator_id != current_user.id and not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to update this event"
)
for field, value in event_update.dict(exclude_unset=True).items():
setattr(event, field, value)
db.commit()
db.refresh(event)
return format_event_response(event, db)
@router.delete("/{event_id}")
async def delete_event(
event_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Delete an event."""
event = db.query(Event).filter(Event.id == event_id).first()
if not event:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Event not found"
)
if event.creator_id != current_user.id and not current_user.is_admin:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not authorized to delete this event"
)
db.delete(event)
db.commit()
return {"message": "Event deleted successfully"}
@router.put("/{event_id}/participation", response_model=EventResponse)
async def update_participation(
event_id: int,
participation_update: ParticipationUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_active_user)
):
"""Update user participation status for an event."""
participation = db.query(EventParticipation).filter(
EventParticipation.event_id == event_id,
EventParticipation.user_id == current_user.id
).first()
if not participation:
# Create new participation if it doesn't exist
participation = EventParticipation(
event_id=event_id,
user_id=current_user.id,
status=participation_update.status
)
db.add(participation)
else:
participation.status = participation_update.status
participation.response_date = datetime.utcnow()
db.commit()
# Update user attendance rate
update_user_attendance_rate(current_user, db)
event = db.query(Event).filter(Event.id == event_id).first()
return format_event_response(event, db)
def format_event_response(event: Event, db: Session) -> dict:
"""Format event response with participation counts."""
participations = []
present_count = absent_count = maybe_count = pending_count = 0
# Get creator user directly
creator = db.query(User).filter(User.id == event.creator_id).first()
for p in event.participations:
user = db.query(User).filter(User.id == p.user_id).first()
participations.append({
"user_id": user.id,
"username": user.username,
"full_name": user.full_name,
"avatar_url": user.avatar_url,
"status": p.status,
"response_date": p.response_date
})
if p.status == ParticipationStatus.PRESENT:
present_count += 1
elif p.status == ParticipationStatus.ABSENT:
absent_count += 1
elif p.status == ParticipationStatus.MAYBE:
maybe_count += 1
else:
pending_count += 1
return {
"id": event.id,
"title": event.title,
"description": event.description,
"location": event.location,
"date": event.date,
"end_date": event.end_date,
"creator_id": event.creator_id,
"cover_image": event.cover_image,
"created_at": event.created_at,
"updated_at": event.updated_at,
"creator_name": creator.full_name if creator else "Unknown",
"creator_avatar": creator.avatar_url if creator else None,
"participations": participations,
"present_count": present_count,
"absent_count": absent_count,
"maybe_count": maybe_count,
"pending_count": pending_count
}
def update_user_attendance_rate(user: User, db: Session):
"""Update user attendance rate based on past events."""
past_participations = db.query(EventParticipation).join(Event).filter(
EventParticipation.user_id == user.id,
Event.date < datetime.utcnow(),
EventParticipation.status.in_([ParticipationStatus.PRESENT, ParticipationStatus.ABSENT])
).all()
if past_participations:
present_count = sum(1 for p in past_participations if p.status == ParticipationStatus.PRESENT)
user.attendance_rate = (present_count / len(past_participations)) * 100
db.commit()