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 from utils.push_service import send_push_to_user 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_dict = event_data.dict(exclude={'invited_user_ids'}) event = Event( **event_dict, creator_id=current_user.id ) db.add(event) db.commit() db.refresh(event) # Gérer les invitations selon le type d'événement if event.is_private: # Événement privé : inviter uniquement les utilisateurs sélectionnés invited_user_ids = event_data.invited_user_ids or [] # Toujours inclure le créateur if current_user.id not in invited_user_ids: invited_user_ids.append(current_user.id) for user_id in invited_user_ids: user = db.query(User).filter(User.id == user_id, User.is_active == True).first() if user: participation = EventParticipation( event_id=event.id, user_id=user.id, status=ParticipationStatus.PENDING ) db.add(participation) # Create notification if user.id != current_user.id: notif_title = f"Invitation à un événement privé: {event.title}" notif_message = f"{current_user.full_name} vous a invité à un événement privé" notif_link = f"/events/{event.id}" notification = Notification( user_id=user.id, type=NotificationType.EVENT_INVITATION, title=notif_title, message=notif_message, link=notif_link ) db.add(notification) # Send push notification send_push_to_user(db, user.id, notif_title, notif_message, notif_link) # Send email notification try: send_event_notification(user.email, event) except: pass else: # Événement public : inviter tous les utilisateurs actifs 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: notif_title = f"Nouvel événement: {event.title}" notif_message = f"{current_user.full_name} a créé un nouvel événement" notif_link = f"/events/{event.id}" notification = Notification( user_id=user.id, type=NotificationType.EVENT_INVITATION, title=notif_title, message=notif_message, link=notif_link ) db.add(notification) # Send push notification send_push_to_user(db, user.id, notif_title, notif_message, notif_link) # Send email notification try: send_event_notification(user.email, event) except: pass 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() # Filtrer les événements privés : ne montrer que ceux où l'utilisateur est invité filtered_events = [] for event in events: if event.is_private: # Vérifier si l'utilisateur est invité (a une participation) participation = db.query(EventParticipation).filter( EventParticipation.event_id == event.id, EventParticipation.user_id == current_user.id ).first() if participation or event.creator_id == current_user.id or current_user.is_admin: filtered_events.append(event) else: # Événement public : visible par tous filtered_events.append(event) return [format_event_response(event, db) for event in filtered_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() # Filtrer les événements privés filtered_events = [] for event in events: if event.is_private: participation = db.query(EventParticipation).filter( EventParticipation.event_id == event.id, EventParticipation.user_id == current_user.id ).first() if participation or event.creator_id == current_user.id or current_user.is_admin: filtered_events.append(event) else: filtered_events.append(event) return [format_event_response(event, db) for event in filtered_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() # Filtrer les événements privés filtered_events = [] for event in events: if event.is_private: participation = db.query(EventParticipation).filter( EventParticipation.event_id == event.id, EventParticipation.user_id == current_user.id ).first() if participation or event.creator_id == current_user.id or current_user.is_admin: filtered_events.append(event) else: filtered_events.append(event) return [format_event_response(event, db) for event in filtered_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" ) # Vérifier l'accès pour les événements privés if event.is_private: participation = db.query(EventParticipation).filter( EventParticipation.event_id == event.id, EventParticipation.user_id == current_user.id ).first() if not participation and event.creator_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You don't have access to this private event" ) 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.post("/{event_id}/invite", response_model=EventResponse) async def invite_users_to_event( event_id: int, user_ids: List[int], db: Session = Depends(get_db), current_user: User = Depends(get_current_active_user) ): """Invite users to a private 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" ) # Vérifier que l'événement est privé et que l'utilisateur est le créateur ou admin if not event.is_private: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="This endpoint is only for private events" ) if event.creator_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Only the event creator can invite users" ) # Inviter les utilisateurs for user_id in user_ids: # Vérifier si l'utilisateur existe et est actif user = db.query(User).filter(User.id == user_id, User.is_active == True).first() if not user: continue # Vérifier si l'utilisateur n'est pas déjà invité existing_participation = db.query(EventParticipation).filter( EventParticipation.event_id == event_id, EventParticipation.user_id == user_id ).first() if not existing_participation: participation = EventParticipation( event_id=event.id, user_id=user.id, status=ParticipationStatus.PENDING ) db.add(participation) # Créer une notification notif_title = f"Invitation à un événement privé: {event.title}" notif_message = f"{current_user.full_name} vous a invité à un événement privé" notif_link = f"/events/{event.id}" notification = Notification( user_id=user.id, type=NotificationType.EVENT_INVITATION, title=notif_title, message=notif_message, link=notif_link ) db.add(notification) # Send push notification send_push_to_user(db, user.id, notif_title, notif_message, notif_link) # Envoyer un email try: send_event_notification(user.email, event) except: pass db.commit() return format_event_response(event, db) @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.""" 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" ) # Pour les événements privés, vérifier que l'utilisateur est invité if event.is_private: participation_check = db.query(EventParticipation).filter( EventParticipation.event_id == event_id, EventParticipation.user_id == current_user.id ).first() if not participation_check and event.creator_id != current_user.id and not current_user.is_admin: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="You are not invited to this private 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, "is_private": event.is_private if event.is_private is not None else False, "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()