257 lines
9.4 KiB
Python
257 lines
9.4 KiB
Python
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
from typing import List, Optional
|
|
import asyncpg
|
|
from datetime import datetime, date, time
|
|
import os
|
|
|
|
# Configuration de la base de données
|
|
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://user:password@db:5432/dockezr")
|
|
|
|
# Pool de connexions
|
|
pool = None
|
|
|
|
# Modèles Pydantic pour les Salles
|
|
class RoomCreate(BaseModel):
|
|
name: str
|
|
capacity: int
|
|
equipment: Optional[str] = None
|
|
description: Optional[str] = None
|
|
|
|
class Room(BaseModel):
|
|
id: int
|
|
name: str
|
|
capacity: int
|
|
equipment: Optional[str]
|
|
description: Optional[str]
|
|
|
|
# Modèles Pydantic pour les Réservations
|
|
class ReservationCreate(BaseModel):
|
|
room_id: int
|
|
user_name: str
|
|
user_email: Optional[str] = None
|
|
reservation_date: date
|
|
start_time: time
|
|
end_time: time
|
|
purpose: str
|
|
|
|
class Reservation(BaseModel):
|
|
id: int
|
|
room_id: int
|
|
room_name: Optional[str] = None
|
|
user_name: str
|
|
user_email: Optional[str] = None
|
|
reservation_date: date
|
|
start_time: time
|
|
end_time: time
|
|
purpose: str
|
|
created_at: datetime
|
|
|
|
# Application FastAPI
|
|
app = FastAPI(
|
|
title="Expernet - Système de Réservation de Salles",
|
|
version="1.0.0",
|
|
description="API de gestion des réservations de salles pour le centre de formation Expernet"
|
|
)
|
|
|
|
# Configuration CORS
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["http://localhost:3000", "http://frontend:3000"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
@app.on_event("startup")
|
|
async def startup():
|
|
global pool
|
|
pool = await asyncpg.create_pool(DATABASE_URL)
|
|
# Créer les tables si elles n'existent pas
|
|
async with pool.acquire() as conn:
|
|
# Table des salles
|
|
await conn.execute('''
|
|
CREATE TABLE IF NOT EXISTS rooms (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
capacity INTEGER NOT NULL,
|
|
equipment TEXT,
|
|
description TEXT
|
|
)
|
|
''')
|
|
|
|
# Table des réservations
|
|
await conn.execute('''
|
|
CREATE TABLE IF NOT EXISTS reservations (
|
|
id SERIAL PRIMARY KEY,
|
|
room_id INTEGER NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
|
|
user_name VARCHAR(255) NOT NULL,
|
|
user_email VARCHAR(255),
|
|
reservation_date DATE NOT NULL,
|
|
start_time TIME NOT NULL,
|
|
end_time TIME NOT NULL,
|
|
purpose TEXT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT NOW()
|
|
)
|
|
''')
|
|
|
|
# Insérer des salles par défaut si la table est vide
|
|
count = await conn.fetchval('SELECT COUNT(*) FROM rooms')
|
|
if count == 0:
|
|
await conn.execute('''
|
|
INSERT INTO rooms (name, capacity, equipment, description) VALUES
|
|
('Salle Atlas', 30, 'Vidéoprojecteur, Tableau blanc, WiFi', 'Grande salle de formation idéale pour les cours'),
|
|
('Salle Horizon', 15, 'Écran TV, Tableau blanc, WiFi', 'Salle de réunion moyenne capacité'),
|
|
('Salle Innovation', 50, 'Vidéoprojecteur, Sono, WiFi, Climatisation', 'Salle de conférence grande capacité'),
|
|
('Salle Connect', 8, 'Écran TV, Visioconférence, WiFi', 'Petite salle pour réunions d''équipe'),
|
|
('Salle Digital', 20, '20 postes informatiques, Vidéoprojecteur, WiFi', 'Salle informatique pour formations pratiques')
|
|
''')
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown():
|
|
await pool.close()
|
|
|
|
@app.get("/")
|
|
async def root():
|
|
return {"message": "Bienvenue sur le système de réservation de salles Expernet!", "status": "running"}
|
|
|
|
@app.get("/health")
|
|
async def health_check():
|
|
return {"status": "healthy", "database": "connected"}
|
|
|
|
# === ROUTES POUR LES SALLES ===
|
|
|
|
@app.get("/rooms", response_model=List[Room])
|
|
async def get_rooms():
|
|
"""Récupérer toutes les salles"""
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch('SELECT * FROM rooms ORDER BY name')
|
|
return [dict(row) for row in rows]
|
|
|
|
@app.get("/rooms/{room_id}", response_model=Room)
|
|
async def get_room(room_id: int):
|
|
"""Récupérer une salle spécifique"""
|
|
async with pool.acquire() as conn:
|
|
row = await conn.fetchrow('SELECT * FROM rooms WHERE id = $1', room_id)
|
|
if row is None:
|
|
raise HTTPException(status_code=404, detail="Salle non trouvée")
|
|
return dict(row)
|
|
|
|
@app.post("/rooms", response_model=Room)
|
|
async def create_room(room: RoomCreate):
|
|
"""Créer une nouvelle salle"""
|
|
async with pool.acquire() as conn:
|
|
try:
|
|
row = await conn.fetchrow(
|
|
'INSERT INTO rooms (name, capacity, equipment, description) VALUES ($1, $2, $3, $4) RETURNING *',
|
|
room.name, room.capacity, room.equipment, room.description
|
|
)
|
|
return dict(row)
|
|
except asyncpg.UniqueViolationError:
|
|
raise HTTPException(status_code=400, detail="Une salle avec ce nom existe déjà")
|
|
|
|
@app.delete("/rooms/{room_id}")
|
|
async def delete_room(room_id: int):
|
|
"""Supprimer une salle"""
|
|
async with pool.acquire() as conn:
|
|
result = await conn.execute('DELETE FROM rooms WHERE id = $1', room_id)
|
|
if result == "DELETE 0":
|
|
raise HTTPException(status_code=404, detail="Salle non trouvée")
|
|
return {"message": "Salle supprimée avec succès"}
|
|
|
|
# === ROUTES POUR LES RÉSERVATIONS ===
|
|
|
|
@app.get("/reservations", response_model=List[Reservation])
|
|
async def get_reservations():
|
|
"""Récupérer toutes les réservations avec les informations des salles"""
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch('''
|
|
SELECT r.*, ro.name as room_name
|
|
FROM reservations r
|
|
JOIN rooms ro ON r.room_id = ro.id
|
|
ORDER BY r.reservation_date DESC, r.start_time DESC
|
|
''')
|
|
return [dict(row) for row in rows]
|
|
|
|
@app.get("/reservations/room/{room_id}", response_model=List[Reservation])
|
|
async def get_reservations_by_room(room_id: int):
|
|
"""Récupérer les réservations d'une salle spécifique"""
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch('''
|
|
SELECT r.*, ro.name as room_name
|
|
FROM reservations r
|
|
JOIN rooms ro ON r.room_id = ro.id
|
|
WHERE r.room_id = $1
|
|
ORDER BY r.reservation_date, r.start_time
|
|
''', room_id)
|
|
return [dict(row) for row in rows]
|
|
|
|
@app.get("/reservations/date/{reservation_date}")
|
|
async def get_reservations_by_date(reservation_date: date):
|
|
"""Récupérer les réservations pour une date spécifique"""
|
|
async with pool.acquire() as conn:
|
|
rows = await conn.fetch('''
|
|
SELECT r.*, ro.name as room_name
|
|
FROM reservations r
|
|
JOIN rooms ro ON r.room_id = ro.id
|
|
WHERE r.reservation_date = $1
|
|
ORDER BY r.start_time
|
|
''', reservation_date)
|
|
return [dict(row) for row in rows]
|
|
|
|
@app.post("/reservations", response_model=Reservation)
|
|
async def create_reservation(reservation: ReservationCreate):
|
|
"""Créer une nouvelle réservation"""
|
|
async with pool.acquire() as conn:
|
|
# Vérifier que la salle existe
|
|
room = await conn.fetchrow('SELECT * FROM rooms WHERE id = $1', reservation.room_id)
|
|
if room is None:
|
|
raise HTTPException(status_code=404, detail="Salle non trouvée")
|
|
|
|
# Vérifier que l'heure de fin est après l'heure de début
|
|
if reservation.end_time <= reservation.start_time:
|
|
raise HTTPException(status_code=400, detail="L'heure de fin doit être après l'heure de début")
|
|
|
|
# Vérifier les conflits de réservation
|
|
conflicts = await conn.fetch('''
|
|
SELECT * FROM reservations
|
|
WHERE room_id = $1
|
|
AND reservation_date = $2
|
|
AND (
|
|
(start_time < $4 AND end_time > $3)
|
|
OR (start_time >= $3 AND start_time < $4)
|
|
)
|
|
''', reservation.room_id, reservation.reservation_date,
|
|
reservation.start_time, reservation.end_time)
|
|
|
|
if conflicts:
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail="La salle est déjà réservée pour ce créneau horaire"
|
|
)
|
|
|
|
# Créer la réservation
|
|
row = await conn.fetchrow('''
|
|
INSERT INTO reservations
|
|
(room_id, user_name, user_email, reservation_date, start_time, end_time, purpose, created_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING *
|
|
''', reservation.room_id, reservation.user_name, reservation.user_email,
|
|
reservation.reservation_date, reservation.start_time, reservation.end_time,
|
|
reservation.purpose, datetime.utcnow())
|
|
|
|
result = dict(row)
|
|
result['room_name'] = room['name']
|
|
return result
|
|
|
|
@app.delete("/reservations/{reservation_id}")
|
|
async def delete_reservation(reservation_id: int):
|
|
"""Annuler une réservation"""
|
|
async with pool.acquire() as conn:
|
|
result = await conn.execute('DELETE FROM reservations WHERE id = $1', reservation_id)
|
|
if result == "DELETE 0":
|
|
raise HTTPException(status_code=404, detail="Réservation non trouvée")
|
|
return {"message": "Réservation annulée avec succès"}
|
|
|