330 lines
10 KiB
Python
330 lines
10 KiB
Python
from fastapi import APIRouter, HTTPException, status
|
|
from fastapi.responses import FileResponse
|
|
from pydantic import BaseModel, Field, HttpUrl
|
|
from typing import Optional, Dict, Any, Literal
|
|
from datetime import datetime as dt
|
|
import os
|
|
|
|
from pif_compiler.functions.common_func import generate_pdf
|
|
from pif_compiler.services.srv_pubchem import pubchem_dap
|
|
from pif_compiler.services.srv_cir import search_ingredient
|
|
from pif_compiler.functions.common_log import get_logger
|
|
from pif_compiler.functions.db_utils import db_connect
|
|
|
|
logger = get_logger()
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class GeneratePdfRequest(BaseModel):
|
|
link: str = Field(..., description="URL of the page to convert to PDF")
|
|
name: str = Field(..., description="Name for the generated PDF file (without extension)")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"link": "https://example.com/page",
|
|
"name": "my_document"
|
|
}
|
|
}
|
|
|
|
|
|
class GeneratePdfResponse(BaseModel):
|
|
success: bool
|
|
name: str
|
|
message: str
|
|
file_path: Optional[str] = None
|
|
|
|
|
|
@router.post("/common/generate-pdf", response_model=GeneratePdfResponse, tags=["Common"])
|
|
async def generate_pdf_endpoint(request: GeneratePdfRequest):
|
|
"""
|
|
Generate a PDF from a web page URL.
|
|
|
|
This endpoint uses Playwright to:
|
|
1. Navigate to the provided URL
|
|
2. Render the page
|
|
3. Generate a PDF file
|
|
4. Save it in the 'pdfs/' directory
|
|
|
|
If a PDF with the same name already exists, it will skip generation
|
|
and return success immediately.
|
|
|
|
Args:
|
|
request: GeneratePdfRequest with the URL and desired PDF name
|
|
|
|
Returns:
|
|
GeneratePdfResponse with success status and file information
|
|
"""
|
|
logger.info(f"API request received to generate PDF: name='{request.name}', link='{request.link}'")
|
|
|
|
try:
|
|
result = await generate_pdf(request.link, request.name)
|
|
|
|
if result:
|
|
file_path = f"pdfs/{request.name}.pdf"
|
|
|
|
# Check if file was already existing or newly created
|
|
if os.path.exists(file_path):
|
|
logger.info(f"PDF available for '{request.name}'")
|
|
return GeneratePdfResponse(
|
|
success=True,
|
|
name=request.name,
|
|
message=f"PDF generated successfully or already exists",
|
|
file_path=file_path
|
|
)
|
|
else:
|
|
logger.error(f"PDF file not found after generation for '{request.name}'")
|
|
return GeneratePdfResponse(
|
|
success=False,
|
|
name=request.name,
|
|
message="PDF generation completed but file not found",
|
|
file_path=None
|
|
)
|
|
else:
|
|
logger.error(f"PDF generation failed for '{request.name}'")
|
|
return GeneratePdfResponse(
|
|
success=False,
|
|
name=request.name,
|
|
message="PDF generation failed",
|
|
file_path=None
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating PDF for '{request.name}': {str(e)}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Internal error while generating PDF: {str(e)}"
|
|
)
|
|
|
|
|
|
@router.get("/common/download-pdf/{name}", response_class=FileResponse, tags=["Common"])
|
|
async def download_pdf(name: str):
|
|
"""
|
|
Download a previously generated PDF file.
|
|
|
|
Args:
|
|
name: Name of the PDF file (without extension)
|
|
|
|
Returns:
|
|
FileResponse with the PDF file for download
|
|
"""
|
|
logger.info(f"API request received to download PDF: name='{name}'")
|
|
|
|
file_path = f"pdfs/{name}.pdf"
|
|
|
|
if not os.path.exists(file_path):
|
|
logger.warning(f"PDF file not found: {file_path}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"PDF file '{name}' not found. Please generate it first using /common/generate-pdf"
|
|
)
|
|
|
|
logger.info(f"Serving PDF file: {file_path}")
|
|
return FileResponse(
|
|
path=file_path,
|
|
media_type="application/pdf",
|
|
filename=f"{name}.pdf"
|
|
)
|
|
|
|
|
|
class PubchemRequest(BaseModel):
|
|
cas: str = Field(..., description="CAS number of the substance to search for in PubChem")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"cas": "64-17-5"
|
|
}
|
|
}
|
|
|
|
|
|
class PubchemResponse(BaseModel):
|
|
success: bool
|
|
cas: str
|
|
data: Optional[Dict[str, Any]] = None
|
|
error: Optional[str] = None
|
|
|
|
|
|
@router.post("/common/pubchem", response_model=PubchemResponse, tags=["Common"])
|
|
async def search_pubchem(request: PubchemRequest):
|
|
"""
|
|
Search for substance information in PubChem database.
|
|
|
|
This endpoint retrieves comprehensive substance data from PubChem including:
|
|
- **Basic info**: CID, CAS, first PubChem name, PubChem link
|
|
- **First level properties**: XLogP, molecular weight, TPSA, exact mass
|
|
- **Second level properties**: Melting Point, Dissociation Constants, pH
|
|
|
|
The data is automatically cleaned and formatted for easier consumption.
|
|
|
|
Args:
|
|
request: PubchemRequest containing the CAS number
|
|
|
|
Returns:
|
|
PubchemResponse with the substance data or error information
|
|
"""
|
|
logger.info(f"API request received for PubChem search: CAS={request.cas}")
|
|
|
|
try:
|
|
result = pubchem_dap(request.cas)
|
|
|
|
# Check if result is None (error occurred)
|
|
if result is None:
|
|
logger.error(f"PubChem search returned None for CAS: {request.cas}")
|
|
return PubchemResponse(
|
|
success=False,
|
|
cas=request.cas,
|
|
data=None,
|
|
error="An error occurred while searching PubChem. Please check the logs for details."
|
|
)
|
|
|
|
# Check if result is a string (no results found)
|
|
if isinstance(result, str):
|
|
logger.warning(f"No results found in PubChem for CAS: {request.cas}")
|
|
return PubchemResponse(
|
|
success=False,
|
|
cas=request.cas,
|
|
data=None,
|
|
error=result
|
|
)
|
|
|
|
# Successful result
|
|
logger.info(f"Successfully retrieved PubChem data for CAS: {request.cas}")
|
|
return PubchemResponse(
|
|
success=True,
|
|
cas=request.cas,
|
|
data=result,
|
|
error=None
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing PubChem request for CAS {request.cas}: {str(e)}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Internal error while processing PubChem request: {str(e)}"
|
|
)
|
|
|
|
|
|
class CirSearchRequest(BaseModel):
|
|
text: str = Field(..., description="Text to search for in the CIR database")
|
|
|
|
class Config:
|
|
json_schema_extra = {
|
|
"example": {
|
|
"text": "olio di argan"
|
|
}
|
|
}
|
|
|
|
|
|
class CirSearchResponse(BaseModel):
|
|
success: bool
|
|
text: str
|
|
results: Optional[list] = None
|
|
count: Optional[int] = None
|
|
error: Optional[str] = None
|
|
|
|
|
|
@router.post("/common/cir-search", response_model=CirSearchResponse, tags=["Common"])
|
|
async def cir_search_endpoint(request: CirSearchRequest):
|
|
"""
|
|
Search for ingredients in the CIR (Cosmetic Ingredient Review) database.
|
|
|
|
This endpoint searches the CIR NOAEL database for ingredients matching
|
|
the provided text query.
|
|
|
|
Args:
|
|
request: CirSearchRequest containing the search text
|
|
|
|
Returns:
|
|
CirSearchResponse with the search results or error information
|
|
"""
|
|
logger.info(f"API request received for CIR search: text='{request.text}'")
|
|
|
|
try:
|
|
results = search_ingredient(request.text)
|
|
|
|
if results is None:
|
|
logger.error(f"CIR search returned None for text: {request.text}")
|
|
return CirSearchResponse(
|
|
success=False,
|
|
text=request.text,
|
|
results=None,
|
|
count=0,
|
|
error="An error occurred while searching the CIR database. Please check the logs for details."
|
|
)
|
|
|
|
logger.info(f"Successfully retrieved {len(results)} results from CIR database for text: {request.text}")
|
|
return CirSearchResponse(
|
|
success=True,
|
|
text=request.text,
|
|
results=results,
|
|
count=len(results),
|
|
error=None
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error processing CIR search request for text '{request.text}': {str(e)}", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail=f"Internal error while processing CIR search request: {str(e)}"
|
|
)
|
|
|
|
|
|
class SegnalazioneRequest(BaseModel):
|
|
cas: Optional[str] = None
|
|
page: str
|
|
description: str
|
|
error: Optional[str] = None
|
|
priority: Literal["bassa", "media", "alta"] = "media"
|
|
|
|
|
|
@router.post("/common/segnalazione", tags=["Common"])
|
|
async def create_segnalazione(request: SegnalazioneRequest):
|
|
"""Salva una segnalazione/ticket nella collection MongoDB 'segnalazioni'."""
|
|
collection = db_connect(collection_name="segnalazioni")
|
|
if collection is None:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Connessione MongoDB fallita"
|
|
)
|
|
|
|
doc = {
|
|
**request.model_dump(),
|
|
"created_at": dt.now().isoformat(),
|
|
"stato": "aperta",
|
|
}
|
|
|
|
try:
|
|
result = collection.insert_one(doc)
|
|
logger.info(f"Segnalazione creata: id={result.inserted_id}, page={request.page}, priority={request.priority}")
|
|
return {"success": True, "id": str(result.inserted_id)}
|
|
except Exception as e:
|
|
logger.error(f"create_segnalazione: errore MongoDB — {e}")
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Errore nel salvataggio della segnalazione"
|
|
)
|
|
|
|
|
|
@router.get("/common/health", tags=["Common"])
|
|
async def common_health_check():
|
|
"""
|
|
Health check endpoint for common functions service.
|
|
|
|
Returns the status of the common functions components.
|
|
"""
|
|
return {
|
|
"status": "healthy",
|
|
"service": "common-functions",
|
|
"components": {
|
|
"api": "operational",
|
|
"logging": "operational",
|
|
"utilities": "operational",
|
|
"pubchem": "operational",
|
|
"cir": "operational"
|
|
}
|
|
}
|
|
|
|
|
|
|