File size: 3,007 Bytes
a8962d7
 
 
efba968
a8962d7
 
 
 
5a01a63
a8962d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
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],
    )