from fastapi import APIRouter, Depends from sqlalchemy import text from .db import get_db from .mirror import enrich as mirror_enrich # The virtual store API — PUBLIC read (it's the customer-facing storefront). Replaces # WowPlatter's payload.php: same scene shape, but from Postgres instead of a full WP bootstrap. router = APIRouter(prefix="/virtual", tags=["virtual-store"]) async def _all(db, table, where=""): rows = await db.execute(text(f"SELECT * FROM {table} {where}")) return [dict(r) for r in rows.mappings()] def _attach(recs, meta): for r in recs: m = meta.get(r["release_id"], {}) r["title"], r["artist"], r["thumb"] = m.get("title"), m.get("artist"), m.get("thumb") return recs @router.get("/scene") async def scene(db=Depends(get_db)): """Everything the renderer needs at boot. Records are SLIM — one front cover per crate; the full per-crate list is lazy-loaded on dig via /virtual/crate/{id}/records.""" spaces = await _all(db, "virtual_space", "WHERE visible='y'") active = spaces[0] if len(spaces) == 1 else next( (s for s in spaces if s.get("is_default") == "y"), spaces[0] if spaces else None) recs = [dict(r) for r in (await db.execute(text(""" SELECT DISTINCT ON (crate_id) crate_id, slot_number, release_id, sku, price, condition FROM inventory WHERE in_stock AND crate_id IS NOT NULL AND release_id IS NOT NULL ORDER BY crate_id, slot_number NULLS LAST"""))).mappings()] recs = _attach(recs, await mirror_enrich({r["release_id"] for r in recs})) return { "space": active, "spaces": spaces, "racks": await _all(db, "virtual_rack", "WHERE visible='y'"), "rack_types": await _all(db, "virtual_rack_type"), "rack_type_levels": await _all(db, "virtual_rack_type_level"), "crates": await _all(db, "virtual_crate", "WHERE visible='y'"), "crate_types": await _all(db, "virtual_crate_type"), "cameras": await _all(db, "virtual_camera"), "lights": await _all(db, "virtual_light"), "decals": await _all(db, "virtual_decal", "WHERE visible <> 'n'"), "portals": await _all(db, "virtual_portal"), "records": recs, } @router.get("/crate/{crate_id}/records") async def crate_records(crate_id: int, db=Depends(get_db)): """Full contents of one crate — loaded when the shopper starts digging it.""" recs = [dict(r) for r in (await db.execute(text(""" SELECT release_id, slot_number, sku, price, condition FROM inventory WHERE crate_id = :c AND in_stock AND release_id IS NOT NULL ORDER BY slot_number NULLS LAST"""), {"c": crate_id})).mappings()] recs = _attach(recs, await mirror_enrich({r["release_id"] for r in recs})) return {"ok": True, "crate_id": crate_id, "records": recs} @router.get("/locate") async def locate(release_id: int, db=Depends(get_db)): """Locate-stock: where in the physical store is this record? Returns the crate + position so the UI can fly the camera there and flash the bin.""" row = (await db.execute(text(""" SELECT i.crate_id, i.slot_number, i.sku, c.name, c.label_text, c.pos_x, c.pos_y, c.pos_z FROM inventory i LEFT JOIN virtual_crate c ON c.id = i.crate_id WHERE i.release_id = :r AND i.in_stock AND i.crate_id IS NOT NULL ORDER BY i.slot_number NULLS LAST LIMIT 1"""), {"r": release_id})).mappings().first() if not row: return {"ok": True, "located": False, "release_id": release_id} d = dict(row) return { "ok": True, "located": True, "release_id": release_id, "crate_id": d["crate_id"], "slot_number": d["slot_number"], "sku": d["sku"], "crate": {"name": d["name"], "label_text": d["label_text"], "pos": [float(d["pos_x"] or 0), float(d["pos_y"] or 0), float(d["pos_z"] or 0)]}, }