- app/mirror.py: centralised enrichment, DISCOGS_MIRROR_KIND selects full (ultra `release`) vs vps (`discogs_release`); virtual.py + wowplatter_v1.py use it. - Dockerfile + .dockerignore for the VPS container. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
84 lines
3.8 KiB
Python
84 lines
3.8 KiB
Python
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)]},
|
|
}
|