feat: Refactor API structure and implement core functionalities

- Updated requirements to include asyncpg, pyyaml, aiofiles, and httpx.
- Modified schema.sql to add 'tipo_pasto' column in 'pasti' table.
- Created .gitignore file to exclude unnecessary files and directories.
- Added README.md for API implementation details and directory structure.
- Introduced config.yaml for centralized configuration management.
- Implemented core modules for database management, authentication, and exception handling.
- Developed models for 'pietanze', 'pasti', and 'prenotazioni' with validation.
- Created routes for CRUD operations on 'pietanze' with pagination and filtering.
- Established logging and error handling mechanisms throughout the application.
- Set up FastAPI application with CORS and health check endpoint.
This commit is contained in:
Matteo Benedetto
2025-06-05 18:31:36 +02:00
parent a394ed9ad2
commit d592e0f32b
20 changed files with 1110 additions and 96 deletions
+1
View File
@@ -0,0 +1 @@
# Routes package initialization
+265
View File
@@ -0,0 +1,265 @@
from fastapi import APIRouter, Depends, HTTPException, Query, status
from typing import List, Optional, Dict, Any
import json
from datetime import datetime
from ..core.database import get_database, DatabaseManager
from ..models.pietanze import PietanzaCreate, PietanzaUpdate, PietanzaResponse
from ..models.common import ErrorResponse
from ..core.auth import get_current_user, get_current_admin_user
from ..core.exceptions import PietanzaNotFoundError, DatabaseError
router = APIRouter(prefix="/pietanze", tags=["Pietanze"])
@router.get("/", response_model=List[PietanzaResponse])
async def list_pietanze(
skip: int = Query(0, ge=0, description="Number of items to skip"),
limit: int = Query(20, ge=1, le=100, description="Number of items to return"),
search: Optional[str] = Query(None, description="Search in nome and descrizione"),
allergeni: Optional[str] = Query(None, description="Filter by allergens (comma-separated)"),
db: DatabaseManager = Depends(get_database)
):
"""Get list of available pietanze with optional filtering"""
try:
# Build query with filters
where_conditions = []
params = []
param_count = 0
if search:
param_count += 1
where_conditions.append(f"(nome ILIKE ${param_count} OR descrizione ILIKE ${param_count})")
params.append(f"%{search}%")
if allergeni:
allergen_list = [a.strip() for a in allergeni.split(",")]
param_count += 1
where_conditions.append(f"allergeni ?| ${param_count}")
params.append(allergen_list)
where_clause = ""
if where_conditions:
where_clause = "WHERE " + " AND ".join(where_conditions)
# Count total items
count_query = f"SELECT COUNT(*) FROM pietanze {where_clause}"
count_result = await db.execute_one(count_query, *params)
total = count_result[0] if count_result else 0
# Get items with pagination
param_count += 1
limit_param = param_count
param_count += 1
offset_param = param_count
query = f"""
SELECT id, nome, descrizione, allergeni, created_at, updated_at
FROM pietanze
{where_clause}
ORDER BY nome
LIMIT ${limit_param} OFFSET ${offset_param}
"""
params.extend([limit, skip])
rows = await db.execute_query(query, *params)
pietanze = []
for row in rows:
pietanze.append(PietanzaResponse(
id=row['id'],
nome=row['nome'],
descrizione=row['descrizione'],
allergeni=row['allergeni'] or [],
created_at=row['created_at'],
updated_at=row['updated_at']
))
return pietanze
except Exception as e:
raise DatabaseError(f"Failed to retrieve pietanze: {str(e)}")
@router.get("/{pietanza_id}", response_model=PietanzaResponse)
async def get_pietanza(
pietanza_id: int,
db: DatabaseManager = Depends(get_database)
):
"""Get specific pietanza by ID"""
try:
query = """
SELECT id, nome, descrizione, allergeni, created_at, updated_at
FROM pietanze
WHERE id = $1
"""
row = await db.execute_one(query, pietanza_id)
if not row:
raise PietanzaNotFoundError(pietanza_id)
return PietanzaResponse(
id=row['id'],
nome=row['nome'],
descrizione=row['descrizione'],
allergeni=row['allergeni'] or [],
created_at=row['created_at'],
updated_at=row['updated_at']
)
except PietanzaNotFoundError:
raise
except Exception as e:
raise DatabaseError(f"Failed to retrieve pietanza: {str(e)}")
@router.post("/", response_model=PietanzaResponse, status_code=status.HTTP_201_CREATED)
async def create_pietanza(
pietanza: PietanzaCreate,
current_user: Dict[str, Any] = Depends(get_current_admin_user),
db: DatabaseManager = Depends(get_database)
):
"""Create new pietanza (admin only)"""
try:
query = """
INSERT INTO pietanze (nome, descrizione, allergeni, created_at, updated_at)
VALUES ($1, $2, $3, $4, $4)
RETURNING id, nome, descrizione, allergeni, created_at, updated_at
"""
now = datetime.utcnow()
allergeni_json = json.dumps(pietanza.allergeni) if pietanza.allergeni else None
row = await db.execute_one(
query,
pietanza.nome,
pietanza.descrizione,
allergeni_json,
now
)
if not row:
raise DatabaseError("Failed to create pietanza")
return PietanzaResponse(
id=row['id'],
nome=row['nome'],
descrizione=row['descrizione'],
allergeni=row['allergeni'] or [],
created_at=row['created_at'],
updated_at=row['updated_at']
)
except Exception as e:
raise DatabaseError(f"Failed to create pietanza: {str(e)}")
@router.put("/{pietanza_id}", response_model=PietanzaResponse)
async def update_pietanza(
pietanza_id: int,
pietanza_update: PietanzaUpdate,
current_user: Dict[str, Any] = Depends(get_current_admin_user),
db: DatabaseManager = Depends(get_database)
):
"""Update existing pietanza (admin only)"""
try:
# Check if pietanza exists
existing = await db.execute_one("SELECT id FROM pietanze WHERE id = $1", pietanza_id)
if not existing:
raise PietanzaNotFoundError(pietanza_id)
# Build update query dynamically
update_fields = []
params = []
param_count = 0
if pietanza_update.nome is not None:
param_count += 1
update_fields.append(f"nome = ${param_count}")
params.append(pietanza_update.nome)
if pietanza_update.descrizione is not None:
param_count += 1
update_fields.append(f"descrizione = ${param_count}")
params.append(pietanza_update.descrizione)
if pietanza_update.allergeni is not None:
param_count += 1
update_fields.append(f"allergeni = ${param_count}")
params.append(json.dumps(pietanza_update.allergeni))
if not update_fields:
# No fields to update, return current pietanza
return await get_pietanza(pietanza_id, db)
# Add updated_at and pietanza_id
param_count += 1
update_fields.append(f"updated_at = ${param_count}")
params.append(datetime.utcnow())
param_count += 1
params.append(pietanza_id)
query = f"""
UPDATE pietanze
SET {', '.join(update_fields)}
WHERE id = ${param_count}
RETURNING id, nome, descrizione, allergeni, created_at, updated_at
"""
row = await db.execute_one(query, *params)
return PietanzaResponse(
id=row['id'],
nome=row['nome'],
descrizione=row['descrizione'],
allergeni=row['allergeni'] or [],
created_at=row['created_at'],
updated_at=row['updated_at']
)
except PietanzaNotFoundError:
raise
except Exception as e:
raise DatabaseError(f"Failed to update pietanza: {str(e)}")
@router.delete("/{pietanza_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_pietanza(
pietanza_id: int,
current_user: Dict[str, Any] = Depends(get_current_admin_user),
db: DatabaseManager = Depends(get_database)
):
"""Delete pietanza (admin only)"""
try:
# Check if pietanza exists
existing = await db.execute_one("SELECT id FROM pietanze WHERE id = $1", pietanza_id)
if not existing:
raise PietanzaNotFoundError(pietanza_id)
# Delete the pietanza
result = await db.execute_command("DELETE FROM pietanze WHERE id = $1", pietanza_id)
# Check if deletion was successful
if not result or not result.endswith("1"):
raise DatabaseError("Failed to delete pietanza")
except PietanzaNotFoundError:
raise
except Exception as e:
raise DatabaseError(f"Failed to delete pietanza: {str(e)}")
@router.get("/{pietanza_id}/allergeni", response_model=List[str])
async def get_pietanza_allergeni(
pietanza_id: int,
db: DatabaseManager = Depends(get_database)
):
"""Get allergen information for specific pietanza"""
try:
query = "SELECT allergeni FROM pietanze WHERE id = $1"
row = await db.execute_one(query, pietanza_id)
if not row:
raise PietanzaNotFoundError(pietanza_id)
return row['allergeni'] or []
except PietanzaNotFoundError:
raise
except Exception as e:
raise DatabaseError(f"Failed to retrieve allergeni: {str(e)}")