[object Object]

← back to Nineoh Guide

deploy prep: db/setup-prod.sh one-shot prod populator, db/review.mjs content-review CLI, DEPLOY.md runbook (Vercel+Neon / EAS TestFlight / human review)

7fd16ebb6fb764db0092398ad046d2a95d625ab7 · 2026-07-25 12:23:20 -0700 · Steve

Files touched

Diff

commit 7fd16ebb6fb764db0092398ad046d2a95d625ab7
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 25 12:23:20 2026 -0700

    deploy prep: db/setup-prod.sh one-shot prod populator, db/review.mjs content-review CLI, DEPLOY.md runbook (Vercel+Neon / EAS TestFlight / human review)
---
 DEPLOY.md        | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 db/review.mjs    | 40 +++++++++++++++++++++++++++++++++++
 db/setup-prod.sh | 20 ++++++++++++++++++
 3 files changed, 124 insertions(+)

diff --git a/DEPLOY.md b/DEPLOY.md
new file mode 100644
index 0000000..8707bb2
--- /dev/null
+++ b/DEPLOY.md
@@ -0,0 +1,64 @@
+# Deploy runbook — Unofficial 90210 Guide
+
+Everything here is **gated** (your accounts / 2FA / spend). I've prepped each track
+so your part is just the interactive steps. Green-lit 2026-07-25.
+
+---
+
+## Track A — Web live on Vercel + Neon
+
+**You do (accounts):**
+1. Create a **Neon** project → copy the pooled connection string.
+2. Populate it (one command, from the repo):
+   ```bash
+   DATABASE_URL="postgres://…neon-pooled…" bash db/setup-prod.sh
+   ```
+   (schema → episodes → cast → bios → recaps → cast photos → Beverly Hills photos → news)
+3. **Vercel** → New Project → import this repo → set **Root Directory = `apps/web`**
+   (Next.js auto-detected). Add env vars:
+   - `DATABASE_URL` = the Neon string
+   - `NEXT_PUBLIC_SITE_URL` = your chosen public URL (e.g. https://yourdomain.com)
+   - `NEXT_PUBLIC_DMCA_EMAIL`, `NEXT_PUBLIC_PRIVACY_EMAIL` = real inboxes
+4. Deploy. Verify `/`, `/episodes`, `/cast`, `/news`, `/sitemap.xml`.
+5. Submit the sitemap in Google Search Console.
+
+**I've prepped:** `db/setup-prod.sh` (idempotent populator), all ingest scripts read
+`DATABASE_URL`, SITE_URL/emails are env-driven, sitemap+robots+JSON-LD ready.
+
+---
+
+## Track B — iOS on TestFlight (Expo EAS)
+
+**You do (Apple + Expo login):**
+```bash
+cd apps/mobile
+eas login                      # your Expo account
+eas init                       # writes real extra.eas.projectId into app.json
+# set the API base to your Vercel URL for the build:
+eas env:create --name EXPO_PUBLIC_API_BASE --value "https://yourdomain.com" --environment production
+eas build -p ios --profile preview   # prompts for Apple login + 2FA (only you can)
+```
+Then in **App Store Connect**: create the app record for bundle id
+`com.abrams.nineohguide` (change first if you prefer), add yourself as an internal
+TestFlight tester once the build uploads (~1 day Apple review).
+
+**I've prepped:** `apps/mobile/app.json` + `eas.json` (dev/preview/production profiles),
+App.tsx reads `EXPO_PUBLIC_API_BASE` first, watchlist + spoiler guard + easter eggs.
+
+**Do NOT** run `eas submit` (public App Store) until the content review below is done.
+
+---
+
+## Track C — Human content review (do before public)
+
+The 114 recaps + 14 bios are AI-drafts (`*_status = 'ai-draft'`). Review workflow:
+```bash
+node db/review.mjs status            # see what's ai-draft vs reviewed
+node db/review.mjs read 1            # read Season 1 recaps to check them
+node db/review.mjs ok-recaps 1       # mark Season 1 recaps reviewed
+node db/review.mjs ok-bios           # mark bios reviewed
+```
+Edit any recap in `db/recaps/s<N>.json` (or a bio in `db/bios.json`) and re-run
+`node db/apply-recaps.mjs <N>` / `node db/apply-bios.mjs` to update.
+
+Keep all photo attribution captions displayed — CC-BY-SA requires it.
diff --git a/db/review.mjs b/db/review.mjs
new file mode 100644
index 0000000..6645d25
--- /dev/null
+++ b/db/review.mjs
@@ -0,0 +1,40 @@
+// Human content-review workflow for the AI-drafted recaps + bios.
+//   node db/review.mjs status            → counts by review status
+//   node db/review.mjs read <season>     → print a season's recaps to read
+//   node db/review.mjs ok-recaps <season> → mark that season's recaps 'reviewed'
+//   node db/review.mjs ok-bios           → mark all bios 'reviewed'
+// Nothing here is destructive; it only flips the *_status gate columns.
+import pg from "pg";
+
+const DATABASE_URL =
+  process.env.DATABASE_URL ?? "postgresql://localhost/nineoh_guide?host=/tmp";
+const pool = new pg.Pool({ connectionString: DATABASE_URL });
+const [cmd, arg] = process.argv.slice(2);
+
+if (cmd === "status") {
+  const r = await pool.query(`select recap_status, count(*) from episodes group by 1 order by 1`);
+  const b = await pool.query(`select bio_status, count(*) from cast_people group by 1 order by 1`);
+  console.log("Recaps:"); console.table(r.rows);
+  console.log("Bios:"); console.table(b.rows);
+} else if (cmd === "read") {
+  const s = Number(arg);
+  const { rows } = await pool.query(
+    `select episode_number, title, summary_short_original
+       from episodes where season_number=$1 order by episode_number`,
+    [s]
+  );
+  for (const e of rows) console.log(`\nS${s}E${e.episode_number} · ${e.title}\n  ${e.summary_short_original ?? "(no recap)"}`);
+} else if (cmd === "ok-recaps") {
+  const s = Number(arg);
+  const r = await pool.query(
+    `update episodes set recap_status='reviewed' where season_number=$1 and recap_status='ai-draft'`,
+    [s]
+  );
+  console.log(`Season ${s}: ${r.rowCount} recaps marked reviewed.`);
+} else if (cmd === "ok-bios") {
+  const r = await pool.query(`update cast_people set bio_status='reviewed' where bio_status='ai-draft'`);
+  console.log(`${r.rowCount} bios marked reviewed.`);
+} else {
+  console.log("usage: node db/review.mjs status | read <season> | ok-recaps <season> | ok-bios");
+}
+await pool.end();
diff --git a/db/setup-prod.sh b/db/setup-prod.sh
new file mode 100755
index 0000000..1dc40e2
--- /dev/null
+++ b/db/setup-prod.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# One-shot: build + populate a fresh production database (Neon / Vercel Postgres).
+# Usage:  DATABASE_URL="postgres://…neon…" bash db/setup-prod.sh
+# Idempotent-ish: schema uses IF NOT EXISTS; ingest scripts skip/dedupe on re-run.
+set -euo pipefail
+
+: "${DATABASE_URL:?Set DATABASE_URL to the Neon/Vercel Postgres connection string}"
+cd "$(dirname "$0")/.."
+
+echo "1/9  schema"                 ; psql "$DATABASE_URL" -f db/schema.sql >/dev/null
+echo "2/9  seed show"              ; node db/seed.mjs
+echo "3/9  episodes (TVmaze)"      ; node db/ingest-tvmaze.mjs
+echo "4/9  cast names (TVmaze)"    ; node db/ingest-cast.mjs
+echo "5/9  cast bios"             ; node db/apply-bios.mjs
+echo "6/9  recaps (all seasons)"   ; node db/apply-recaps.mjs
+echo "7/9  cast photos (Wikimedia, license-checked)" ; node db/ingest-wikimedia-images.mjs
+echo "8/9  Beverly Hills photos"   ; node db/ingest-beverlyhills-photos.mjs
+echo "9/9  news index (Google News RSS)" ; node db/ingest-news.mjs
+
+echo "✅ production database populated."

← c7a0053 mobile forward: easter eggs — tap UNOFFICIAL badge 5x for hi  ·  back to Nineoh Guide  ·  games: integrate Steve's 6 original self-contained 90210 gam 592af52 →