# shodh.net Cutover Checklist

Phase 5 deliverable. Everything in "Before DNS" is code-complete and verified
locally; the "Cutover day" and "After" sections are operational steps for when
the domain moves to the new platform.

## Already done (verified 2026-08-26)

- [x] Legacy redirect map: every Joomla URL 301s to its new home
      (`shodh-sanchayan-ui/legacy-redirects.js`, consumed by `next.config.js`)
      — **90/90 URLs verified** by `scripts/verify-redirects.mjs`
- [x] Per-article download URLs resolve via `/journal/archive/download/{fileId}`
      (legacy phocadownload file id → imported article → counted PDF download)
- [x] Issue category URLs resolve via `/journal/archive/legacy/{catId}`,
      including the legacy site's own broken `id=0` Vol.8/1 link (mapped to cat 50)
- [x] Legacy direct files preserved under `/downloads/` (certification form,
      membership forms EN/HI, Hindi fonts ZIP, UGC PDFs — including the English
      gazette whose legacy link was missing its .pdf extension)
- [x] `sitemap.xml` (static routes + all archive issues) and `robots.txt`
      (crawlers blocked from authenticated areas)
- [x] Schema.org structured data (PublicationIssue / PublicationVolume /
      Periodical with both ISSNs / ScholarlyArticle) on archive issue pages
- [x] Production build green (47 routes)

## Before DNS cutover (hosting/ops decisions for the owner)

- [ ] Choose production hosting for the two services (UI: Node/Next standalone
      or Vercel; API: Docker on a VPS; Postgres: managed or same VPS)
- [ ] Set production env vars: `NEXT_PUBLIC_API_URL`, `NEXT_PUBLIC_SITE_URL`
      (both baked in at UI **build** time — changing them later needs a rebuild),
      DB credentials, a freshly generated `JWT_SECRET` (never the staging one),
      `CORS_ORIGINS`, `UI_BASE_URL`, `MAIL_USER`/`MAIL_PASS`, `AI_API_KEY`,
      `REVIEW_FEE_PAISE`/`REVIEW_GST_PERCENT` if they ever change
- [ ] Fill in the UPI payee in the CMS (`/admin/page-content` → PAYMENT). Until
      the UPI id and account name are both set, the author payment page refuses
      to render — deliberately, so nobody pays to a blank payee
- [ ] Deal with the seeded `admin@shodh.net` account. V1 creates an ADMIN row
      whose password_hash is the literal `$2a$10$placeholder_hash_change_on_first_login`
      — not a valid bcrypt hash, so no password can ever match it and login
      always returns 401 (verified on a fresh install). It is therefore not a
      backdoor, but it *is* a live ADMIN row tied to a real mailbox: anyone able
      to receive mail at admin@shodh.net could take it over through password
      reset. Either delete it or set a real password with must_change_password.
- [ ] Create Dr. Singh's real admin account with a forced password change, and
      **do not** seed the UAT users. The four test admins have known passwords;
      if any staging seed reaches production, that is a live admin backdoor
- [ ] Decide whether the review fee is switched on, and at what amount. This is
      now the editor's, in the admin portal under **Journal Settings** — no
      deploy needed. Switching it off releases any paper held for a fee, keeps
      declared payments in the reconciliation queue, and hides the public
      Publication Fee page. (`REVIEW_FEE_PAISE` survives only as the fallback
      for an instance whose settings table predates that screen.)
- [ ] **Ask Dr. Singh:** should the public **Subscription Fee** page — journal
      membership pricing, Life ₹3000 / Five Year ₹1400 / Three Year ₹800 /
      Annual $15 — also disappear when the review fee is switched off? It is a
      separate revenue stream and does not mention the review fee, so today it
      stays visible. Never discussed with him; left as-is deliberately rather
      than guessed at, because hiding a live price list would cost him members
- [ ] Run Flyway against the production DB (V1–V28) and the two imports:
      archive manifest (enriched) and registry entries; CMS pages are seeded by
      migration automatically
- [ ] Copy `uploads/archive/` (169 MB, 471 PDFs) to production storage — the
      database holds only the key (`archive_articles.pdf_key`), never the bytes,
      so a DB dump alone leaves every download 404ing
- [ ] Obtain the hosting backup ONE more time for Vol.15/16 print-copy PDFs
      (the earlier attempt was interrupted; DB dumps are already secured) —
      then re-run the idempotent archive import
- [ ] TLS certificate for shodh.net on the new host

## Cutover day

- [ ] **Verify production before the domain moves.** Point a temporary name
      (e.g. `new.shodh.net`) at the production instance and run the full
      verification against it. Everything below should be a confirmation, not a
      discovery — the one thing you cannot rehearse is the DNS switch itself
- [ ] **Re-export the legacy data last.** The live Joomla site keeps taking
      registry entries and downloads right up to the moment DNS moves; anything
      exported earlier is already stale. Re-run the read-only MySQL dump and the
      FTP mirror, then re-run the idempotent imports
- [ ] Lower the shodh.net DNS TTL a day in advance (e.g. to 300s)
- [ ] Point the A/AAAA records at the new host
- [ ] Run `node scripts/verify-redirects.mjs https://shodh.net` — all 90 must pass
- [ ] Spot-check: home page, an archive issue + PDF download, a registry page,
      a CMS page (guidelines), the contact form, login + submit wizard
- [ ] Submit the new sitemap in Google Search Console
      (property already verified via the legacy meta tag)

## After cutover

- [ ] Keep the old hosting account alive but idle for ≥1 month (rollback path
      and reference) — production stays read-only per the standing rule.
      Rollback is putting the old A record back, so keep the previous value
      written down before changing it
- [ ] Watch the first outgoing emails. A sending address with no history can
      land in spam; check that a password reset actually arrives, in an inbox
      rather than a junk folder
- [ ] Watch 404s in the new app's logs for legacy URLs the inventory missed;
      add rules to `legacy-redirects.js` as they surface
- [ ] Update Search Console / analytics to the new stack (GA4 if desired)
- [ ] Decide retirement date for the old hosting; take a final backup before
      cancelling
