feat: deploy-ready — env-aware Discogs enrichment + Dockerfile
- 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>
This commit is contained in:
parent
e1fcf94f93
commit
9b8ecdb84d
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
.venv/
|
||||
.git/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.env
|
||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
COPY . .
|
||||
EXPOSE 8010
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8010"]
|
||||
31
app/mirror.py
Normal file
31
app/mirror.py
Normal file
@ -0,0 +1,31 @@
|
||||
import os
|
||||
|
||||
from sqlalchemy import text
|
||||
|
||||
from .db import MirrorSession
|
||||
|
||||
# Enrichment from the Discogs mirror. Two known shapes, selected by env so the same code runs
|
||||
# on ultra (the full dump) and on the VPS (dealgod's discogs_release subset):
|
||||
# full → table `release` cols artists_sort / thumb / estimated_weight (ultra)
|
||||
# vps → table `discogs_release` cols artist (no cover/weight) (dealgod-db)
|
||||
KIND = os.getenv("DISCOGS_MIRROR_KIND", "full")
|
||||
if KIND == "vps":
|
||||
_SQL = ("SELECT id, title, artist, NULL::text AS thumb, NULL::int AS weight "
|
||||
"FROM discogs_release WHERE id = ANY(:ids)")
|
||||
else:
|
||||
_SQL = ("SELECT id, title, artists_sort AS artist, "
|
||||
"COALESCE(thumb_local_url, thumb) AS thumb, estimated_weight AS weight "
|
||||
"FROM release WHERE id = ANY(:ids)")
|
||||
|
||||
|
||||
async def enrich(ids):
|
||||
"""release_id(s) → {id: {title, artist, thumb, weight}}. Best-effort: mirror down → {}."""
|
||||
ids = [i for i in set(ids) if i is not None]
|
||||
if not ids:
|
||||
return {}
|
||||
try:
|
||||
async with MirrorSession() as m:
|
||||
rows = await m.execute(text(_SQL), {"ids": ids})
|
||||
return {r["id"]: dict(r) for r in rows.mappings()}
|
||||
except Exception:
|
||||
return {}
|
||||
@ -1,7 +1,8 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy import text
|
||||
|
||||
from .db import get_db, MirrorSession
|
||||
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.
|
||||
@ -13,25 +14,10 @@ async def _all(db, table, where=""):
|
||||
return [dict(r) for r in rows.mappings()]
|
||||
|
||||
|
||||
async def _enrich(release_ids):
|
||||
"""Pull title/artist/cover from the discogs_full mirror for a set of release ids."""
|
||||
ids = [i for i in release_ids if i is not None]
|
||||
if not ids:
|
||||
return {}
|
||||
try:
|
||||
async with MirrorSession() as m:
|
||||
rows = await m.execute(text(
|
||||
"SELECT id, title, artists_sort, COALESCE(thumb_local_url, thumb) AS thumb "
|
||||
"FROM release WHERE id = ANY(:ids)"), {"ids": ids})
|
||||
return {r["id"]: dict(r) for r in rows.mappings()}
|
||||
except Exception:
|
||||
return {} # mirror down → scene still renders, just without covers/titles
|
||||
|
||||
|
||||
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("artists_sort"), m.get("thumb")
|
||||
r["title"], r["artist"], r["thumb"] = m.get("title"), m.get("artist"), m.get("thumb")
|
||||
return recs
|
||||
|
||||
|
||||
@ -48,7 +34,7 @@ async def scene(db=Depends(get_db)):
|
||||
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 _enrich({r["release_id"] for r in recs}))
|
||||
recs = _attach(recs, await mirror_enrich({r["release_id"] for r in recs}))
|
||||
|
||||
return {
|
||||
"space": active, "spaces": spaces,
|
||||
@ -72,7 +58,7 @@ async def crate_records(crate_id: int, db=Depends(get_db)):
|
||||
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 _enrich({r["release_id"] for r in recs}))
|
||||
recs = _attach(recs, await mirror_enrich({r["release_id"] for r in recs}))
|
||||
return {"ok": True, "crate_id": crate_id, "records": recs}
|
||||
|
||||
|
||||
|
||||
@ -7,7 +7,8 @@ from sqlalchemy import text
|
||||
|
||||
from . import __version__
|
||||
from .auth import require_token
|
||||
from .db import get_db, MirrorSession
|
||||
from .db import get_db
|
||||
from .mirror import enrich as mirror_enrich
|
||||
|
||||
# The wowplatter/v1 contract — RecordGod's front door (see RECORDGOD_PLAN.md §3).
|
||||
# Routes 1, 2, 5 live here. Routes 3-4 (price/labels) are deliberately NOT defined yet:
|
||||
@ -76,15 +77,10 @@ async def inventory_intake(body: IntakeIn, ident=Depends(require_token), db=Depe
|
||||
title = artist = None
|
||||
weight = None
|
||||
if body.release_id:
|
||||
try: # ponytail: mirror down → stage un-enriched rather than fail the intake
|
||||
async with MirrorSession() as mirror:
|
||||
m = (await mirror.execute(text(
|
||||
"SELECT title, artists_sort, estimated_weight FROM release WHERE id = :r"
|
||||
), {"r": body.release_id})).mappings().first()
|
||||
if m:
|
||||
title, artist, weight = m["title"], m["artists_sort"], m["estimated_weight"]
|
||||
except Exception:
|
||||
pass
|
||||
# ponytail: mirror down → stage un-enriched rather than fail the intake (enrich() swallows)
|
||||
meta = (await mirror_enrich([body.release_id])).get(body.release_id)
|
||||
if meta:
|
||||
title, artist, weight = meta["title"], meta["artist"], meta["weight"]
|
||||
|
||||
sku = body.sku or _new_sku()
|
||||
await db.execute(text("""
|
||||
|
||||
Loading…
Reference in New Issue
Block a user