Code-API commited on
Commit
a8962d7
·
1 Parent(s): f87115f

feat: add qr code genartor api

Browse files
app/api/v1/qr_generator.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import Any, Dict, List, Optional
5
+
6
+ from fastapi import APIRouter, HTTPException
7
+ from pydantic import BaseModel, Field, field_validator
8
+
9
+ from app.services.qr_generator_service import QRGeneratorService, QRGeneratorError
10
+
11
+ router = APIRouter()
12
+
13
+ _service_lock = threading.Lock()
14
+ _shared_service: Optional[QRGeneratorService] = None
15
+
16
+
17
+ def _get_service() -> QRGeneratorService:
18
+ global _shared_service
19
+ if _shared_service is None:
20
+ with _service_lock:
21
+ if _shared_service is None:
22
+ _shared_service = QRGeneratorService()
23
+ return _shared_service
24
+
25
+
26
+ class QRGenerateRequest(BaseModel):
27
+ data: List[str] = Field(
28
+ ...,
29
+ min_length=1,
30
+ max_length=500,
31
+ description="List of data strings to generate QR codes for (max 500)",
32
+ )
33
+ size: str = Field(
34
+ "300x300",
35
+ description="QR code image size in WIDTHxHEIGHT format (e.g., 300x300)",
36
+ )
37
+
38
+ @field_validator("data")
39
+ @classmethod
40
+ def validate_data_items(cls, v: List[str]) -> List[str]:
41
+ for i, item in enumerate(v):
42
+ if not item or not item.strip():
43
+ raise ValueError(f"Data item at index {i} must be a non-empty string")
44
+ if len(item) > 2048:
45
+ raise ValueError(
46
+ f"Data item at index {i} exceeds max length of 2048 characters"
47
+ )
48
+ return v
49
+
50
+ @field_validator("size")
51
+ @classmethod
52
+ def validate_size(cls, v: str) -> str:
53
+ parts = v.lower().split("x")
54
+ if len(parts) != 2:
55
+ raise ValueError("Size must be in format WIDTHxHEIGHT (e.g., 300x300)")
56
+ for part in parts:
57
+ if not part.isdigit() or int(part) < 1 or int(part) > 1000:
58
+ raise ValueError("Width and height must be between 1 and 1000")
59
+ return v
60
+
61
+
62
+ class QRItemResult(BaseModel):
63
+ index: int
64
+ data: str
65
+ qr_image_url: Optional[str] = None
66
+ error: Optional[str] = None
67
+
68
+
69
+ class QRGenerateResponse(BaseModel):
70
+ success: bool
71
+ total: int
72
+ succeeded: int
73
+ failed: int
74
+ results: List[QRItemResult]
75
+
76
+
77
+ @router.post(
78
+ "/qr-generator",
79
+ response_model=QRGenerateResponse,
80
+ summary="Generate QR codes for a batch of data strings",
81
+ description="Accepts up to 500 data strings and returns QR code image URLs for each. "
82
+ "The actual QR image is hosted by an external service.",
83
+ )
84
+ def generate_qr_codes(body: QRGenerateRequest):
85
+ service = _get_service()
86
+ try:
87
+ results = service.generate_bulk(body.data, body.size)
88
+ except QRGeneratorError as e:
89
+ raise HTTPException(status_code=400, detail={"success": False, "error": str(e)})
90
+
91
+ succeeded = sum(1 for r in results if r["error"] is None)
92
+ failed = len(results) - succeeded
93
+ return QRGenerateResponse(
94
+ success=failed == 0,
95
+ total=len(results),
96
+ succeeded=succeeded,
97
+ failed=failed,
98
+ results=[QRItemResult(**r) for r in results],
99
+ )
app/api/v1/router.py CHANGED
@@ -2,7 +2,7 @@ from __future__ import annotations
2
 
3
  from fastapi import APIRouter
4
 
5
- from app.api.v1 import auth, batch, chat, code_executor, convert, database, embeddings, reconcile, scraper, semantic_router, sql_validator, system, token_counter, token_generator, url_shortener, vector_stores, web_search, webhook_socket
6
  from app.api.verify import router as verify_router
7
 
8
  api_v1_router = APIRouter()
@@ -24,4 +24,5 @@ api_v1_router.include_router(token_generator.router, tags=["Token Generator"])
24
  api_v1_router.include_router(chat.router, tags=["Chat"])
25
  api_v1_router.include_router(vector_stores.router, tags=["Vector Stores"])
26
  api_v1_router.include_router(webhook_socket.router, tags=["Webhook / Socket"])
 
27
  api_v1_router.include_router(url_shortener.router, tags=["URL Shortener"])
 
2
 
3
  from fastapi import APIRouter
4
 
5
+ from app.api.v1 import auth, batch, chat, code_executor, convert, database, embeddings, qr_generator, reconcile, scraper, semantic_router, sql_validator, system, token_counter, token_generator, url_shortener, vector_stores, web_search, webhook_socket
6
  from app.api.verify import router as verify_router
7
 
8
  api_v1_router = APIRouter()
 
24
  api_v1_router.include_router(chat.router, tags=["Chat"])
25
  api_v1_router.include_router(vector_stores.router, tags=["Vector Stores"])
26
  api_v1_router.include_router(webhook_socket.router, tags=["Webhook / Socket"])
27
+ api_v1_router.include_router(qr_generator.router, tags=["QR Generator"])
28
  api_v1_router.include_router(url_shortener.router, tags=["URL Shortener"])
app/services/qr_generator_service.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import urllib.parse
4
+ from typing import Any, Dict, List
5
+
6
+ from app.core.logger import get_logger
7
+
8
+ logger = get_logger(__name__)
9
+
10
+ QR_CODE_BASE_URL = "https://api.qrserver.com/v1/create-qr-code/"
11
+ DEFAULT_SIZE = "300x300"
12
+ MAX_DATA_LENGTH = 2048
13
+ MAX_ITEMS = 500
14
+
15
+
16
+ class QRGeneratorError(Exception):
17
+ pass
18
+
19
+
20
+ class QRGeneratorService:
21
+
22
+ def generate_bulk(
23
+ self,
24
+ data_list: List[str],
25
+ size: str = DEFAULT_SIZE,
26
+ ) -> List[Dict[str, Any]]:
27
+ results: List[Dict[str, Any]] = []
28
+ for idx, data in enumerate(data_list):
29
+ try:
30
+ self._validate_data(data)
31
+ qr_url = self._build_qr_url(data, size)
32
+ results.append({
33
+ "index": idx,
34
+ "data": data,
35
+ "qr_image_url": qr_url,
36
+ "error": None,
37
+ })
38
+ except QRGeneratorError as e:
39
+ results.append({
40
+ "index": idx,
41
+ "data": data,
42
+ "qr_image_url": None,
43
+ "error": str(e),
44
+ })
45
+ return results
46
+
47
+ @staticmethod
48
+ def _validate_data(data: str) -> None:
49
+ if not data or not isinstance(data, str):
50
+ raise QRGeneratorError("Data must be a non-empty string")
51
+ if len(data) > MAX_DATA_LENGTH:
52
+ raise QRGeneratorError(
53
+ f"Data exceeds max length of {MAX_DATA_LENGTH} characters"
54
+ )
55
+
56
+ @staticmethod
57
+ def _build_qr_url(data: str, size: str) -> str:
58
+ encoded = urllib.parse.quote(data, safe="")
59
+ return f"{QR_CODE_BASE_URL}?size={size}&data={encoded}"