RECORDGOD/migrate_virtual.py
type-two 8b48010b50 feat(virtual): lift the 3D store out of WordPress — scene API + raw Three.js renderer
- migrate_virtual.py: all 15 wp_rmp_disc_virtual_* tables → Postgres (~700 rows).
- app/virtual.py (public): GET /virtual/scene (geometry + slim front covers enriched
  from discogs_full), /virtual/crate/{id}/records (digging), /virtual/locate (locate-stock).
  Replaces payload.php — no WordPress bootstrap.
- webstore/index.html: walkable raw Three.js store (room/racks/crates from the data,
  pointer-lock FPS, click-to-dig hook), served same-origin at /store.
- Proven: 51 racks, 346 crates, 277 covers; locate release→crate works.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 17:59:52 +10:00

83 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Lift the WowPlatter 3D-store geometry (wp_rmp_disc_virtual_*) into RecordGod Postgres.
These tables are read-mostly scene config (~700 rows total) — we do NOT redesign them, just
relocate verbatim so the RecordGod /virtual/scene endpoint can serve the same payload payload.php
built, but from Postgres instead of a full WordPress bootstrap. Auto-discovers every
wp_rmp_disc_virtual* table and mirrors it as virtual_* in recordgod.
python migrate_virtual.py
"""
import os
import pathlib
import re
import pymysql
import psycopg
WP_CONFIG = os.getenv("WP_CONFIG", "/opt/homebrew/var/www/monsterrobot.localsite/wp-config.php")
PG_DSN = os.getenv("RECORDGOD_DSN", "postgresql://localhost/recordgod")
SOCK = os.getenv("MARIA_SOCK", "/opt/homebrew/var/mysql/mysql.sock")
def wp_creds():
t = pathlib.Path(WP_CONFIG).read_text()
g = lambda k: re.search(rf"'{k}',\s*'([^']*)'", t).group(1)
return g("DB_NAME"), g("DB_USER"), g("DB_PASSWORD")
def pgtype(mysql_type):
t = mysql_type.lower()
if t in ("tinyint", "smallint", "mediumint", "int", "integer", "bigint"):
return "bigint"
if t in ("decimal", "numeric"):
return "numeric"
if t in ("float", "double"):
return "double precision"
if t in ("datetime", "timestamp"):
return "timestamptz"
if t == "date":
return "date"
return "text" # varchar/char/text/longtext/enum/json — kept as-is; geometry config, not redesigned
def main():
name, user, pw = wp_creds()
my = pymysql.connect(unix_socket=SOCK, user=user, password=pw, database=name,
cursorclass=pymysql.cursors.DictCursor)
pg = psycopg.connect(PG_DSN, autocommit=False)
c = my.cursor()
c.execute("""SELECT table_name FROM information_schema.tables
WHERE table_schema=%s AND table_name LIKE 'wp_rmp_disc_virtual%%'
ORDER BY table_name""", (name,))
tables = [r["table_name"] for r in c.fetchall()]
for src in tables:
dst = src.replace("wp_rmp_disc_", "") # virtual_crate, virtual_rack, ...
c.execute("""SELECT column_name, data_type FROM information_schema.columns
WHERE table_schema=%s AND table_name=%s ORDER BY ordinal_position""",
(name, src))
cols = c.fetchall()
coldefs = ", ".join(f'"{col["column_name"]}" {pgtype(col["data_type"])}' for col in cols)
names = [col["column_name"] for col in cols]
pg.execute(f'DROP TABLE IF EXISTS "{dst}"')
pg.execute(f'CREATE TABLE "{dst}" ({coldefs})')
c.execute(f"SELECT * FROM `{src}`")
rows = [tuple(r[n] for n in names) for r in c.fetchall()]
if rows:
ph = ",".join(["%s"] * len(names))
quoted = ",".join(f'"{n}"' for n in names)
with pg.cursor() as cur:
cur.executemany(f'INSERT INTO "{dst}" ({quoted}) VALUES ({ph})', rows)
print(f" {dst:28} {len(rows):>5}")
pg.commit()
print("done.")
if __name__ == "__main__":
main()