v0.2 - 19 features: comparator, allergies, gamification, shopping list, achievements, stats, profile, share, bottom nav

This commit is contained in:
2026-02-10 18:52:42 -03:00
parent e8f4788a33
commit ecdd7546d3
33 changed files with 2105 additions and 309 deletions

View File

@@ -0,0 +1,58 @@
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import HTMLResponse
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
import json
from app.database import get_db
from app.models.scan import Scan
from app.models.product import Product
router = APIRouter(prefix="/api", tags=["share"])
@router.get("/scan/{scan_id}/share", response_class=HTMLResponse)
async def share_scan(scan_id: int, db: AsyncSession = Depends(get_db)):
res = await db.execute(select(Scan).where(Scan.id == scan_id))
scan = res.scalar_one_or_none()
if not scan:
raise HTTPException(status_code=404, detail="Scan não encontrado")
analysis = json.loads(scan.analysis_json or '{}')
score = scan.score or 0
color = '#10B981' if score >= 70 else '#EAB308' if score >= 50 else '#F97316' if score >= 30 else '#EF4444'
label = 'Excelente' if score >= 90 else 'Bom' if score >= 70 else 'Regular' if score >= 50 else 'Ruim' if score >= 30 else 'Péssimo'
positives = ''.join(f'<li style="color:#10B981">✅ {p}</li>' for p in analysis.get("positives", []))
negatives = ''.join(f'<li style="color:#EF4444">❌ {n}</li>' for n in analysis.get("negatives", []))
html = f"""<!DOCTYPE html>
<html><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<meta property="og:title" content="ALETHEIA: {scan.product_name} - Score {score}/100">
<meta property="og:description" content="{scan.summary or ''}">
<title>ALETHEIA - {scan.product_name}</title>
<style>
*{{margin:0;padding:0;box-sizing:border-box}}
body{{background:#0A0E17;color:white;font-family:Inter,system-ui,sans-serif;padding:20px;max-width:500px;margin:0 auto}}
.card{{background:#111827;border-radius:20px;padding:24px;margin-bottom:16px;border:1px solid rgba(255,255,255,0.05)}}
.score{{width:120px;height:120px;border-radius:50%;border:6px solid {color};display:flex;align-items:center;justify-content:center;margin:0 auto 16px;flex-direction:column}}
.score span{{font-size:36px;font-weight:900;color:{color}}}
.score small{{font-size:12px;color:#9CA3AF}}
h1{{font-size:20px;text-align:center;margin-bottom:4px}}
h2{{font-size:14px;color:#9CA3AF;text-align:center;margin-bottom:16px}}
.label{{display:inline-block;padding:4px 16px;border-radius:20px;font-weight:700;font-size:14px;color:{color};background:{color}15;text-align:center;margin:0 auto 20px;display:block;width:fit-content}}
ul{{list-style:none;padding:0}}li{{font-size:13px;margin-bottom:6px;color:#D1D5DB}}
.logo{{text-align:center;margin-top:24px;font-size:12px;color:#6B7280}}
.logo b{{background:linear-gradient(135deg,#00D4AA,#7C3AED);-webkit-background-clip:text;-webkit-text-fill-color:transparent}}
</style></head><body>
<div class="card">
<div class="score"><span>{score}</span><small>/100</small></div>
<h1>{scan.product_name or 'Produto'}</h1>
<h2>{scan.brand or ''}</h2>
<div class="label">{label}</div>
<p style="font-size:13px;color:#D1D5DB;text-align:center;margin-bottom:16px">{scan.summary or ''}</p>
{f'<ul>{positives}</ul>' if positives else ''}
{f'<ul style="margin-top:12px">{negatives}</ul>' if negatives else ''}
</div>
<div class="logo">Analisado por <b>ALETHEIA</b> — A verdade sobre o que você come</div>
</body></html>"""
return HTMLResponse(content=html)