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

View File

@@ -0,0 +1,20 @@
const { query } = require('../../utils/db')
async function insert({ userId = null, action, detail = null, ip = null }) {
const res = await query(
'INSERT INTO activity_log (user_id, action, detail, ip) VALUES (?, ?, ?, ?)',
[userId, action, detail, ip],
)
return res.insertId
}
async function list({ limit = 50, offset = 0 } = {}) {
return query(
'SELECT a.id, a.user_id, u.username, a.action, a.detail, a.ip, a.created_at ' +
'FROM activity_log a LEFT JOIN users u ON u.id = a.user_id ' +
'ORDER BY a.id DESC LIMIT ? OFFSET ?',
[limit, offset],
)
}
module.exports = { insert, list }

View File

@@ -0,0 +1,25 @@
const activityDb = require('./activity.db')
const logger = require('../../utils/logger')('activity')
/**
* Record an admin action. `detail` may be an object (stored as JSON). Never throws
* into the request path — logging must not break the action it records.
*/
async function log({ req, userId, action, detail }) {
try {
const resolvedUserId = userId ?? (req && req.user ? req.user.id : null)
const ip = req ? req.ip : null
const detailStr =
detail == null ? null : typeof detail === 'string' ? detail : JSON.stringify(detail)
await activityDb.insert({ userId: resolvedUserId, action, detail: detailStr, ip })
} catch (err) {
logger.error(`failed to record action "${action}"`, { error: err.message })
}
}
async function list(opts) {
return activityDb.list(opts)
}
module.exports = { log, list }