Phases 3-4 of docs/website/THEMING_AND_NAV.md. Three presets, the curated font shortlist, and /admin/appearance to drive them. The design put the presets in theme.css as [data-theme] blocks. That does not work: SiteContext writes --accent as an inline style on <html>, which beats any attribute-selector block, so a preset's accent would have been painted over by BRAND_ACCENT_COLOR while getPublic().brand.accent -- the value the Android app themes itself from -- reported the other one. Presets now live in server/src/config/themePresets.js. themeResolve.js layers :root <- preset <- custom per field into a token map, getPublic() returns it as `theme`, and the client writes it onto <html>. One authority for the merge, and brand.accent is by construction the accent the site paints. theme.css's :root is untouched, so an instance with no row gets no theme block and renders as today. Also: presets carry the full 15-token palette (eight would have left Fantasy with blue-grey borders); the option catalog is served from GET /settings/theme/options so the form cannot offer what the server rejects; validation is strict on write and forgiving on read; and the Discord bot now fetches the effective accent instead of its boot-time env copy. Fixes a Phase 0 bug in passing: settings/nav.controller.js imported the logger factory rather than calling it, so a DB fault would have thrown a TypeError inside the catch instead of returning 500. Co-Authored-By: Claude <noreply@anthropic.com>
59 lines
1.7 KiB
JavaScript
59 lines
1.7 KiB
JavaScript
require('dotenv').config()
|
|
|
|
const app = require('./app')
|
|
const bootstrap = require('./bootstrap')
|
|
const createLogger = require('./utils/logger')
|
|
const discordManager = require('./discord/discordManager')
|
|
const brand = require('./brand')
|
|
const pkg = require('../package.json')
|
|
|
|
const log = createLogger('server')
|
|
const PORT = Number(process.env.PORT) || 4100
|
|
const HOST = '0.0.0.0'
|
|
|
|
async function start() {
|
|
log.info(`starting ${brand.name} bot v${pkg.version}`, {
|
|
node: process.version,
|
|
logFile: createLogger.logFilePath || 'disabled (console only)',
|
|
})
|
|
|
|
const server = app.listen(PORT, HOST, () => {
|
|
log.info(`internal API listening on http://${HOST}:${PORT}`)
|
|
})
|
|
|
|
// Pick up the site's effective accent before the first embed can be built.
|
|
// Best-effort by design: it never rejects, and a site that is not up yet just
|
|
// leaves the bot on its BRAND_ACCENT_COLOR default until the next read.
|
|
await brand.refreshAccent()
|
|
|
|
await bootstrap()
|
|
|
|
setupShutdown(server)
|
|
}
|
|
|
|
function setupShutdown(server) {
|
|
let closing = false
|
|
const shutdown = async (signal) => {
|
|
if (closing) return
|
|
closing = true
|
|
log.warn(`${signal} received — shutting down gracefully`)
|
|
server.close(() => log.info('internal API closed'))
|
|
await discordManager.stop()
|
|
await createLogger.close()
|
|
process.exit(0)
|
|
}
|
|
|
|
process.on('SIGINT', () => shutdown('SIGINT'))
|
|
process.on('SIGTERM', () => shutdown('SIGTERM'))
|
|
process.on('unhandledRejection', (reason) => log.error('unhandledRejection', { reason: String(reason) }))
|
|
process.on('uncaughtException', (err) => {
|
|
log.error('uncaughtException', err)
|
|
process.exit(1)
|
|
})
|
|
}
|
|
|
|
start().catch((err) => {
|
|
log.error('failed to start bot', err)
|
|
process.exit(1)
|
|
})
|