taw bch yekhdem
Browse files
app.py
CHANGED
|
@@ -18,12 +18,13 @@ Version: 1.0.0
|
|
| 18 |
|
| 19 |
import os
|
| 20 |
import sys
|
|
|
|
| 21 |
from datetime import datetime
|
| 22 |
from typing import Optional, List, Dict, Any, Union
|
| 23 |
import logging
|
| 24 |
|
| 25 |
# FastAPI imports
|
| 26 |
-
from fastapi import FastAPI, HTTPException, status
|
| 27 |
from fastapi.middleware.cors import CORSMiddleware
|
| 28 |
from fastapi.responses import JSONResponse
|
| 29 |
|
|
@@ -37,17 +38,8 @@ from bson import ObjectId
|
|
| 37 |
# Environment variables
|
| 38 |
from dotenv import load_dotenv
|
| 39 |
|
| 40 |
-
# Load environment variables from .env file
|
| 41 |
-
|
| 42 |
-
try:
|
| 43 |
-
load_dotenv('mongodb.env')
|
| 44 |
-
print("✅ Loaded environment variables from mongodb.env")
|
| 45 |
-
except:
|
| 46 |
-
try:
|
| 47 |
-
load_dotenv('.env')
|
| 48 |
-
print("✅ Loaded environment variables from .env")
|
| 49 |
-
except:
|
| 50 |
-
print("ℹ️ No .env file found, using system environment variables")
|
| 51 |
|
| 52 |
# Configure logging
|
| 53 |
logging.basicConfig(
|
|
@@ -65,16 +57,6 @@ MONGODB_URI = os.getenv('MONGODB_URI', 'mongodb://localhost:27017/audit_checklis
|
|
| 65 |
PORT = int(os.getenv('PORT', 8000)) # Hugging Face Spaces uses port 8000
|
| 66 |
CORS_ORIGIN = os.getenv('CORS_ORIGIN', '*') # Allow all origins by default for mobile apps
|
| 67 |
|
| 68 |
-
# Debug logging for environment variables
|
| 69 |
-
logger.info(f"MONGODB_URI: {'*' * 50 if MONGODB_URI.startswith('mongodb+srv://') else MONGODB_URI}")
|
| 70 |
-
logger.info(f"PORT: {PORT}")
|
| 71 |
-
logger.info(f"CORS_ORIGIN: {CORS_ORIGIN}")
|
| 72 |
-
|
| 73 |
-
# Check if we're using the default MongoDB URI (which means env var is not set)
|
| 74 |
-
if MONGODB_URI == 'mongodb://localhost:27017/audit_checklist':
|
| 75 |
-
logger.warning("⚠️ MONGODB_URI environment variable not set! Using default localhost connection.")
|
| 76 |
-
logger.warning("⚠️ Please set MONGODB_URI environment variable in Hugging Face Space settings.")
|
| 77 |
-
|
| 78 |
# Initialize FastAPI application
|
| 79 |
app = FastAPI(
|
| 80 |
title="Audit Checklist API",
|
|
@@ -214,6 +196,8 @@ class ChecklistData(BaseModel):
|
|
| 214 |
verificationDate: Date when the checklist was verified
|
| 215 |
createdAt: Timestamp when the checklist was created
|
| 216 |
updatedAt: Timestamp when the checklist was last updated
|
|
|
|
|
|
|
| 217 |
"""
|
| 218 |
userId: str = Field(..., description="Unique identifier for the user")
|
| 219 |
title: str = Field(..., description="Title of the checklist")
|
|
@@ -226,6 +210,8 @@ class ChecklistData(BaseModel):
|
|
| 226 |
createdAt: Optional[datetime] = Field(default=None, description="Creation timestamp")
|
| 227 |
updatedAt: Optional[datetime] = Field(default=None, description="Last update timestamp")
|
| 228 |
metadata: Optional[Metadata] = Field(default=None, description="Additional metadata including user information")
|
|
|
|
|
|
|
| 229 |
|
| 230 |
class Config:
|
| 231 |
# Allow extra fields that might be sent from frontend
|
|
@@ -304,30 +290,25 @@ def serialize_checklist(checklist_doc: Dict[str, Any]) -> Dict[str, Any]:
|
|
| 304 |
Returns:
|
| 305 |
Dictionary with ObjectId converted to string
|
| 306 |
"""
|
| 307 |
-
|
| 308 |
-
|
| 309 |
-
from bson import ObjectId
|
| 310 |
-
|
| 311 |
-
# Make a deep copy to avoid modifying the original document
|
| 312 |
-
serialized = copy.deepcopy(checklist_doc)
|
| 313 |
-
|
| 314 |
-
# Convert ObjectId to string recursively
|
| 315 |
-
def convert_objectid(obj):
|
| 316 |
-
if isinstance(obj, ObjectId):
|
| 317 |
-
return str(obj)
|
| 318 |
-
elif isinstance(obj, dict):
|
| 319 |
-
return {key: convert_objectid(value) for key, value in obj.items()}
|
| 320 |
-
elif isinstance(obj, list):
|
| 321 |
-
return [convert_objectid(item) for item in obj]
|
| 322 |
-
elif hasattr(obj, 'isoformat'): # datetime objects
|
| 323 |
-
return obj.isoformat()
|
| 324 |
-
else:
|
| 325 |
-
return obj
|
| 326 |
|
| 327 |
-
#
|
| 328 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 329 |
|
| 330 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 331 |
|
| 332 |
# =============================================================================
|
| 333 |
# API ENDPOINTS
|
|
@@ -425,8 +406,8 @@ async def get_checklist(user_id: str):
|
|
| 425 |
)
|
| 426 |
|
| 427 |
if not checklist_doc:
|
| 428 |
-
#
|
| 429 |
-
logger.info(f"No checklist found for user {user_id},
|
| 430 |
|
| 431 |
# Default checklist structure - Complete 38-item audit checklist
|
| 432 |
default_checklist = {
|
|
@@ -530,8 +511,10 @@ async def get_checklist(user_id: str):
|
|
| 530 |
"updatedAt": datetime.utcnow()
|
| 531 |
}
|
| 532 |
|
| 533 |
-
#
|
| 534 |
-
|
|
|
|
|
|
|
| 535 |
|
| 536 |
# Serialize the document
|
| 537 |
serialized_checklist = serialize_checklist(checklist_doc)
|
|
@@ -577,45 +560,76 @@ async def save_checklist(user_id: str, checklist_data: dict):
|
|
| 577 |
metrics = calculate_checklist_metrics(checklist_dict)
|
| 578 |
checklist_dict.update(metrics)
|
| 579 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 580 |
# Update timestamps
|
| 581 |
checklist_dict['updatedAt'] = datetime.utcnow()
|
| 582 |
|
| 583 |
-
#
|
| 584 |
-
|
| 585 |
-
session_id = f"{user_id}-{datetime.utcnow().strftime('%Y%m%d-%H%M%S')}"
|
| 586 |
-
checklist_dict['sessionId'] = session_id
|
| 587 |
|
| 588 |
-
|
| 589 |
-
|
| 590 |
-
checklist_dict['
|
| 591 |
-
|
| 592 |
-
|
| 593 |
-
|
| 594 |
-
|
| 595 |
-
|
| 596 |
-
|
| 597 |
-
|
| 598 |
-
|
| 599 |
-
# Remove _id if it exists to let MongoDB generate a new one
|
| 600 |
-
if '_id' in checklist_dict:
|
| 601 |
-
del checklist_dict['_id']
|
| 602 |
-
|
| 603 |
-
# Insert as a new document (always create new session)
|
| 604 |
-
result = await db.checklists.insert_one(checklist_dict)
|
| 605 |
-
|
| 606 |
-
if result.inserted_id:
|
| 607 |
-
logger.info(f"Created new checklist session for user {user_id}")
|
| 608 |
-
message = "Checklist session created successfully"
|
| 609 |
-
else:
|
| 610 |
-
logger.error(f"Failed to create checklist for user {user_id}")
|
| 611 |
-
raise HTTPException(
|
| 612 |
-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 613 |
-
detail="Failed to save checklist"
|
| 614 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 615 |
|
| 616 |
-
# Retrieve the
|
| 617 |
-
|
| 618 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 619 |
|
| 620 |
return ChecklistResponse(
|
| 621 |
success=True,
|
|
@@ -669,6 +683,943 @@ async def get_checklists_by_user_name(user_name: str):
|
|
| 669 |
detail=f"Failed to retrieve checklists for user: {str(e)}"
|
| 670 |
)
|
| 671 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 672 |
@app.get("/api/checklists", response_model=Dict[str, Any])
|
| 673 |
async def get_all_checklists():
|
| 674 |
"""
|
|
@@ -756,9 +1707,9 @@ if __name__ == "__main__":
|
|
| 756 |
|
| 757 |
# Run the server
|
| 758 |
uvicorn.run(
|
| 759 |
-
"
|
| 760 |
host="0.0.0.0",
|
| 761 |
port=PORT,
|
| 762 |
-
reload=
|
| 763 |
log_level="info"
|
| 764 |
)
|
|
|
|
| 18 |
|
| 19 |
import os
|
| 20 |
import sys
|
| 21 |
+
import base64
|
| 22 |
from datetime import datetime
|
| 23 |
from typing import Optional, List, Dict, Any, Union
|
| 24 |
import logging
|
| 25 |
|
| 26 |
# FastAPI imports
|
| 27 |
+
from fastapi import FastAPI, HTTPException, status, UploadFile, File
|
| 28 |
from fastapi.middleware.cors import CORSMiddleware
|
| 29 |
from fastapi.responses import JSONResponse
|
| 30 |
|
|
|
|
| 38 |
# Environment variables
|
| 39 |
from dotenv import load_dotenv
|
| 40 |
|
| 41 |
+
# Load environment variables from .env file
|
| 42 |
+
load_dotenv('mongodb.env')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
# Configure logging
|
| 45 |
logging.basicConfig(
|
|
|
|
| 57 |
PORT = int(os.getenv('PORT', 8000)) # Hugging Face Spaces uses port 8000
|
| 58 |
CORS_ORIGIN = os.getenv('CORS_ORIGIN', '*') # Allow all origins by default for mobile apps
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
# Initialize FastAPI application
|
| 61 |
app = FastAPI(
|
| 62 |
title="Audit Checklist API",
|
|
|
|
| 196 |
verificationDate: Date when the checklist was verified
|
| 197 |
createdAt: Timestamp when the checklist was created
|
| 198 |
updatedAt: Timestamp when the checklist was last updated
|
| 199 |
+
imageData: Base64 encoded image data (stored when compliance < 100%)
|
| 200 |
+
imageType: MIME type of the image (e.g., image/jpeg, image/png)
|
| 201 |
"""
|
| 202 |
userId: str = Field(..., description="Unique identifier for the user")
|
| 203 |
title: str = Field(..., description="Title of the checklist")
|
|
|
|
| 210 |
createdAt: Optional[datetime] = Field(default=None, description="Creation timestamp")
|
| 211 |
updatedAt: Optional[datetime] = Field(default=None, description="Last update timestamp")
|
| 212 |
metadata: Optional[Metadata] = Field(default=None, description="Additional metadata including user information")
|
| 213 |
+
imageData: Optional[str] = Field(default=None, description="Base64 encoded image data for non-compliant checklists")
|
| 214 |
+
imageType: Optional[str] = Field(default=None, description="MIME type of the image")
|
| 215 |
|
| 216 |
class Config:
|
| 217 |
# Allow extra fields that might be sent from frontend
|
|
|
|
| 290 |
Returns:
|
| 291 |
Dictionary with ObjectId converted to string
|
| 292 |
"""
|
| 293 |
+
if checklist_doc and '_id' in checklist_doc:
|
| 294 |
+
checklist_doc['_id'] = str(checklist_doc['_id'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
+
# Convert datetime objects to ISO strings
|
| 297 |
+
for field in ['createdAt', 'updatedAt', 'verificationDate']:
|
| 298 |
+
if field in checklist_doc and checklist_doc[field]:
|
| 299 |
+
# Only convert if it's a datetime object, not a string
|
| 300 |
+
if hasattr(checklist_doc[field], 'isoformat'):
|
| 301 |
+
checklist_doc[field] = checklist_doc[field].isoformat()
|
| 302 |
|
| 303 |
+
# Convert datetime objects in items
|
| 304 |
+
for section in checklist_doc.get('sections', []):
|
| 305 |
+
for item in section.get('items', []):
|
| 306 |
+
if 'checkedAt' in item and item['checkedAt']:
|
| 307 |
+
# Only convert if it's a datetime object, not a string
|
| 308 |
+
if hasattr(item['checkedAt'], 'isoformat'):
|
| 309 |
+
item['checkedAt'] = item['checkedAt'].isoformat()
|
| 310 |
+
|
| 311 |
+
return checklist_doc
|
| 312 |
|
| 313 |
# =============================================================================
|
| 314 |
# API ENDPOINTS
|
|
|
|
| 406 |
)
|
| 407 |
|
| 408 |
if not checklist_doc:
|
| 409 |
+
# Create new checklist if none exists
|
| 410 |
+
logger.info(f"No checklist found for user {user_id}, creating new one")
|
| 411 |
|
| 412 |
# Default checklist structure - Complete 38-item audit checklist
|
| 413 |
default_checklist = {
|
|
|
|
| 511 |
"updatedAt": datetime.utcnow()
|
| 512 |
}
|
| 513 |
|
| 514 |
+
# Insert the new checklist
|
| 515 |
+
result = await db.checklists.insert_one(default_checklist)
|
| 516 |
+
checklist_doc = await db.checklists.find_one({"_id": result.inserted_id})
|
| 517 |
+
logger.info(f"Created new checklist for user {user_id}")
|
| 518 |
|
| 519 |
# Serialize the document
|
| 520 |
serialized_checklist = serialize_checklist(checklist_doc)
|
|
|
|
| 560 |
metrics = calculate_checklist_metrics(checklist_dict)
|
| 561 |
checklist_dict.update(metrics)
|
| 562 |
|
| 563 |
+
# Handle image data for non-compliant checklists
|
| 564 |
+
compliance_score = metrics.get('complianceScore', 0.0)
|
| 565 |
+
if compliance_score < 100.0 and 'collectedImages' in checklist_dict and checklist_dict['collectedImages']:
|
| 566 |
+
# Process collected images from individual items
|
| 567 |
+
collected_images = checklist_dict['collectedImages']
|
| 568 |
+
logger.info(f"Storing {len(collected_images)} images for non-compliant checklist (score: {compliance_score}%)")
|
| 569 |
+
|
| 570 |
+
# Store the collected images in the checklist data
|
| 571 |
+
checklist_dict['imageData'] = collected_images
|
| 572 |
+
checklist_dict['imageType'] = 'multiple_items'
|
| 573 |
+
elif compliance_score < 100.0:
|
| 574 |
+
logger.info(f"No image data provided for non-compliant checklist (score: {compliance_score}%)")
|
| 575 |
+
|
| 576 |
# Update timestamps
|
| 577 |
checklist_dict['updatedAt'] = datetime.utcnow()
|
| 578 |
|
| 579 |
+
# Check if this is an update to existing checklist or new save
|
| 580 |
+
existing_checklist = await db.checklists.find_one({"userId": user_id})
|
|
|
|
|
|
|
| 581 |
|
| 582 |
+
if existing_checklist:
|
| 583 |
+
# Update existing checklist
|
| 584 |
+
checklist_dict['createdAt'] = existing_checklist.get('createdAt', datetime.utcnow())
|
| 585 |
+
|
| 586 |
+
# Remove _id if it exists to prevent immutable field error
|
| 587 |
+
if '_id' in checklist_dict:
|
| 588 |
+
del checklist_dict['_id']
|
| 589 |
+
|
| 590 |
+
result = await db.checklists.update_one(
|
| 591 |
+
{"userId": user_id},
|
| 592 |
+
{"$set": checklist_dict}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 593 |
)
|
| 594 |
+
|
| 595 |
+
if result.modified_count > 0:
|
| 596 |
+
logger.info(f"Updated existing checklist for user {user_id}")
|
| 597 |
+
message = "Checklist updated successfully"
|
| 598 |
+
else:
|
| 599 |
+
logger.error(f"Failed to update checklist for user {user_id}")
|
| 600 |
+
raise HTTPException(
|
| 601 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 602 |
+
detail="Failed to update checklist"
|
| 603 |
+
)
|
| 604 |
+
else:
|
| 605 |
+
# Create new checklist
|
| 606 |
+
checklist_dict['createdAt'] = datetime.utcnow()
|
| 607 |
+
|
| 608 |
+
# Remove _id if it exists to let MongoDB generate a new one
|
| 609 |
+
if '_id' in checklist_dict:
|
| 610 |
+
del checklist_dict['_id']
|
| 611 |
+
|
| 612 |
+
result = await db.checklists.insert_one(checklist_dict)
|
| 613 |
+
|
| 614 |
+
if result.inserted_id:
|
| 615 |
+
logger.info(f"Created new checklist for user {user_id}")
|
| 616 |
+
message = "Checklist created successfully"
|
| 617 |
+
else:
|
| 618 |
+
logger.error(f"Failed to create checklist for user {user_id}")
|
| 619 |
+
raise HTTPException(
|
| 620 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 621 |
+
detail="Failed to save checklist"
|
| 622 |
+
)
|
| 623 |
|
| 624 |
+
# Retrieve the updated/created checklist
|
| 625 |
+
if existing_checklist:
|
| 626 |
+
# For updates, get the updated document
|
| 627 |
+
updated_checklist = await db.checklists.find_one({"userId": user_id})
|
| 628 |
+
serialized_checklist = serialize_checklist(updated_checklist)
|
| 629 |
+
else:
|
| 630 |
+
# For new checklists, get the newly created document
|
| 631 |
+
created_checklist = await db.checklists.find_one({"_id": result.inserted_id})
|
| 632 |
+
serialized_checklist = serialize_checklist(created_checklist)
|
| 633 |
|
| 634 |
return ChecklistResponse(
|
| 635 |
success=True,
|
|
|
|
| 683 |
detail=f"Failed to retrieve checklists for user: {str(e)}"
|
| 684 |
)
|
| 685 |
|
| 686 |
+
@app.post("/api/checklist/{user_id}/image")
|
| 687 |
+
async def upload_checklist_image(user_id: str, image: UploadFile = File(...)):
|
| 688 |
+
"""
|
| 689 |
+
Upload an image for a checklist (for non-compliant cases)
|
| 690 |
+
|
| 691 |
+
Args:
|
| 692 |
+
user_id: Unique identifier for the user
|
| 693 |
+
image: The image file to upload
|
| 694 |
+
|
| 695 |
+
Returns:
|
| 696 |
+
Dictionary containing the base64 encoded image data and metadata
|
| 697 |
+
"""
|
| 698 |
+
try:
|
| 699 |
+
logger.info(f"Uploading image for user: {user_id}")
|
| 700 |
+
|
| 701 |
+
# Validate image file type
|
| 702 |
+
allowed_types = ["image/jpeg", "image/jpg", "image/png", "image/gif", "image/webp"]
|
| 703 |
+
if image.content_type not in allowed_types:
|
| 704 |
+
raise HTTPException(
|
| 705 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 706 |
+
detail=f"Invalid image type. Allowed types: {', '.join(allowed_types)}"
|
| 707 |
+
)
|
| 708 |
+
|
| 709 |
+
# Read image data
|
| 710 |
+
image_data = await image.read()
|
| 711 |
+
|
| 712 |
+
# Encode to base64
|
| 713 |
+
base64_data = base64.b64encode(image_data).decode('utf-8')
|
| 714 |
+
|
| 715 |
+
# Create data URL
|
| 716 |
+
data_url = f"data:{image.content_type};base64,{base64_data}"
|
| 717 |
+
|
| 718 |
+
logger.info(f"Image uploaded successfully for user {user_id}, size: {len(image_data)} bytes")
|
| 719 |
+
|
| 720 |
+
return {
|
| 721 |
+
"success": True,
|
| 722 |
+
"data": {
|
| 723 |
+
"imageData": base64_data,
|
| 724 |
+
"imageType": image.content_type,
|
| 725 |
+
"dataUrl": data_url,
|
| 726 |
+
"size": len(image_data),
|
| 727 |
+
"filename": image.filename
|
| 728 |
+
},
|
| 729 |
+
"message": "Image uploaded successfully"
|
| 730 |
+
}
|
| 731 |
+
|
| 732 |
+
except HTTPException:
|
| 733 |
+
raise
|
| 734 |
+
except Exception as e:
|
| 735 |
+
logger.error(f"Error uploading image for user {user_id}: {e}")
|
| 736 |
+
raise HTTPException(
|
| 737 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 738 |
+
detail=f"Failed to upload image: {str(e)}"
|
| 739 |
+
)
|
| 740 |
+
|
| 741 |
+
async def ensure_default_template_exists():
|
| 742 |
+
"""
|
| 743 |
+
Ensure the default template exists in the database.
|
| 744 |
+
Creates it if it doesn't exist.
|
| 745 |
+
"""
|
| 746 |
+
try:
|
| 747 |
+
logger.info("Checking if default template exists...")
|
| 748 |
+
# Check if template exists (check both old and new IDs)
|
| 749 |
+
template = await db.checklist_templates.find_one({
|
| 750 |
+
"$or": [
|
| 751 |
+
{"templateId": "default"},
|
| 752 |
+
{"templateId": "default-audit-checklist"}
|
| 753 |
+
]
|
| 754 |
+
})
|
| 755 |
+
|
| 756 |
+
if not template:
|
| 757 |
+
logger.info("Default template not found, creating it now...")
|
| 758 |
+
|
| 759 |
+
# Create the default template
|
| 760 |
+
default_template = {
|
| 761 |
+
"templateId": "default",
|
| 762 |
+
"title": "Checklist di Audit Operativo",
|
| 763 |
+
"sections": [
|
| 764 |
+
{
|
| 765 |
+
"id": "S1",
|
| 766 |
+
"title": "1. PERSONALE E IGIENE",
|
| 767 |
+
"icon": "Users",
|
| 768 |
+
"items": [
|
| 769 |
+
{"id": "I1.1", "requirement": "Indumenti da lavoro puliti (divisa, grembiule)."},
|
| 770 |
+
{"id": "I1.2", "requirement": "Scarpe antinfortunistiche / Calzature pulite."},
|
| 771 |
+
{"id": "I1.3", "requirement": "Cuffie e/o Retine per capelli indossate correttamente."},
|
| 772 |
+
{"id": "I1.4", "requirement": "Assenza di gioielli, piercing visibili, unghie lunghe."},
|
| 773 |
+
{"id": "I1.5", "requirement": "Lavaggio mani documentato all'ingresso/reingresso."},
|
| 774 |
+
{"id": "I1.6", "requirement": "Assenza di cibo/bevande non autorizzate nelle aree produttive."},
|
| 775 |
+
],
|
| 776 |
+
},
|
| 777 |
+
{
|
| 778 |
+
"id": "S2",
|
| 779 |
+
"title": "2. STRUTTURE E IMPIANTI",
|
| 780 |
+
"icon": "Building",
|
| 781 |
+
"items": [
|
| 782 |
+
{"id": "I2.1", "requirement": "Illuminazione adeguata e funzionante in tutte le aree."},
|
| 783 |
+
{"id": "I2.2", "requirement": "Porte esterne/interne in buone condizioni e chiuse correttamente (sigillatura)."},
|
| 784 |
+
{"id": "I2.3", "requirement": "Integrità di pavimenti, pareti e soffitti (assenza di crepe/danni)."},
|
| 785 |
+
{"id": "I2.4", "requirement": "Controllo vetri / lampade protette o anti-frantumazione."},
|
| 786 |
+
{"id": "I2.5", "requirement": "Condizioni igieniche dei servizi igienici e spogliatoi (pulizia e dotazioni)."},
|
| 787 |
+
{"id": "I2.6", "requirement": "Ventilazione e aspirazione funzionanti, pulite e non ostruite."},
|
| 788 |
+
],
|
| 789 |
+
},
|
| 790 |
+
{
|
| 791 |
+
"id": "S3",
|
| 792 |
+
"title": "3. GESTIONE E IGIENE AMBIENTALE",
|
| 793 |
+
"icon": "Package",
|
| 794 |
+
"items": [
|
| 795 |
+
{"id": "I3.1", "requirement": "Contenitori dei rifiuti puliti, chiusi e identificati."},
|
| 796 |
+
{"id": "I3.2", "requirement": "Separazione corretta dei rifiuti (es. umido, secco, plastica)."},
|
| 797 |
+
{"id": "I3.3", "requirement": "Area di stoccaggio rifiuti (interna ed esterna) ordinata e sanificata."},
|
| 798 |
+
{"id": "I3.4", "requirement": "Frequenza di rimozione dei rifiuti adeguata a prevenire accumuli."},
|
| 799 |
+
],
|
| 800 |
+
},
|
| 801 |
+
{
|
| 802 |
+
"id": "S4",
|
| 803 |
+
"title": "4. CONTROLLO PROCESSO E QUALITÀ",
|
| 804 |
+
"icon": "Settings",
|
| 805 |
+
"items": [
|
| 806 |
+
{"id": "I4.1", "requirement": "Monitoraggio e registrazione dei Punti Critici di Controllo (CCP)."},
|
| 807 |
+
{"id": "I4.2", "requirement": "Procedure di Buona Fabbricazione (GMP) rispettate (es. pulizia prima dell'avvio)."},
|
| 808 |
+
{"id": "I4.3", "requirement": "Verifica e calibrazione del Metal Detector eseguita e registrata."},
|
| 809 |
+
{"id": "I4.4", "requirement": "Corretta identificazione dei lotti di materie prime e semilavorati."},
|
| 810 |
+
{"id": "I4.5", "requirement": "Rispettate le temperature di stoccaggio dei prodotti finiti e intermedi."},
|
| 811 |
+
{"id": "I4.6", "requirement": "Corretta gestione, etichettatura e isolamento dei prodotti non conformi."},
|
| 812 |
+
],
|
| 813 |
+
},
|
| 814 |
+
{
|
| 815 |
+
"id": "S5",
|
| 816 |
+
"title": "5. CONTROLLO INFESTANTI",
|
| 817 |
+
"icon": "Shield",
|
| 818 |
+
"items": [
|
| 819 |
+
{"id": "I5.1", "requirement": "Assenza di tracce visibili di roditori, insetti o uccelli."},
|
| 820 |
+
{"id": "I5.2", "requirement": "Stazioni di monitoraggio/trappole numerate, integre e registrate."},
|
| 821 |
+
{"id": "I5.3", "requirement": "Mappe e registri delle trappole aggiornati e disponibili."},
|
| 822 |
+
{"id": "I5.4", "requirement": "Barriere fisiche (es. reti, spazzole) anti-intrusione in buone condizioni."},
|
| 823 |
+
{"id": "I5.5", "requirement": "Prodotti chimici di controllo infestanti stoccati in modo sicuro e separato."},
|
| 824 |
+
],
|
| 825 |
+
},
|
| 826 |
+
{
|
| 827 |
+
"id": "S6",
|
| 828 |
+
"title": "6. MANUTENZIONE E VETRI",
|
| 829 |
+
"icon": "Settings",
|
| 830 |
+
"items": [
|
| 831 |
+
{"id": "I6.1", "requirement": "Piano di manutenzione preventiva e correttiva rispettato e registrato."},
|
| 832 |
+
{"id": "I6.2", "requirement": "Lubrificanti e oli utilizzati autorizzati per uso alimentare (se richiesto)."},
|
| 833 |
+
{"id": "I6.3", "requirement": "Registri di calibrazione/verifica degli strumenti di misura critici aggiornati."},
|
| 834 |
+
{"id": "I6.4", "requirement": "Gestione dei vetri rotti (registri rotture) aggiornata e conforme alla procedura."},
|
| 835 |
+
{"id": "I6.5", "requirement": "Assenza di perdite, gocciolamenti o cavi scoperti da impianti/macchinari."},
|
| 836 |
+
{"id": "I6.6", "requirement": "Area di deposito utensili e pezzi di ricambio ordinata e pulita."},
|
| 837 |
+
],
|
| 838 |
+
},
|
| 839 |
+
{
|
| 840 |
+
"id": "S7",
|
| 841 |
+
"title": "7. DOCUMENTAZIONE E FORMAZIONE",
|
| 842 |
+
"icon": "FileText",
|
| 843 |
+
"items": [
|
| 844 |
+
{"id": "I7.1", "requirement": "Documentazione di processo (procedure, istruzioni) aggiornata e disponibile."},
|
| 845 |
+
{"id": "I7.2", "requirement": "Registri di formazione del personale aggiornati e verificabili."},
|
| 846 |
+
{"id": "I7.3", "requirement": "Certificazioni e attestati del personale validi e aggiornati."},
|
| 847 |
+
{"id": "I7.4", "requirement": "Controllo documenti (versioni, distribuzione, obsolescenza) conforme."},
|
| 848 |
+
{"id": "I7.5", "requirement": "Archiviazione e conservazione documenti secondo i requisiti normativi."},
|
| 849 |
+
],
|
| 850 |
+
},
|
| 851 |
+
],
|
| 852 |
+
"metadata": {
|
| 853 |
+
"createdAt": datetime.utcnow().isoformat(),
|
| 854 |
+
"updatedAt": datetime.utcnow().isoformat(),
|
| 855 |
+
}
|
| 856 |
+
}
|
| 857 |
+
|
| 858 |
+
result = await db.checklist_templates.insert_one(default_template)
|
| 859 |
+
logger.info(f"✅ Successfully created default template with ID: {result.inserted_id}")
|
| 860 |
+
# Fetch the newly created template to return with _id
|
| 861 |
+
template = await db.checklist_templates.find_one({"templateId": "default"})
|
| 862 |
+
return template
|
| 863 |
+
else:
|
| 864 |
+
logger.info("✅ Default template already exists in database")
|
| 865 |
+
return template
|
| 866 |
+
|
| 867 |
+
except Exception as e:
|
| 868 |
+
logger.error(f"❌ Error ensuring default template exists: {e}", exc_info=True)
|
| 869 |
+
return None
|
| 870 |
+
|
| 871 |
+
@app.get("/api/checklist-template/default", response_model=Dict[str, Any])
|
| 872 |
+
async def get_default_checklist_template():
|
| 873 |
+
"""
|
| 874 |
+
Get the default checklist template
|
| 875 |
+
|
| 876 |
+
Returns:
|
| 877 |
+
Dictionary containing the default checklist template structure
|
| 878 |
+
|
| 879 |
+
Raises:
|
| 880 |
+
HTTPException: If template not found or database operation fails
|
| 881 |
+
"""
|
| 882 |
+
try:
|
| 883 |
+
logger.info("📋 GET /api/checklist-template/default - Retrieving default checklist template")
|
| 884 |
+
|
| 885 |
+
# Ensure template exists (creates if not found)
|
| 886 |
+
template_doc = await ensure_default_template_exists()
|
| 887 |
+
logger.info(f"Template check result: {'Found' if template_doc else 'Not Found'}")
|
| 888 |
+
|
| 889 |
+
if not template_doc:
|
| 890 |
+
# Fallback to looking for old template ID
|
| 891 |
+
logger.info("Checking for old template ID: default-audit-checklist")
|
| 892 |
+
template_doc = await db.checklist_templates.find_one({"templateId": "default-audit-checklist"})
|
| 893 |
+
|
| 894 |
+
if not template_doc:
|
| 895 |
+
# If template doesn't exist, create it from the hardcoded structure
|
| 896 |
+
logger.info("Default template not found, creating from hardcoded structure")
|
| 897 |
+
|
| 898 |
+
# Use the same structure as in get_checklist but mark as template
|
| 899 |
+
default_template = {
|
| 900 |
+
"templateId": "default-audit-checklist",
|
| 901 |
+
"title": "Checklist di Audit Operativo (38 Controlli)",
|
| 902 |
+
"description": "Template per audit operativo in ambiente di produzione alimentare",
|
| 903 |
+
"version": "1.0",
|
| 904 |
+
"isTemplate": True,
|
| 905 |
+
"createdAt": datetime.utcnow(),
|
| 906 |
+
"updatedAt": datetime.utcnow(),
|
| 907 |
+
"sections": [
|
| 908 |
+
{
|
| 909 |
+
"id": "S1",
|
| 910 |
+
"title": "1. PERSONALE E IGIENE",
|
| 911 |
+
"icon": "Users",
|
| 912 |
+
"items": [
|
| 913 |
+
{"id": "I1.1", "requirement": "Indumenti da lavoro puliti (divisa, grembiule).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 914 |
+
{"id": "I1.2", "requirement": "Scarpe antinfortunistiche / Calzature pulite.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 915 |
+
{"id": "I1.3", "requirement": "Cuffie e/o Retine per capelli indossate correttamente.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 916 |
+
{"id": "I1.4", "requirement": "Assenza di gioielli, piercing visibili, unghie lunghe.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 917 |
+
{"id": "I1.5", "requirement": "Lavaggio mani documentato all'ingresso/reingresso.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 918 |
+
{"id": "I1.6", "requirement": "Assenza di cibo/bevande non autorizzate nelle aree produttive.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 919 |
+
]
|
| 920 |
+
},
|
| 921 |
+
{
|
| 922 |
+
"id": "S2",
|
| 923 |
+
"title": "2. STRUTTURE E IMPIANTI",
|
| 924 |
+
"icon": "Building",
|
| 925 |
+
"items": [
|
| 926 |
+
{"id": "I2.1", "requirement": "Illuminazione adeguata e funzionante in tutte le aree.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 927 |
+
{"id": "I2.2", "requirement": "Porte esterne/interne in buone condizioni e chiuse correttamente (sigillatura).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 928 |
+
{"id": "I2.3", "requirement": "Integrità di pavimenti, pareti e soffitti (assenza di crepe/danni).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 929 |
+
{"id": "I2.4", "requirement": "Controllo vetri / lampade protette o anti-frantumazione.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 930 |
+
{"id": "I2.5", "requirement": "Condizioni igieniche dei servizi igienici e spogliatoi (pulizia e dotazioni).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 931 |
+
{"id": "I2.6", "requirement": "Ventilazione e aspirazione funzionanti, pulite e non ostruite.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 932 |
+
]
|
| 933 |
+
},
|
| 934 |
+
{
|
| 935 |
+
"id": "S3",
|
| 936 |
+
"title": "3. GESTIONE E IGIENE AMBIENTALE",
|
| 937 |
+
"icon": "Package",
|
| 938 |
+
"items": [
|
| 939 |
+
{"id": "I3.1", "requirement": "Contenitori dei rifiuti puliti, chiusi e identificati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 940 |
+
{"id": "I3.2", "requirement": "Separazione corretta dei rifiuti (es. umido, secco, plastica).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 941 |
+
{"id": "I3.3", "requirement": "Area di stoccaggio rifiuti (interna ed esterna) ordinata e sanificata.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 942 |
+
{"id": "I3.4", "requirement": "Frequenza di rimozione dei rifiuti adeguata a prevenire accumuli.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 943 |
+
]
|
| 944 |
+
},
|
| 945 |
+
{
|
| 946 |
+
"id": "S4",
|
| 947 |
+
"title": "4. CONTROLLO PROCESSO E QUALITÀ",
|
| 948 |
+
"icon": "Settings",
|
| 949 |
+
"items": [
|
| 950 |
+
{"id": "I4.1", "requirement": "Monitoraggio e registrazione dei Punti Critici di Controllo (CCP).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 951 |
+
{"id": "I4.2", "requirement": "Procedure di Buona Fabbricazione (GMP) rispettate (es. pulizia prima dell'avvio).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 952 |
+
{"id": "I4.3", "requirement": "Verifica e calibrazione del Metal Detector eseguita e registrata.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 953 |
+
{"id": "I4.4", "requirement": "Corretta identificazione dei lotti di materie prime e semilavorati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 954 |
+
{"id": "I4.5", "requirement": "Rispettate le temperature di stoccaggio dei prodotti finiti e intermedi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 955 |
+
{"id": "I4.6", "requirement": "Corretta gestione, etichettatura e isolamento dei prodotti non conformi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 956 |
+
]
|
| 957 |
+
},
|
| 958 |
+
{
|
| 959 |
+
"id": "S5",
|
| 960 |
+
"title": "5. CONTROLLO INFESTANTI",
|
| 961 |
+
"icon": "Shield",
|
| 962 |
+
"items": [
|
| 963 |
+
{"id": "I5.1", "requirement": "Assenza di tracce visibili di roditori, insetti o uccelli.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 964 |
+
{"id": "I5.2", "requirement": "Stazioni di monitoraggio/trappole numerate, integre e registrate.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 965 |
+
{"id": "I5.3", "requirement": "Mappe e registri delle trappole aggiornati e disponibili.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 966 |
+
{"id": "I5.4", "requirement": "Barriere fisiche (es. reti, spazzole) anti-intrusione in buone condizioni.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 967 |
+
{"id": "I5.5", "requirement": "Prodotti chimici di controllo infestanti stoccati in modo sicuro e separato.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 968 |
+
]
|
| 969 |
+
},
|
| 970 |
+
{
|
| 971 |
+
"id": "S6",
|
| 972 |
+
"title": "6. MANUTENZIONE E VETRI",
|
| 973 |
+
"icon": "Settings",
|
| 974 |
+
"items": [
|
| 975 |
+
{"id": "I6.1", "requirement": "Piano di manutenzione preventiva e correttiva rispettato e registrato.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 976 |
+
{"id": "I6.2", "requirement": "Lubrificanti e oli utilizzati autorizzati per uso alimentare (se richiesto).", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 977 |
+
{"id": "I6.3", "requirement": "Registri di calibrazione/verifica degli strumenti di misura critici aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 978 |
+
{"id": "I6.4", "requirement": "Gestione dei vetri rotti (registri rotture) aggiornata e conforme alla procedura.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 979 |
+
{"id": "I6.5", "requirement": "Assenza di perdite, gocciolamenti o cavi scoperti da impianti/macchinari.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 980 |
+
{"id": "I6.6", "requirement": "Area di deposito utensili e pezzi di ricambio ordinata e pulita.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 981 |
+
]
|
| 982 |
+
},
|
| 983 |
+
{
|
| 984 |
+
"id": "S7",
|
| 985 |
+
"title": "7. DOCUMENTAZIONE E FORMAZIONE",
|
| 986 |
+
"icon": "FileText",
|
| 987 |
+
"items": [
|
| 988 |
+
{"id": "I7.1", "requirement": "Documentazione di processo (procedure, istruzioni) aggiornata e disponibile.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 989 |
+
{"id": "I7.2", "requirement": "Registri di formazione del personale aggiornati e verificabili.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 990 |
+
{"id": "I7.3", "requirement": "Certificazioni e attestati del personale validi e aggiornati.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 991 |
+
{"id": "I7.4", "requirement": "Controllo documenti (versioni, distribuzione, obsolescenza) conforme.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None},
|
| 992 |
+
{"id": "I7.5", "requirement": "Archiviazione e conservazione documenti secondo i requisiti normativi.", "compliance": "N/A", "deviation": "", "action": "", "imageData": None}
|
| 993 |
+
]
|
| 994 |
+
}
|
| 995 |
+
],
|
| 996 |
+
"totalItems": 38,
|
| 997 |
+
"completedItems": 0,
|
| 998 |
+
"nonCompliantItems": 0,
|
| 999 |
+
"complianceScore": 0.0
|
| 1000 |
+
}
|
| 1001 |
+
|
| 1002 |
+
# Insert the template
|
| 1003 |
+
await db.checklist_templates.insert_one(default_template)
|
| 1004 |
+
template_doc = default_template
|
| 1005 |
+
|
| 1006 |
+
# Serialize the document
|
| 1007 |
+
serialized_template = serialize_checklist(template_doc)
|
| 1008 |
+
|
| 1009 |
+
return {
|
| 1010 |
+
"success": True,
|
| 1011 |
+
"data": serialized_template,
|
| 1012 |
+
"message": "Default checklist template retrieved successfully"
|
| 1013 |
+
}
|
| 1014 |
+
|
| 1015 |
+
except Exception as e:
|
| 1016 |
+
logger.error(f"Error retrieving default checklist template: {e}")
|
| 1017 |
+
raise HTTPException(
|
| 1018 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1019 |
+
detail=f"Failed to retrieve checklist template: {str(e)}"
|
| 1020 |
+
)
|
| 1021 |
+
|
| 1022 |
+
@app.post("/api/checklist-template/item", response_model=Dict[str, Any])
|
| 1023 |
+
async def add_template_item(item_data: dict):
|
| 1024 |
+
"""
|
| 1025 |
+
Add a new item to the shared checklist template
|
| 1026 |
+
|
| 1027 |
+
Args:
|
| 1028 |
+
item_data: Dictionary containing item information (sectionId, requirement, etc.)
|
| 1029 |
+
|
| 1030 |
+
Returns:
|
| 1031 |
+
Dictionary confirming the item was added
|
| 1032 |
+
|
| 1033 |
+
Raises:
|
| 1034 |
+
HTTPException: If database operation fails
|
| 1035 |
+
"""
|
| 1036 |
+
try:
|
| 1037 |
+
logger.info(f"Adding new item to shared template")
|
| 1038 |
+
|
| 1039 |
+
# Ensure template exists first and get it
|
| 1040 |
+
template = await ensure_default_template_exists()
|
| 1041 |
+
|
| 1042 |
+
if not template:
|
| 1043 |
+
raise HTTPException(
|
| 1044 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1045 |
+
detail="Template not found"
|
| 1046 |
+
)
|
| 1047 |
+
|
| 1048 |
+
template_id = template.get("templateId", "default")
|
| 1049 |
+
|
| 1050 |
+
# Find the section and add the new item
|
| 1051 |
+
section_id = item_data.get('sectionId')
|
| 1052 |
+
section_index = next((i for i, s in enumerate(template['sections']) if s['id'] == section_id), None)
|
| 1053 |
+
|
| 1054 |
+
if section_index is None:
|
| 1055 |
+
raise HTTPException(
|
| 1056 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1057 |
+
detail=f"Section {section_id} not found"
|
| 1058 |
+
)
|
| 1059 |
+
|
| 1060 |
+
new_item = {
|
| 1061 |
+
"id": item_data.get('id', f"I{section_id[-1]}.{len(template['sections'][section_index]['items'])+1}"),
|
| 1062 |
+
"requirement": item_data.get('requirement', ''),
|
| 1063 |
+
}
|
| 1064 |
+
|
| 1065 |
+
# Update the template
|
| 1066 |
+
result = await db.checklist_templates.update_one(
|
| 1067 |
+
{"templateId": template_id, "sections.id": section_id},
|
| 1068 |
+
{"$push": {"sections.$.items": new_item}}
|
| 1069 |
+
)
|
| 1070 |
+
|
| 1071 |
+
if result.modified_count > 0:
|
| 1072 |
+
logger.info(f"Successfully added item to template")
|
| 1073 |
+
return {
|
| 1074 |
+
"success": True,
|
| 1075 |
+
"message": "Item added successfully",
|
| 1076 |
+
"data": new_item
|
| 1077 |
+
}
|
| 1078 |
+
else:
|
| 1079 |
+
raise HTTPException(
|
| 1080 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1081 |
+
detail="Failed to add item"
|
| 1082 |
+
)
|
| 1083 |
+
|
| 1084 |
+
except HTTPException:
|
| 1085 |
+
raise
|
| 1086 |
+
except Exception as e:
|
| 1087 |
+
logger.error(f"Error adding item to template: {e}")
|
| 1088 |
+
raise HTTPException(
|
| 1089 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1090 |
+
detail=f"Failed to add item: {str(e)}"
|
| 1091 |
+
)
|
| 1092 |
+
|
| 1093 |
+
@app.put("/api/checklist-template/item/{item_id}", response_model=Dict[str, Any])
|
| 1094 |
+
async def update_template_item(item_id: str, item_data: dict):
|
| 1095 |
+
"""
|
| 1096 |
+
Update an existing item in the shared checklist template
|
| 1097 |
+
|
| 1098 |
+
Args:
|
| 1099 |
+
item_id: ID of the item to update
|
| 1100 |
+
item_data: Dictionary containing updated item information
|
| 1101 |
+
|
| 1102 |
+
Returns:
|
| 1103 |
+
Dictionary confirming the item was updated
|
| 1104 |
+
|
| 1105 |
+
Raises:
|
| 1106 |
+
HTTPException: If database operation fails
|
| 1107 |
+
"""
|
| 1108 |
+
try:
|
| 1109 |
+
logger.info(f"Updating item {item_id} in shared template")
|
| 1110 |
+
|
| 1111 |
+
# Ensure template exists first and get it
|
| 1112 |
+
template = await ensure_default_template_exists()
|
| 1113 |
+
template_id = template.get("templateId") if template else "default"
|
| 1114 |
+
|
| 1115 |
+
# Prepare update fields
|
| 1116 |
+
update_fields = {}
|
| 1117 |
+
if 'requirement' in item_data:
|
| 1118 |
+
update_fields["sections.$[].items.$[item].requirement"] = item_data['requirement']
|
| 1119 |
+
|
| 1120 |
+
if not update_fields:
|
| 1121 |
+
raise HTTPException(
|
| 1122 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 1123 |
+
detail="No valid fields to update"
|
| 1124 |
+
)
|
| 1125 |
+
|
| 1126 |
+
# Update the item in template (use the template ID we found)
|
| 1127 |
+
result = await db.checklist_templates.update_one(
|
| 1128 |
+
{"templateId": template_id},
|
| 1129 |
+
{"$set": update_fields},
|
| 1130 |
+
array_filters=[{"item.id": item_id}]
|
| 1131 |
+
)
|
| 1132 |
+
|
| 1133 |
+
if result.modified_count > 0:
|
| 1134 |
+
logger.info(f"Successfully updated item {item_id} in template")
|
| 1135 |
+
return {
|
| 1136 |
+
"success": True,
|
| 1137 |
+
"message": "Item updated successfully",
|
| 1138 |
+
"data": item_data
|
| 1139 |
+
}
|
| 1140 |
+
else:
|
| 1141 |
+
raise HTTPException(
|
| 1142 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1143 |
+
detail="Item not found or no changes made"
|
| 1144 |
+
)
|
| 1145 |
+
|
| 1146 |
+
except HTTPException:
|
| 1147 |
+
raise
|
| 1148 |
+
except Exception as e:
|
| 1149 |
+
logger.error(f"Error updating item {item_id} in template: {e}")
|
| 1150 |
+
raise HTTPException(
|
| 1151 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1152 |
+
detail=f"Failed to update item: {str(e)}"
|
| 1153 |
+
)
|
| 1154 |
+
|
| 1155 |
+
@app.delete("/api/checklist-template/item/{item_id}", response_model=Dict[str, Any])
|
| 1156 |
+
async def delete_template_item(item_id: str):
|
| 1157 |
+
"""
|
| 1158 |
+
Delete an item from the shared checklist template
|
| 1159 |
+
|
| 1160 |
+
Args:
|
| 1161 |
+
item_id: ID of the item to delete
|
| 1162 |
+
|
| 1163 |
+
Returns:
|
| 1164 |
+
Dictionary confirming the item was deleted
|
| 1165 |
+
|
| 1166 |
+
Raises:
|
| 1167 |
+
HTTPException: If database operation fails
|
| 1168 |
+
"""
|
| 1169 |
+
try:
|
| 1170 |
+
logger.info(f"Deleting item {item_id} from shared template")
|
| 1171 |
+
|
| 1172 |
+
# Ensure template exists first and get it
|
| 1173 |
+
template = await ensure_default_template_exists()
|
| 1174 |
+
template_id = template.get("templateId") if template else "default"
|
| 1175 |
+
|
| 1176 |
+
# Remove the item from the template
|
| 1177 |
+
result = await db.checklist_templates.update_one(
|
| 1178 |
+
{"templateId": template_id},
|
| 1179 |
+
{"$pull": {"sections.$[].items": {"id": item_id}}}
|
| 1180 |
+
)
|
| 1181 |
+
|
| 1182 |
+
if result.modified_count > 0:
|
| 1183 |
+
logger.info(f"Successfully deleted item {item_id} from template")
|
| 1184 |
+
return {
|
| 1185 |
+
"success": True,
|
| 1186 |
+
"message": "Item deleted successfully"
|
| 1187 |
+
}
|
| 1188 |
+
else:
|
| 1189 |
+
raise HTTPException(
|
| 1190 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1191 |
+
detail="Item not found"
|
| 1192 |
+
)
|
| 1193 |
+
|
| 1194 |
+
except HTTPException:
|
| 1195 |
+
raise
|
| 1196 |
+
except Exception as e:
|
| 1197 |
+
logger.error(f"Error deleting item {item_id} from template: {e}")
|
| 1198 |
+
raise HTTPException(
|
| 1199 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1200 |
+
detail=f"Failed to delete item: {str(e)}"
|
| 1201 |
+
)
|
| 1202 |
+
|
| 1203 |
+
@app.post("/api/checklist-template/section", response_model=Dict[str, Any])
|
| 1204 |
+
async def add_template_section(section_data: dict):
|
| 1205 |
+
"""
|
| 1206 |
+
Add a new section to the shared checklist template
|
| 1207 |
+
|
| 1208 |
+
Args:
|
| 1209 |
+
section_data: Dictionary containing section information
|
| 1210 |
+
|
| 1211 |
+
Returns:
|
| 1212 |
+
Dictionary confirming the section was added
|
| 1213 |
+
|
| 1214 |
+
Raises:
|
| 1215 |
+
HTTPException: If database operation fails
|
| 1216 |
+
"""
|
| 1217 |
+
try:
|
| 1218 |
+
logger.info(f"Adding new section to shared template")
|
| 1219 |
+
|
| 1220 |
+
# Ensure template exists first and get it
|
| 1221 |
+
template = await ensure_default_template_exists()
|
| 1222 |
+
|
| 1223 |
+
if not template:
|
| 1224 |
+
raise HTTPException(
|
| 1225 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1226 |
+
detail="Template not found"
|
| 1227 |
+
)
|
| 1228 |
+
|
| 1229 |
+
template_id = template.get("templateId", "default")
|
| 1230 |
+
|
| 1231 |
+
# Create new section
|
| 1232 |
+
new_section = {
|
| 1233 |
+
"id": section_data.get('id', f"S{len(template['sections'])+1}"),
|
| 1234 |
+
"title": section_data.get('title', 'New Section'),
|
| 1235 |
+
"icon": section_data.get('icon', 'Settings'),
|
| 1236 |
+
"items": []
|
| 1237 |
+
}
|
| 1238 |
+
|
| 1239 |
+
# Add the new section
|
| 1240 |
+
result = await db.checklist_templates.update_one(
|
| 1241 |
+
{"templateId": template_id},
|
| 1242 |
+
{"$push": {"sections": new_section}}
|
| 1243 |
+
)
|
| 1244 |
+
|
| 1245 |
+
if result.modified_count > 0:
|
| 1246 |
+
logger.info(f"Successfully added section to template")
|
| 1247 |
+
return {
|
| 1248 |
+
"success": True,
|
| 1249 |
+
"message": "Section added successfully",
|
| 1250 |
+
"data": new_section
|
| 1251 |
+
}
|
| 1252 |
+
else:
|
| 1253 |
+
raise HTTPException(
|
| 1254 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1255 |
+
detail="Failed to add section"
|
| 1256 |
+
)
|
| 1257 |
+
|
| 1258 |
+
except HTTPException:
|
| 1259 |
+
raise
|
| 1260 |
+
except Exception as e:
|
| 1261 |
+
logger.error(f"Error adding section to template: {e}")
|
| 1262 |
+
raise HTTPException(
|
| 1263 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1264 |
+
detail=f"Failed to add section: {str(e)}"
|
| 1265 |
+
)
|
| 1266 |
+
|
| 1267 |
+
@app.delete("/api/checklist-template/section/{section_id}", response_model=Dict[str, Any])
|
| 1268 |
+
async def delete_template_section(section_id: str):
|
| 1269 |
+
"""
|
| 1270 |
+
Delete a section from the shared checklist template
|
| 1271 |
+
|
| 1272 |
+
Args:
|
| 1273 |
+
section_id: ID of the section to delete
|
| 1274 |
+
|
| 1275 |
+
Returns:
|
| 1276 |
+
Dictionary confirming the section was deleted
|
| 1277 |
+
|
| 1278 |
+
Raises:
|
| 1279 |
+
HTTPException: If database operation fails
|
| 1280 |
+
"""
|
| 1281 |
+
try:
|
| 1282 |
+
logger.info(f"Deleting section {section_id} from shared template")
|
| 1283 |
+
|
| 1284 |
+
# Ensure template exists first and get it
|
| 1285 |
+
template = await ensure_default_template_exists()
|
| 1286 |
+
template_id = template.get("templateId") if template else "default"
|
| 1287 |
+
|
| 1288 |
+
# Remove the section from the template
|
| 1289 |
+
result = await db.checklist_templates.update_one(
|
| 1290 |
+
{"templateId": template_id},
|
| 1291 |
+
{"$pull": {"sections": {"id": section_id}}}
|
| 1292 |
+
)
|
| 1293 |
+
|
| 1294 |
+
if result.modified_count > 0:
|
| 1295 |
+
logger.info(f"Successfully deleted section {section_id} from template")
|
| 1296 |
+
return {
|
| 1297 |
+
"success": True,
|
| 1298 |
+
"message": "Section deleted successfully"
|
| 1299 |
+
}
|
| 1300 |
+
else:
|
| 1301 |
+
raise HTTPException(
|
| 1302 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1303 |
+
detail="Section not found"
|
| 1304 |
+
)
|
| 1305 |
+
|
| 1306 |
+
except HTTPException:
|
| 1307 |
+
raise
|
| 1308 |
+
except Exception as e:
|
| 1309 |
+
logger.error(f"Error deleting section {section_id} from template: {e}")
|
| 1310 |
+
raise HTTPException(
|
| 1311 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1312 |
+
detail=f"Failed to delete section: {str(e)}"
|
| 1313 |
+
)
|
| 1314 |
+
|
| 1315 |
+
@app.post("/api/checklist/{user_id}/item", response_model=Dict[str, Any])
|
| 1316 |
+
async def add_checklist_item(user_id: str, item_data: dict):
|
| 1317 |
+
"""
|
| 1318 |
+
Add a new item to a checklist
|
| 1319 |
+
|
| 1320 |
+
Args:
|
| 1321 |
+
user_id: Unique identifier for the user
|
| 1322 |
+
item_data: Dictionary containing item information (sectionId, requirement, etc.)
|
| 1323 |
+
|
| 1324 |
+
Returns:
|
| 1325 |
+
Dictionary confirming the item was added
|
| 1326 |
+
|
| 1327 |
+
Raises:
|
| 1328 |
+
HTTPException: If database operation fails
|
| 1329 |
+
"""
|
| 1330 |
+
try:
|
| 1331 |
+
logger.info(f"Adding new item to checklist for user: {user_id}")
|
| 1332 |
+
|
| 1333 |
+
# Get the user's checklist
|
| 1334 |
+
checklist_doc = await db.checklists.find_one({"userId": user_id})
|
| 1335 |
+
|
| 1336 |
+
if not checklist_doc:
|
| 1337 |
+
raise HTTPException(
|
| 1338 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1339 |
+
detail="Checklist not found for user"
|
| 1340 |
+
)
|
| 1341 |
+
|
| 1342 |
+
# Find the section and add the new item
|
| 1343 |
+
section_id = item_data.get('sectionId')
|
| 1344 |
+
new_item = {
|
| 1345 |
+
"id": item_data.get('id', f"I{section_id}.{len(checklist_doc['sections'][int(section_id[-1])-1]['items'])+1}"),
|
| 1346 |
+
"requirement": item_data.get('requirement', ''),
|
| 1347 |
+
"compliance": "N/A",
|
| 1348 |
+
"deviation": "",
|
| 1349 |
+
"action": "",
|
| 1350 |
+
"imageData": None,
|
| 1351 |
+
"checkedAt": None,
|
| 1352 |
+
"checkedBy": ""
|
| 1353 |
+
}
|
| 1354 |
+
|
| 1355 |
+
# Update the checklist
|
| 1356 |
+
result = await db.checklists.update_one(
|
| 1357 |
+
{"userId": user_id, "sections.id": section_id},
|
| 1358 |
+
{"$push": {f"sections.$.items": new_item}}
|
| 1359 |
+
)
|
| 1360 |
+
|
| 1361 |
+
if result.modified_count > 0:
|
| 1362 |
+
logger.info(f"Successfully added item to checklist for user {user_id}")
|
| 1363 |
+
return {
|
| 1364 |
+
"success": True,
|
| 1365 |
+
"message": "Item added successfully",
|
| 1366 |
+
"data": new_item
|
| 1367 |
+
}
|
| 1368 |
+
else:
|
| 1369 |
+
raise HTTPException(
|
| 1370 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1371 |
+
detail="Failed to add item"
|
| 1372 |
+
)
|
| 1373 |
+
|
| 1374 |
+
except HTTPException:
|
| 1375 |
+
raise
|
| 1376 |
+
except Exception as e:
|
| 1377 |
+
logger.error(f"Error adding item for user {user_id}: {e}")
|
| 1378 |
+
raise HTTPException(
|
| 1379 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1380 |
+
detail=f"Failed to add item: {str(e)}"
|
| 1381 |
+
)
|
| 1382 |
+
|
| 1383 |
+
@app.put("/api/checklist/{user_id}/item/{item_id}", response_model=Dict[str, Any])
|
| 1384 |
+
async def update_checklist_item(user_id: str, item_id: str, item_data: dict):
|
| 1385 |
+
"""
|
| 1386 |
+
Update an existing checklist item
|
| 1387 |
+
|
| 1388 |
+
Args:
|
| 1389 |
+
user_id: Unique identifier for the user
|
| 1390 |
+
item_id: ID of the item to update
|
| 1391 |
+
item_data: Dictionary containing updated item information
|
| 1392 |
+
|
| 1393 |
+
Returns:
|
| 1394 |
+
Dictionary confirming the item was updated
|
| 1395 |
+
|
| 1396 |
+
Raises:
|
| 1397 |
+
HTTPException: If database operation fails
|
| 1398 |
+
"""
|
| 1399 |
+
try:
|
| 1400 |
+
logger.info(f"Updating item {item_id} for user: {user_id}")
|
| 1401 |
+
|
| 1402 |
+
# Check if checklist exists
|
| 1403 |
+
checklist = await db.checklists.find_one({"userId": user_id})
|
| 1404 |
+
if not checklist:
|
| 1405 |
+
logger.warning(f"No checklist found for user {user_id}")
|
| 1406 |
+
raise HTTPException(
|
| 1407 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1408 |
+
detail=f"Checklist not found for user {user_id}. Please create a checklist first."
|
| 1409 |
+
)
|
| 1410 |
+
|
| 1411 |
+
# Prepare update fields
|
| 1412 |
+
update_fields = {}
|
| 1413 |
+
for field in ['requirement', 'compliance', 'deviation', 'action']:
|
| 1414 |
+
if field in item_data:
|
| 1415 |
+
update_fields[f"sections.$[].items.$[item].{field}"] = item_data[field]
|
| 1416 |
+
|
| 1417 |
+
if not update_fields:
|
| 1418 |
+
raise HTTPException(
|
| 1419 |
+
status_code=status.HTTP_400_BAD_REQUEST,
|
| 1420 |
+
detail="No valid fields to update"
|
| 1421 |
+
)
|
| 1422 |
+
|
| 1423 |
+
# Update the item
|
| 1424 |
+
result = await db.checklists.update_one(
|
| 1425 |
+
{"userId": user_id},
|
| 1426 |
+
{"$set": update_fields},
|
| 1427 |
+
array_filters=[{"item.id": item_id}]
|
| 1428 |
+
)
|
| 1429 |
+
|
| 1430 |
+
if result.modified_count > 0:
|
| 1431 |
+
logger.info(f"Successfully updated item {item_id} for user {user_id}")
|
| 1432 |
+
return {
|
| 1433 |
+
"success": True,
|
| 1434 |
+
"message": "Item updated successfully",
|
| 1435 |
+
"data": item_data
|
| 1436 |
+
}
|
| 1437 |
+
else:
|
| 1438 |
+
raise HTTPException(
|
| 1439 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1440 |
+
detail="Item not found or no changes made"
|
| 1441 |
+
)
|
| 1442 |
+
|
| 1443 |
+
except HTTPException:
|
| 1444 |
+
raise
|
| 1445 |
+
except Exception as e:
|
| 1446 |
+
logger.error(f"Error updating item {item_id} for user {user_id}: {e}")
|
| 1447 |
+
raise HTTPException(
|
| 1448 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1449 |
+
detail=f"Failed to update item: {str(e)}"
|
| 1450 |
+
)
|
| 1451 |
+
|
| 1452 |
+
@app.delete("/api/checklist/{user_id}/item/{item_id}", response_model=Dict[str, Any])
|
| 1453 |
+
async def delete_checklist_item(user_id: str, item_id: str):
|
| 1454 |
+
"""
|
| 1455 |
+
Delete a checklist item
|
| 1456 |
+
|
| 1457 |
+
Args:
|
| 1458 |
+
user_id: Unique identifier for the user
|
| 1459 |
+
item_id: ID of the item to delete
|
| 1460 |
+
|
| 1461 |
+
Returns:
|
| 1462 |
+
Dictionary confirming the item was deleted
|
| 1463 |
+
|
| 1464 |
+
Raises:
|
| 1465 |
+
HTTPException: If database operation fails
|
| 1466 |
+
"""
|
| 1467 |
+
try:
|
| 1468 |
+
logger.info(f"Deleting item {item_id} for user: {user_id}")
|
| 1469 |
+
|
| 1470 |
+
# Check if checklist exists
|
| 1471 |
+
checklist = await db.checklists.find_one({"userId": user_id})
|
| 1472 |
+
if not checklist:
|
| 1473 |
+
logger.warning(f"No checklist found for user {user_id}")
|
| 1474 |
+
raise HTTPException(
|
| 1475 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1476 |
+
detail=f"Checklist not found for user {user_id}. Please create a checklist first."
|
| 1477 |
+
)
|
| 1478 |
+
|
| 1479 |
+
# Remove the item from the checklist
|
| 1480 |
+
result = await db.checklists.update_one(
|
| 1481 |
+
{"userId": user_id},
|
| 1482 |
+
{"$pull": {"sections.$[].items": {"id": item_id}}}
|
| 1483 |
+
)
|
| 1484 |
+
|
| 1485 |
+
if result.modified_count > 0:
|
| 1486 |
+
logger.info(f"Successfully deleted item {item_id} for user {user_id}")
|
| 1487 |
+
return {
|
| 1488 |
+
"success": True,
|
| 1489 |
+
"message": "Item deleted successfully"
|
| 1490 |
+
}
|
| 1491 |
+
else:
|
| 1492 |
+
raise HTTPException(
|
| 1493 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1494 |
+
detail="Item not found"
|
| 1495 |
+
)
|
| 1496 |
+
|
| 1497 |
+
except HTTPException:
|
| 1498 |
+
raise
|
| 1499 |
+
except Exception as e:
|
| 1500 |
+
logger.error(f"Error deleting item {item_id} for user {user_id}: {e}")
|
| 1501 |
+
raise HTTPException(
|
| 1502 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1503 |
+
detail=f"Failed to delete item: {str(e)}"
|
| 1504 |
+
)
|
| 1505 |
+
|
| 1506 |
+
@app.post("/api/checklist/{user_id}/section", response_model=Dict[str, Any])
|
| 1507 |
+
async def add_checklist_section(user_id: str, section_data: dict):
|
| 1508 |
+
"""
|
| 1509 |
+
Add a new section to a checklist
|
| 1510 |
+
|
| 1511 |
+
Args:
|
| 1512 |
+
user_id: Unique identifier for the user
|
| 1513 |
+
section_data: Dictionary containing section information
|
| 1514 |
+
|
| 1515 |
+
Returns:
|
| 1516 |
+
Dictionary confirming the section was added
|
| 1517 |
+
|
| 1518 |
+
Raises:
|
| 1519 |
+
HTTPException: If database operation fails
|
| 1520 |
+
"""
|
| 1521 |
+
try:
|
| 1522 |
+
logger.info(f"Adding new section to checklist for user: {user_id}")
|
| 1523 |
+
|
| 1524 |
+
# Get the user's checklist
|
| 1525 |
+
checklist_doc = await db.checklists.find_one({"userId": user_id})
|
| 1526 |
+
|
| 1527 |
+
if not checklist_doc:
|
| 1528 |
+
raise HTTPException(
|
| 1529 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1530 |
+
detail="Checklist not found for user"
|
| 1531 |
+
)
|
| 1532 |
+
|
| 1533 |
+
# Create new section
|
| 1534 |
+
new_section = {
|
| 1535 |
+
"id": section_data.get('id', f"S{len(checklist_doc['sections'])+1}"),
|
| 1536 |
+
"title": section_data.get('title', 'New Section'),
|
| 1537 |
+
"icon": section_data.get('icon', 'Settings'),
|
| 1538 |
+
"items": []
|
| 1539 |
+
}
|
| 1540 |
+
|
| 1541 |
+
# Add the new section
|
| 1542 |
+
result = await db.checklists.update_one(
|
| 1543 |
+
{"userId": user_id},
|
| 1544 |
+
{"$push": {"sections": new_section}}
|
| 1545 |
+
)
|
| 1546 |
+
|
| 1547 |
+
if result.modified_count > 0:
|
| 1548 |
+
logger.info(f"Successfully added section to checklist for user {user_id}")
|
| 1549 |
+
return {
|
| 1550 |
+
"success": True,
|
| 1551 |
+
"message": "Section added successfully",
|
| 1552 |
+
"data": new_section
|
| 1553 |
+
}
|
| 1554 |
+
else:
|
| 1555 |
+
raise HTTPException(
|
| 1556 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1557 |
+
detail="Failed to add section"
|
| 1558 |
+
)
|
| 1559 |
+
|
| 1560 |
+
except HTTPException:
|
| 1561 |
+
raise
|
| 1562 |
+
except Exception as e:
|
| 1563 |
+
logger.error(f"Error adding section for user {user_id}: {e}")
|
| 1564 |
+
raise HTTPException(
|
| 1565 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1566 |
+
detail=f"Failed to add section: {str(e)}"
|
| 1567 |
+
)
|
| 1568 |
+
|
| 1569 |
+
@app.delete("/api/checklist/{user_id}/section/{section_id}", response_model=Dict[str, Any])
|
| 1570 |
+
async def delete_checklist_section(user_id: str, section_id: str):
|
| 1571 |
+
"""
|
| 1572 |
+
Delete a checklist section and all its items
|
| 1573 |
+
|
| 1574 |
+
Args:
|
| 1575 |
+
user_id: Unique identifier for the user
|
| 1576 |
+
section_id: ID of the section to delete
|
| 1577 |
+
|
| 1578 |
+
Returns:
|
| 1579 |
+
Dictionary confirming the section was deleted
|
| 1580 |
+
|
| 1581 |
+
Raises:
|
| 1582 |
+
HTTPException: If database operation fails
|
| 1583 |
+
"""
|
| 1584 |
+
try:
|
| 1585 |
+
logger.info(f"Deleting section {section_id} for user: {user_id}")
|
| 1586 |
+
|
| 1587 |
+
# Check if checklist exists
|
| 1588 |
+
checklist = await db.checklists.find_one({"userId": user_id})
|
| 1589 |
+
if not checklist:
|
| 1590 |
+
logger.warning(f"No checklist found for user {user_id}")
|
| 1591 |
+
raise HTTPException(
|
| 1592 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1593 |
+
detail=f"Checklist not found for user {user_id}. Please create a checklist first."
|
| 1594 |
+
)
|
| 1595 |
+
|
| 1596 |
+
# Remove the section from the checklist
|
| 1597 |
+
result = await db.checklists.update_one(
|
| 1598 |
+
{"userId": user_id},
|
| 1599 |
+
{"$pull": {"sections": {"id": section_id}}}
|
| 1600 |
+
)
|
| 1601 |
+
|
| 1602 |
+
if result.modified_count > 0:
|
| 1603 |
+
logger.info(f"Successfully deleted section {section_id} for user {user_id}")
|
| 1604 |
+
return {
|
| 1605 |
+
"success": True,
|
| 1606 |
+
"message": "Section deleted successfully"
|
| 1607 |
+
}
|
| 1608 |
+
else:
|
| 1609 |
+
raise HTTPException(
|
| 1610 |
+
status_code=status.HTTP_404_NOT_FOUND,
|
| 1611 |
+
detail="Section not found"
|
| 1612 |
+
)
|
| 1613 |
+
|
| 1614 |
+
except HTTPException:
|
| 1615 |
+
raise
|
| 1616 |
+
except Exception as e:
|
| 1617 |
+
logger.error(f"Error deleting section {section_id} for user {user_id}: {e}")
|
| 1618 |
+
raise HTTPException(
|
| 1619 |
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
| 1620 |
+
detail=f"Failed to delete section: {str(e)}"
|
| 1621 |
+
)
|
| 1622 |
+
|
| 1623 |
@app.get("/api/checklists", response_model=Dict[str, Any])
|
| 1624 |
async def get_all_checklists():
|
| 1625 |
"""
|
|
|
|
| 1707 |
|
| 1708 |
# Run the server
|
| 1709 |
uvicorn.run(
|
| 1710 |
+
"main:app",
|
| 1711 |
host="0.0.0.0",
|
| 1712 |
port=PORT,
|
| 1713 |
+
reload=True, # Enable auto-reload for development
|
| 1714 |
log_level="info"
|
| 1715 |
)
|