Files
verify/backend/app/routers/verify.py
Mohamed Mathar Irfan ed6610d5d8 Initial project upload
2026-07-28 17:57:02 +05:30

86 lines
3.0 KiB
Python

"""Public consumer verification — no auth. Single-read via the QR snapshot.
GET /verify/{code} is READ-ONLY (no scan logging) so renders, prefetches, bots
and React Strict Mode double-mounts never inflate the scan count.
The frontend logs a scan explicitly via POST /scans/log after the page mounts.
Must return valid data in Active, Grace, and Archived subscription states;
never 404 for a valid QR. Resolves the four states:
genuine | unverified | not_found | recalled.
"""
from fastapi import APIRouter, Request
from app.models import QRCode, Organization, ScanLog
from app.models.documents import now
from app.models.enums import QRStatus, VerificationState, SubscriptionStatus
router = APIRouter(tags=["verify"])
def _device_from_ua(ua: str) -> str:
ua = (ua or "").lower()
if "android" in ua:
return "Android"
if "iphone" in ua or "ipad" in ua or "ios" in ua:
return "iOS"
if "mobile" in ua:
return "Other"
return "Desktop"
def _resolve_state(qr: QRCode, product: dict) -> VerificationState:
if product.get("recalled"):
return VerificationState.recalled
if qr.status in (QRStatus.disabled, QRStatus.inactive, QRStatus.expired):
return VerificationState.unverified
return VerificationState.genuine
@router.get("/verify/{qr_code}")
async def verify(qr_code: str):
"""Read-only. Does NOT log a scan."""
qr = await QRCode.find_one(QRCode.code == qr_code)
if not qr:
return {"state": VerificationState.not_found,
"message": "This QR code is not registered with VerifyPack."}
org = await Organization.get(qr.org_id)
if org and org.subscription_status == SubscriptionStatus.deleted:
return {"state": VerificationState.not_found,
"message": "This product record is no longer available."}
snap = qr.snapshot or {}
product = snap.get("product", {})
state = _resolve_state(qr, product)
return {
"state": state,
"qr_code": qr.code,
"serial": qr.serial,
"brand": snap.get("brand"),
"product": product,
"batch": snap.get("batch"),
"trust_score": product.get("trust_score", 90),
"subscription_state": org.subscription_status if org else None,
"powered_by": "VerifyPack",
}
@router.post("/scans/log")
async def log_scan(qr_code: str, request: Request):
"""Record a single scan. Called once by the consumer page after it mounts."""
qr = await QRCode.find_one(QRCode.code == qr_code)
if not qr:
return {"logged": False}
ua = request.headers.get("user-agent", "")
try:
await ScanLog(org_id=qr.org_id, qr_id=str(qr.id), qr_code=qr.code,
product_id=qr.product_id, device=_device_from_ua(ua),
ip=request.client.host if request.client else None).insert()
qr.scan_count += 1
qr.last_scan_at = now()
await qr.save()
except Exception:
return {"logged": False}
return {"logged": True, "scan_count": qr.scan_count}