Commit Graph

30 Commits

Author SHA1 Message Date
3ae4d1c3db docs(tree): add per-repo PROJECT_TREE snapshots + index them
Add auto-generated project-tree snapshots for the website, link, and
Android-app repos under docs/<repo>/PROJECT_TREE.md, and link them from
the README (adding a previously-missing android/ section). These files
are maintained going forward by the sync-project-tree CI workflow in each
source repo, which opens a PR here whenever the tracked layout changes.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 16:14:52 -05:00
0d5c0486dd docs(ntfy): ntfy publishes a host port for the external reverse proxy
Match the website change: the ntfy relay is reached through the public
reverse proxy, which runs outside the compose network and can only reach
a service via a published host port. Update the "no published host port /
internal-only publisher" claims in android/PLAN.md (§11 + §13) and
website/BACKEND_DESIGN.md to describe the published NTFY_HOST_PORT
(default 2586 -> ntfy:80), and note that both devices and the backend
publisher reach ntfy on the public origin.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 03:57:42 -05:00
30cfa0df1f docs(backend): note the /player/* group is role-agnostic self-service
Record that the whole /player/* router sits behind requireAuth only (not
requireRole('player')): staff are a superset of players, every handler is
self-scoped to the caller, and staff reach the identical handlers under
/admin/shard/*. This is why a staff account with linked characters gets
its "My characters" and personal notification streams on the mobile
client. Matches the code change in RunicGateway/website.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 02:18:22 -05:00
6e7da3acbe docs: trusted devices & MFA improvements (design + API/security/schema)
Add TRUSTED_DEVICES_MFA.md (the approved design/implementation plan) and fold
the feature into BACKEND_DESIGN §3 (trusted_devices + recovery_codes schema),
§4 (login/totp trust+recovery, /auth/me/trusted-devices*, recovery-codes*,
admin trusted-device + /mfa/reset routes), and §6 (trusted-device security
model + audit actions). Note the app-side trust/recovery flow in android PLAN §4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 23:39:05 -05:00
80f75b1b4c docs(website): add architecture diagram
Add docs/website/ARCHITECTURE.md (canonical copy of the website architecture
Mermaid diagram, with fuller notes) and embed the same diagram in the
website-README mirror. Mirrors the diagram added to the website repo README.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 21:13:29 -05:00
027fe2cbc3 docs(website): add Discord bot test plan
Document the test plan for the bot/ workspace — the last of the three website
npm workspaces without a suite (server + client landed in website PR #86).
Records the shared node --test conventions (no jest/vitest/jsdom; DB pool at a
dead port; Discord objects hand-faked) and maps the meaningful bot behavior to
lock: the normalize/duration/spam pure logic, the invite/site-api/internal-key
single-collaborator units, the messageFilter pipeline (bypass-first decision
order + fixed-duration mute + best-effort recording), and the models with real
shaping logic. Includes the CI + SonarQube coverage wiring to mirror PR #86 and
a suggested phasing.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-21 01:01:34 -05:00
28c5c228f7 docs(website): document the SPA Content-Security-Policy
Replace the vague "helmet with a CSP suited to the SPA" line with the actual
policy now implemented in server/src/app.js: per-directive sources and the
rationale for each non-'self' allowance (Google Fonts, inline React styles,
external/embedded images, same-origin REST+SSE), why upgrade-insecure-requests
is omitted, the scoped looser CSP for the /api/docs Swagger UI route, and the
X-Powered-By handling across the public and internal listeners.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-20 23:02:57 -05:00
752793f6c3 docs(android): spec Android App Links (assetlinks.json + build-time host)
Promote APP_LINKS.md from a deferred design note into an implementation spec
matching the website `feat/mobile-app-links` and android `feat/app-links`
branches: the server-side `/.well-known/assetlinks.json` route + `mobile_app_links_enabled`
toggle + additive redirect-allowlist entry, and the app-side `autoVerify`
intent-filter driven by a build-time `appLinkHost` (a single multi-tenant APK
cannot autoVerify open-ended shard domains, so App Links are a white-label /
first-party build opt-in; the custom scheme stays the permanent fallback).

Update PLAN.md §9 (M9 follow-up) and the §14 open item, and the redirect-URI
allowlist section of website/BACKEND_DESIGN.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-20 18:37:04 -05:00
1aba1ff93d docs(auth): design the mobile SSO authorization bridge (M9)
Record the plan before coding: native Android "Sign in with Google/Discord"
via a Mobile SSO Authorization Bridge that extends the existing /auth/sso/*
redirect flow and terminates in the existing mobile bearer tokens.

- BACKEND_DESIGN.md: mobile_auth_sessions / mobile_auth_codes schema, the
  /auth/mobile/sso/{start,exchange} contract, the two PKCE layers, state/CSRF,
  exact-match redirect-URI allowlist, TOTP parity, and the documented
  revocation-latency window.
- android/PLAN.md: promote §4.2's "possible later enhancement" to milestone M9
  (backend-first, mirroring M7); status note.
- android/APP_LINKS.md: new architecture note on the per-shard assetlinks.json
  / pairing multi-tenancy question (App Links deferred; custom scheme only now).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 17:01:57 -05:00
71cb181152 docs(android): M7 Part 2 landed — embedded ntfy distributor + push.ntfyUrl
Flip M7 Part 2 to landed (Android-app#15) and record the two implementation
decisions: the direct-ntfy embedded distributor (no UnifiedPush library — the
plan's stated likely path; foreground-service SSE, no second app, no Google
Play Services), and the small additive push.ntfyUrl settings field the app
needs to discover the relay (website#79). Document push.ntfyUrl +
NTFY_PUBLIC_URL in BACKEND_DESIGN.md.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 15:27:55 -05:00
cb10cee6f1 docs: M7 push backend contract + status (website#78)
- BACKEND_DESIGN.md: push_devices + notification_subscriptions tables (§3), the
  /auth/me/devices* + /auth/me/notifications/* API rows (§4), a push-notification
  design + security section (content-free tickles, PUBLIC_KINDS split, owner-keyed
  personal streams, SSRF endpoint guard, untrusted-relay model), and the ntfy
  compose service in the deploy section (§8).
- PLAN.md: flip M7 Part 1 (backend + docs) to in-review — status line, §8 item 3,
  §9 M7.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-20 05:15:25 -05:00
874fcfd79d docs: document /public version+brand; mark PLAN §8 items 4 & 6 done
Counterpart to RunicGateway/website#77.

- BACKEND_DESIGN.md: /public table now documents the new GET /public/version
  (DB-free identity/version), the version block on /public/status, and the brand
  block on /public/settings (per-shard theming: name/accent/logo/hero/favicon).
- android/PLAN.md: mark §8 item 4 (version/health) and item 6 (branding) DONE and
  update the prerequisite-progress summary — all v1 prerequisites are now done;
  only push notifications (item 3) remains and is post-v1 (M7). App M0–M4 unblocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-19 12:18:53 -05:00
63bce88bd7 docs: document /auth/me self surface; mark PLAN §8.1 done
Counterpart to RunicGateway/website#76 (role-agnostic /auth/me/* self surface).

- BACKEND_DESIGN.md: add the /auth/me/account* rows to the /auth API contract and
  a note that the surface reuses account.controller behind requireAuth (any role),
  so a client manages its own account without touching /admin.
- android/PLAN.md: mark §8 item 1 (role-agnostic self-service) DONE and update the
  prerequisite-progress summary; version/health (item 4) and branding (item 6)
  remain open, push (item 3) is post-v1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-19 04:56:55 -05:00
d87a45e914 docs(backend): document the password-reset endpoints and table
Add /auth/password/forgot and /auth/password/reset/:token to the API
contract and the password_resets table to the schema section, matching the
website change (RunicGateway/website feat/password-reset). Notes the
no-enumeration behaviour, single-use hashed-token model, and that the Android
app hands off to the web reset page (PLAN.md §4.2).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NgyHnrNa8WwG3doxvxjuCr
2026-07-19 03:57:22 -05:00
50244e5c2b docs(website): link moderation appeals doc from the feature index 2026-07-19 03:36:11 +00:00
4f0c282f3f docs(website): add moderation appeals + Discord reversal (Phase 6c/6d) 2026-07-19 03:34:26 +00:00
8a3e37ae73 docs(website): staff in-game location is admin/moderator-only
The "What each audience sees" table said the public "Staff online" list
is shown "with name + map location". Location is now privileged: the
server includes map/coords only for admin/moderator callers and strips
them from the payload for players and the public. Update the wording to
match (RunicGateway/website#72).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
2026-07-18 21:21:20 -05:00
Claude
faf98141b7 docs(website): rebrand website-README to Runic Gateway
Sync the extracted website README with the current repo README after the
org rename + BRAND_* branding work: UOMysticmoon → Runic Gateway as the
default identity (UOMysticmoon retained only as the example first instance),
UOM/link → RunicGateway/link, DB/cookie/TOTP defaults updated, and the new
Branding section added. Doc-local BACKEND_DESIGN.md links kept relative;
the .env.uomysticmoon.example link points at the website repo where it lives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XmHdsbnLzDMAVQkAoTQSBe
2026-07-18 16:55:20 -05:00
Claude
69c8afa7bc docs: add index README and source README snapshots 2026-07-17 23:57:50 -05:00
efe17d2965 Modernize email: Gmail OAuth2 sending, configured under Settings
Retire env-var SMTP basic-auth and send the contact form through Gmail over
OAuth2 (SMTP XOAUTH2), configured in Admin -> Settings -> Email via an in-app
"Connect Gmail" consent flow. Reuses the existing google SSO OAuth client; the
captured refresh token is stored AES-GCM-encrypted (write-only over the API,
never returned), mirroring the auth-provider and Discord-bot secret patterns.

- schema: new email_config singleton table (mirrors bot_config)
- model: emailConfig.{db,model} with encrypted refresh token + getSafe/getWithSecret
- mailer: nodemailer OAuth2 transport (client id/secret from the google provider
  row), contact recipient = contact_email setting, mailto: fallback preserved,
  plus sendTest()
- routes/controller: /admin/email config, connect start+callback (ssoState CSRF
  + PKCE), test, disconnect
- client: EmailDelivery section on the Settings page + api methods; Settings copy
  now spells out that contact_email is the delivery recipient
- docs/env: drop SMTP_*/CONTACT_TO from env examples; update README/BACKEND_DESIGN
- tests: emailConfig.model + mailer suites (8 new; full suite 142 pass)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XKeCQEJZr1AFJN4Bgcmvh3
2026-07-07 22:29:27 -05:00
7c23ecee05 Hero Phase 4: moon/badge/image elements + resize + snap grid
Final phase of the hero canvas editor (see HERO_EDITOR.md) — v1 complete.

- element tray adds moon, badge, and image; property panels:
  - moon: size / glow / color
  - badge: text / background / text color / corner radius
  - image: upload (/admin/uploads, >1MB warning) / width% / alt
- corner resize handle on selected elements (image→width%, moon→size,
  text_block→box width)
- 8px snap-grid toggle with a faint canvas grid overlay; drag snaps when on
- HeroElement: image element shows an "Upload an image" placeholder until a
  source is set (a srcless image never ships live)

Verified in-browser: all five element types add + edit; moon resized 64->104px
via the handle; snap grid overlays; a published moon + badge render on the live
portal; no console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:47:05 -05:00
0cb19a08d5 Hero Phase 3: element select / drag / edit (text_block + buttons)
Third phase of the hero canvas editor (see HERO_EDITOR.md).

- HeroElement: editor mode — inner content made non-interactive so the wrapper
  handles select/drag; selection outline; box width now canvas-relative
  (calc(100% - 36px)) so text blocks fit the smaller editor canvas
- HeroEditor: element tray (+ Text / + Buttons), click-to-select, native
  Pointer Events drag (position as % of the canvas, clamped), Delete key + panel
  delete, z-order (send back / bring forward), and per-type property panels:
  - text_block: per-line text / tag / font size (px) / color / bold, add+remove
    lines, alignment
  - buttons: per-item label / path / variant, add+remove, alignment
  empty-canvas click deselects (back to the background panel)
- theme.css: .hero-el-editable outline/hover/selected + grid helper

Verified in-browser: selecting shows the line editor, editing updates the canvas
live, drag repositions, add/delete and z-order work, deselect returns to the
background panel; no console errors. Moon/badge/image + resize + snap are Phase 4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:40:52 -05:00
3b076df09b Hero Phase 2: editor shell + background/overlay + draft/preview/publish
Second phase of the hero canvas editor (see HERO_EDITOR.md).

- new lib/heroLayout.js: shared defaultLayout/buildOverlay/heroBackground/
  parseLayout used by both the portal and the editor (Portal refactored onto it)
- new admin view HeroEditor.jsx at /admin/hero (+ sidebar nav + route):
  - live canvas preview (16:9) rendering the draft via HeroElement
  - background panel: image upload (/admin/uploads, >1MB warning), 3x3 position
    grid, overlay opacity slider — all update the canvas in real time
  - debounced (800ms) auto-save to hero_layout_draft
  - Publish (writes hero_layout + draft), Preview (opens /?preview=1), Revert
- Portal: ?preview=1 renders the draft via the admin settings endpoint, with a
  "showing unpublished draft" banner; normal load renders the published layout

No schema/dep changes. Verified end to end: overlay/position update the canvas,
auto-save writes the draft, publish updates the live portal, preview shows the
draft while the public page shows live. Element drag/properties land in Phase 3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:32:09 -05:00
2e382893fe Add hero canvas editor spec (corrected to current code)
Build contract for the WYSIWYG portal-hero editor on hero-feature, derived
from the design doc and corrected against the codebase:
- public settings is a whitelist (getPublic/PUBLIC_KEYS), so hero_layout must
  be added there — the doc's "no backend changes" was wrong
- moon is the reusable MoonDot component; route vs nav live in App.jsx vs
  AdminLayout.jsx; admin content is 1000px (canvas scales to fit)

Locked decisions: full v1, buttons as a first-class element type, pre-populate
the current hero on first run, native Pointer Events for drag. Phased plan
with per-phase exit checks. No schema change (JSON in settings).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 02:08:21 -05:00
effd606d4d Wiki Phase 4: full-text search + revision history
Final phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive): wiki_revisions table (per-save content snapshots).
The FULLTEXT index on wiki_pages(title, body) shipped in Phase 1.

Search:
- MATCH ... AGAINST natural-language search over title + body, ordered by
  relevance
- public: GET /public/wiki?q= (published only); admin: GET /admin/wiki?q=
  (all statuses)
- public wiki index gains a search box; admin list gains a search field

Revision history:
- every create/update snapshots the page into wiki_revisions
- admin endpoints: list revisions, get one, and restore (restore overwrites
  the page, rebuilds links, and appends a new revision — history stays
  append-only); logged as wiki.revision.restore
- editor gains a History modal: revision list + word-level diff (jsdiff) of a
  chosen revision against the current page, with one-click restore

Verified end-to-end: search matches body and title; two edits produce three
revisions; diff renders added/removed words; restore reverts and records a new
revision. No console errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 15:49:05 -05:00
62d0bf85b7 Wiki Phase 3: internal links, backlinks, and tags
Connectivity phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive new tables): wiki_tags, wiki_page_tags, wiki_links.

Internal links & backlinks:
- new wiki.links.js parses a saved body for /wiki/<slug> (and data-wiki-slug)
  targets; wiki_links is rebuilt on every save
- article shows a "Linked from" section (published backlinks) and renders
  links to non-existent pages as red links (server returns missing_links)
- editor gains an internal-link picker listing existing pages

Tags:
- pages accept a tags[] array; tags upsert on save, page tag-set is replaced,
  and orphaned tags are auto-pruned (on save and delete)
- public/admin list filter by ?tag=; /wiki/tags lists tags with published counts
- article shows tag chips; the index has a flat tag-filtered view; editor has a
  comma-separated tags field

Verified end-to-end: A->B backlink appears, red link detected, link index
rebuilds on edit, tag filtering + chips + pruning all work.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 11:25:15 -05:00
0748460338 Wiki Phase 2: TipTap rich-text editor + inline image uploads
Replaces the raw-HTML textarea in the wiki editor with a TipTap (ProseMirror)
WYSIWYG editor.

- new RichTextEditor component: bold/italic/strike, H2/H3, bullet+ordered
  lists, blockquote, code block, divider, link, inline image, undo/redo
- generalized POST /admin/uploads (reuses the screenshot multer config) →
  { url }; the editor uploads inline images through it
- editor output still passes through the Phase 1 server-side sanitizer on
  save and DOMPurify on render
- lazy-loaded as its own chunk so the public bundle doesn't ship TipTap
  (public ~82kB gzip; editor chunk ~106kB gzip loaded only in admin)
- RTE styling added to theme.css (toolbar, active states, prose content)

Verified: uploads serve as images; H2/H3 + lists + link + inline image
round-trip through the WYSIWYG and render sanitized on the public page.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:57:27 -05:00
1e9301c956 Wiki Phase 1: categories, drafts/publish, HTML sanitization
Foundation & safety phase of the wiki upgrade (see WIKI_UPGRADE.md).

Schema (additive, idempotent via ensureSchema):
- new wiki_categories table; wiki_pages gains category_id, excerpt,
  published, published_at, sort_order, and a FULLTEXT index
- migration ALTERs guarded with IF NOT EXISTS for existing databases
- seed reworked into 4 sections with the 8 starter pages assigned

Security:
- new utils/sanitizeHtml.js (sanitize-html allowlist); wiki bodies are
  sanitized on every save, and the article renders through DOMPurify
- strips <script>, event handlers (onerror), and javascript: URLs

Backend:
- public: published-only list with ?category filter + /wiki/categories
- admin: extended page CRUD, PATCH publish toggle, category CRUD;
  drafts visible to admin, hidden from public
- all writes logged to activity_log

Frontend:
- data-driven public wiki index (sections + real descriptions; removed
  hardcoded blurbs/Roman numerals) with ?category filtering
- article: category breadcrumb + sanitized render
- admin: Section/Status columns, draft/publish + section + excerpt in the
  editor, and a Manage sections modal

Verified end-to-end against MariaDB 11: migration clean, XSS neutralized,
drafts hidden, client builds, server boots.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 10:45:21 -05:00
6567623738 Add wiki upgrade spec and phased implementation plan
Detailed design + process for upgrading the CMS wiki: rich-text (TipTap)
editing, server+client sanitization, categories, drafts/publish, tags,
internal links/backlinks, inline images, FULLTEXT search, and revision
history. Staff-only (admin/editor). Additive, idempotent schema migration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 03:39:54 -05:00
efbafe7203 Initial commit: UOMysticmoon backend (Express + MariaDB + JWT)
- Layered API (router -> controller -> model -> db), serverlinkr pattern
- Public / auth / admin route groups; posts, wiki, settings, users, activity models
- JWT httpOnly-cookie auth (Secure auto-detected: LAN HTTP + Pangolin HTTPS)
- Site LIVE/MAINTENANCE mode with admin preview bypass
- Dual file+console logging (info/warn/error/debug) + HTTP access logs
- Docker Compose (app + MariaDB), schema.sql + seed, .env.example
- Verified end-to-end against MariaDB (27/27 smoke checks)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:58:32 -05:00