from __future__ import annotations import threading from typing import List, Optional from fastapi import APIRouter, HTTPException from pydantic import BaseModel, Field, field_validator from app.services.qr_generator_service import QRGeneratorError, QRGeneratorService router = APIRouter() _service_lock = threading.Lock() _shared_service: Optional[QRGeneratorService] = None def _get_service() -> QRGeneratorService: global _shared_service if _shared_service is None: with _service_lock: if _shared_service is None: _shared_service = QRGeneratorService() return _shared_service class QRGenerateRequest(BaseModel): data: List[str] = Field( ..., min_length=1, max_length=500, description="List of data strings to generate QR codes for (max 500)", ) size: str = Field( "300x300", description="QR code image size in WIDTHxHEIGHT format (e.g., 300x300)", ) @field_validator("data") @classmethod def validate_data_items(cls, v: List[str]) -> List[str]: for i, item in enumerate(v): if not item or not item.strip(): raise ValueError(f"Data item at index {i} must be a non-empty string") if len(item) > 2048: raise ValueError( f"Data item at index {i} exceeds max length of 2048 characters" ) return v @field_validator("size") @classmethod def validate_size(cls, v: str) -> str: parts = v.lower().split("x") if len(parts) != 2: raise ValueError("Size must be in format WIDTHxHEIGHT (e.g., 300x300)") for part in parts: if not part.isdigit() or int(part) < 1 or int(part) > 1000: raise ValueError("Width and height must be between 1 and 1000") return v class QRItemResult(BaseModel): index: int data: str qr_image_url: Optional[str] = None error: Optional[str] = None class QRGenerateResponse(BaseModel): success: bool total: int succeeded: int failed: int results: List[QRItemResult] @router.post( "/qr-generator", response_model=QRGenerateResponse, summary="Generate QR codes for a batch of data strings", description="Accepts up to 500 data strings and returns QR code image URLs for each. " "The actual QR image is hosted by an external service.", ) def generate_qr_codes(body: QRGenerateRequest): service = _get_service() try: results = service.generate_bulk(body.data, body.size) except QRGeneratorError as e: raise HTTPException(status_code=400, detail={"success": False, "error": str(e)}) succeeded = sum(1 for r in results if r["error"] is None) failed = len(results) - succeeded return QRGenerateResponse( success=failed == 0, total=len(results), succeeded=succeeded, failed=failed, results=[QRItemResult(**r) for r in results], )