Files
website/README.md
whitlocktech eef79e2403 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

3.6 KiB

UOMysticmoon Website

Public site, wiki, and protected admin panel for the UOMysticmoon private Ultima Online shard. Built on the serverlinkr layered pattern: Express + MariaDB + JWT backend and a React + Vite frontend in the same repo, deployed with Docker Compose behind a Pangolin reverse proxy.

Build order: (1) backend (this phase) → (2) frontend design (Claude Design) → (3) frontend coding. See BACKEND_DESIGN.md for the full design.

Layout

server/   Express API (router → controller → model → db), MariaDB schema + seed
client/   React + Vite SPA  (added in the frontend phase)
Dockerfile, docker-compose.yml, .env.example

Quick start (local dev)

# 1. Start a MariaDB (or use your own and set DB_* in server/.env)
docker compose up -d db

# 2. Configure + install
cp server/.env.example server/.env      # edit DB_*, JWT_SECRET, ADMIN_USERNAME/PASSWORD
npm run install-server

# 3. Run the API (creates tables, seeds defaults + first admin on boot)
npm run server                           # http://localhost:3000  (API at /api/v1)

GET /api/health{ "status": "ok" } confirms it's up.

Deploy (Docker Compose)

cp .env.example .env                      # fill in DB creds, JWT_SECRET, admin, SMTP
docker compose up -d --build              # app on 0.0.0.0:3000, MariaDB on the internal network

Point Pangolin at the app container on port 3000. The auth cookie auto-detects HTTPS, so the admin panel works both via the LAN IP (HTTP) and through the proxy (HTTPS). The full app image build requires client/ (frontend phase); until then the server runs API-only.

Key endpoints

Group Base Auth
Auth /api/v1/auth (login, logout, me) cookie
Public /api/v1/public (settings, status, posts/:category, wiki, contact) none
Admin /api/v1/admin (dashboard, site-mode, posts, wiki, settings, activity, users) cookie (admin)

See BACKEND_DESIGN.md §4 for the complete contract.

Security notes

JWT in an httpOnly cookie · bcrypt hashing · login rate limiting · admin routes noindex · first admin seeded from env (no hardcoded credentials) · .env is git-ignored. SMTP is optional — the contact form falls back to a mailto: link when SMTP is not configured.

Logging

Every log line goes to both the console and a log file, timestamped and leveled (error / warn / info / debug):

2026-06-26T18:55:01.123Z INFO  [server] listening on http://0.0.0.0:3000 ...
2026-06-26T18:55:09.880Z INFO  [http] 192.168.1.40 admin POST /api/v1/auth/login 200 12 ms - 48 bytes
2026-06-26T18:55:14.402Z WARN  [auth] login failed {"username":"root","ip":"192.168.1.40"}
2026-06-26T18:55:20.110Z ERROR [error] GET /api/v1/public/wiki -> 500 ... {"stack":"..."}

What's captured: startup config banner, schema/seed steps, HTTP access logs (real client IP via trust proxy, the authenticated admin, method/URL/status/time/size), login success/failure, rate-limit hits, site-mode changes, all errors with stack traces, and graceful shutdown. Passwords and request bodies are never logged.

Env Default Meaning
LOG_LEVEL info console verbosity
FILE_LOG_LEVEL debug file verbosity (keeps a full record)
LOG_TO_FILE true set false for console-only
LOG_DIR <server>/logs (/app/logs in Docker) log directory
LOG_FILE app.log log file name

In Docker the log file is bind-mounted to ./logs/app.log on the host; docker compose logs -f app also shows the console stream.