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>
This commit is contained in:
2026-06-26 20:58:32 -05:00
commit eef79e2403
41 changed files with 4195 additions and 0 deletions

72
server/db/seed.js Normal file
View File

@@ -0,0 +1,72 @@
require('dotenv').config()
const settingsDb = require('../src/model/settings/settings.db')
const wikiDb = require('../src/model/wiki/wiki.db')
const users = require('../src/model/users/users.model')
const { ensureSchema, close } = require('../src/utils/db')
const log = require('../src/utils/logger')('seed')
// Default settings — only inserted if the key does not already exist.
const DEFAULT_SETTINGS = {
site_mode: 'maintenance', // start safe: site is in maintenance until set live
site_mode_changed_at: '',
site_mode_changed_by: '',
maintenance_message:
'Mysticmoon is being shaped beneath a midnight sky. The site will return soon.',
status_message: 'In progress.',
homepage_teaser:
'Mysticmoon is still being shaped beneath a midnight sky. A quiet preview for ' +
'future news, screenshots, guides, and community notes as the world comes online.',
contact_email: process.env.CONTACT_TO || 'UOMysticmoon@gmail.com',
site_title: 'UOMysticmoon',
}
// The 8 starter wiki categories (editable later via the admin panel).
const WIKI_PAGES = [
['new-player-guide', 'New Player Guide', 'First steps, basic survival, and early goals.'],
['maps-atlas', 'Maps & Atlas', 'Regions, towns, routes, and travel notes.'],
['systems', 'Server Systems', 'Shard mechanics and custom features.'],
['items', 'Items & Rewards', 'Equipment, treasures, rewards, and curiosities.'],
['monsters', 'Monsters & Encounters', 'Creatures, bosses, spawns, and dangers.'],
['crafting', 'Crafting', 'Professions, materials, recipes, and tools.'],
['lore', 'Lore', 'Stories, places, factions, and mysteries.'],
['rules', 'Rules', 'Player conduct, shard expectations, and policies.'],
]
async function seedDefaults() {
for (const [key, value] of Object.entries(DEFAULT_SETTINGS)) {
await settingsDb.seedDefault(key, value)
}
for (const [slug, title, body] of WIKI_PAGES) {
await wikiDb.seedDefault(slug, title, body)
}
log.info('settings and wiki defaults ensured')
}
// Create the first admin from env vars, only when no users exist yet.
async function createInitialAdminFromEnv() {
const { ADMIN_USERNAME, ADMIN_PASSWORD } = process.env
if (!ADMIN_USERNAME || !ADMIN_PASSWORD) return
if ((await users.count()) > 0) return
await users.createUser({ username: ADMIN_USERNAME, password: ADMIN_PASSWORD, role: 'admin' })
log.info(`created initial admin "${ADMIN_USERNAME}"`)
}
// Allow running standalone: `npm run seed`
if (require.main === module) {
;(async () => {
try {
await ensureSchema()
await seedDefaults()
await createInitialAdminFromEnv()
} catch (err) {
log.error('seed failed', err)
process.exitCode = 1
} finally {
await close()
}
})()
}
module.exports = { seedDefaults, createInitialAdminFromEnv }