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:
20
server/src/model/activity/activity.db.js
Normal file
20
server/src/model/activity/activity.db.js
Normal 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 }
|
||||
25
server/src/model/activity/activity.model.js
Normal file
25
server/src/model/activity/activity.model.js
Normal 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 }
|
||||
Reference in New Issue
Block a user