- 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>
83 lines
3.0 KiB
Python
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()
|