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:
90
server/src/router/v1/public/public.controller.js
Normal file
90
server/src/router/v1/public/public.controller.js
Normal file
@@ -0,0 +1,90 @@
|
||||
const posts = require('../../../model/posts/posts.model')
|
||||
const wiki = require('../../../model/wiki/wiki.model')
|
||||
const settings = require('../../../model/settings/settings.model')
|
||||
const mailer = require('../../../utils/mailer')
|
||||
|
||||
const log = require('../../../utils/logger')('public')
|
||||
|
||||
async function getSettings(req, res) {
|
||||
try {
|
||||
return res.json(await settings.getPublic())
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function getStatus(req, res) {
|
||||
try {
|
||||
return res.json({
|
||||
mode: (await settings.get('site_mode')) || 'live',
|
||||
status_message: (await settings.get('status_message')) || '',
|
||||
})
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function getPosts(req, res) {
|
||||
const { category } = req.params
|
||||
if (!posts.isValidUrlCategory(category)) {
|
||||
return res.status(404).json({ message: 'Unknown category' })
|
||||
}
|
||||
try {
|
||||
return res.json(await posts.listPublished(category))
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function getPost(req, res) {
|
||||
const { category, idOrSlug } = req.params
|
||||
if (!posts.isValidUrlCategory(category)) {
|
||||
return res.status(404).json({ message: 'Unknown category' })
|
||||
}
|
||||
try {
|
||||
const post = await posts.getPublished(category, idOrSlug)
|
||||
if (!post) return res.status(404).json({ message: 'Not found' })
|
||||
return res.json(post)
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function getWikiList(req, res) {
|
||||
try {
|
||||
return res.json(await wiki.list())
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function getWikiPage(req, res) {
|
||||
try {
|
||||
const page = await wiki.getBySlug(req.params.slug)
|
||||
if (!page) return res.status(404).json({ message: 'Not found' })
|
||||
return res.json(page)
|
||||
} catch (err) {
|
||||
return res.status(500).json({ message: 'Internal Server Error' })
|
||||
}
|
||||
}
|
||||
|
||||
async function contact(req, res) {
|
||||
const { name, email, message } = req.body
|
||||
try {
|
||||
const result = await mailer.sendContactMessage({ name, email, message })
|
||||
return res.json(result)
|
||||
} catch (err) {
|
||||
log.error('contact send failed', err)
|
||||
return res.status(502).json({ message: 'Could not send message right now.' })
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getSettings,
|
||||
getStatus,
|
||||
getPosts,
|
||||
getPost,
|
||||
getWikiList,
|
||||
getWikiPage,
|
||||
contact,
|
||||
}
|
||||
30
server/src/router/v1/public/public.routes.js
Normal file
30
server/src/router/v1/public/public.routes.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const express = require('express')
|
||||
const { body } = require('express-validator')
|
||||
|
||||
const ctrl = require('./public.controller')
|
||||
const siteMode = require('../../../middleware/siteMode')
|
||||
const validate = require('../../../middleware/validate')
|
||||
const { contactLimiter } = require('../../../middleware/rateLimit')
|
||||
|
||||
const publicRouter = express.Router()
|
||||
|
||||
// Always available (so the client can render the maintenance page + contact).
|
||||
publicRouter.get('/settings', ctrl.getSettings)
|
||||
publicRouter.get('/status', ctrl.getStatus)
|
||||
publicRouter.post(
|
||||
'/contact',
|
||||
contactLimiter,
|
||||
body('message').isString().trim().notEmpty().isLength({ max: 5000 }),
|
||||
body('email').optional({ values: 'falsy' }).isEmail(),
|
||||
body('name').optional({ values: 'falsy' }).isString().trim().isLength({ max: 100 }),
|
||||
validate,
|
||||
ctrl.contact,
|
||||
)
|
||||
|
||||
// Content — gated by site mode (admins with a valid token bypass for preview).
|
||||
publicRouter.get('/posts/:category', siteMode, ctrl.getPosts)
|
||||
publicRouter.get('/posts/:category/:idOrSlug', siteMode, ctrl.getPost)
|
||||
publicRouter.get('/wiki', siteMode, ctrl.getWikiList)
|
||||
publicRouter.get('/wiki/:slug', siteMode, ctrl.getWikiPage)
|
||||
|
||||
module.exports = publicRouter
|
||||
Reference in New Issue
Block a user