This commit is contained in:
2026-04-20 18:05:36 +00:00
parent d73422555d
commit 33b1f276c6
26 changed files with 598 additions and 183 deletions

View File

@@ -31,6 +31,7 @@ When the user asks for direct fixes, make them — but still avoid unsolicited r
- `npm run start:1p` / `start:test:1p` — inject secrets via 1Password CLI (`op run`).
- `npm run test-mongodb` / `test-mongodb:test` — connectivity probe; no test suite exists.
- No lint step configured. No unit/integration test framework.
- **Verification:** prefer `node --check <file>` for syntax, and inline `node -e "..."` for behavior. For tightly-coupled modules, stub deps via `Module._resolveFilename` override (see `services/channelQueue.js` tests in session history).
Many files under `scripts/` are one-shot maintenance utilities (backups, user lookups, transcript mapping). They are not wired into CI or into the bot's runtime.
@@ -41,7 +42,7 @@ Node.js **CommonJS** · Discord.js 14 · Express 5 · Mongoose 6 (MongoDB Atlas)
1. **CommonJS only.** `require` / `module.exports`. Never `import`.
2. **Read before write.** Never propose or make changes to a file without first reading its current contents.
3. **Route channel operations through `services/channelQueue.js`**: `enqueueSend(channel, ...args)`, `enqueueRename(channel, name)`, `enqueueMove(channel, categoryId)`. Direct `channel.send(...)` / `channel.setName(...)` calls bypass ordering + rate-limit protection. **Audit note:** several files still bypass (`handlers/commands.js`, `handlers/buttons.js`, `handlers/accountinfo.js`, `handlers/setup.js`, `services/tickets.js`, `services/debugLog.js`, `services/patternChecker.js`, `services/surgeChecker.js`, `services/chatAlertChecker.js`, `services/staffChannel.js`, `routes/bosscord.js:191`)treat as in-flight cleanup, migrate sends incrementally when touching those files.
3. **Route channel operations through `services/channelQueue.js`**: `enqueueSend`, `enqueueRename`, `enqueueMove`, `enqueueDelete` (awaits both rename+send tails before deleting). Bypass sites are tagged `// TODO(queue-migrate):`grep to find them; migrate incrementally when touching.
4. **Logging is fire-and-forget.** Never `await logSystem/logError/logAutomation/logGmail/...`. Chain `.catch(() => {})` instead.
5. **Use `ChannelType` enum from `discord.js`**, not bare integers (`0`, `4`, `5`, `12`, `15`).
6. **Mongoose schema defaults:** pass function references (`default: Date.now`), never invocations (`default: Date.now()` pins all documents to module-load time).
@@ -62,6 +63,8 @@ Single Node process. Entry: `broccolini-discord.js`.
- **Public (`app`)** — `GET /` healthcheck + `/api/*` (bOSScord consumer). CORS origin is `process.env.BOSSCORD_CLIENT_ORIGIN` (default `http://100.114.205.53:3081`). Rate-limited 60 req/min/IP. Auth: `Authorization: Bearer ${BOSSCORD_API_KEY}`.
- **Internal (`internalApp`)** — `127.0.0.1` only, `/internal/*`. Rate-limited 10 req/min. Auth: `x-internal-secret` header. `POST /config` enforces an explicit `ALLOWED_CONFIG_KEYS` allowlist; unknown keys return 400. `POST /restart` exits the process so the container supervisor restarts it.
`routes/internalApi.js` is required at module scope by `broccolini-discord.js` *before* the parent's `module.exports` populates — reaching back to the parent (e.g., `trackInterval`, `trackTimeout`, `clearGmailPollInterval`) must use a **lazy `require('../broccolini-discord')` inside the handler**, not a top-level destructure.
### Intervals & shutdown
- Every `setInterval` inside `ready` is wrapped via `trackInterval(...)` into the module-scoped `activeIntervals` Set.
- `handleShutdown(signal)` is idempotent (`shuttingDown` flag): clears every tracked interval, closes both HTTP servers, calls `client.destroy()`, calls `closeMongoDB()`, then `process.exit(0)`. Wired to SIGTERM/SIGINT.
@@ -86,6 +89,9 @@ Every `interactionCreate` branch runs through `runHandler(name, interaction, fn)
- In-memory counters bucketed into `today` / `week` / `month`, with scheduled resets at midnight / Monday 00:00 / 1st 00:00.
- `escalatingCooldowns` entries carry a `lastUsed` timestamp; a 6-hour interval prunes entries idle for >48h. The cleanup interval is `.unref()`-ed so shutdown isn't blocked by it.
### `.env` persistence (`services/configPersistence.js`)
- Values are stored in **backtick** containers because dotenv v17 only decodes `\n`/`\r` inside `"…"` (not `\"` or `\\`) — backticks preserve quotes + literal newlines verbatim. `readEnvFile` joins multi-line backtick values; `writeEnvFile` re-reads after write and throws on key-count mismatch.
## bOSScord integration
bOSScord is a separate React + Express cockpit app that consumes this bot's `/api/*` endpoints.
@@ -119,3 +125,5 @@ Names and full tables are in `README.md` / `.env.example`. Ones that commonly tr
## Settings site
`settings-site/` contains a separate Express app (`settings-site/server.js`) for the admin UI — it talks to `internalApp` via `INTERNAL_API_SECRET`. It is **not** part of this bot's process. Changes to the bot's `/internal/config` contract (e.g., the `ALLOWED_CONFIG_KEYS` set) may break the settings UI. See `settings-site/CLAUDE.md` for that subproject's architecture and conventions.
Its Docker build context is `settings-site/` only — parent-repo files (e.g., `utils.js`) are unreachable inside the container. Any shared helper must be inlined or the build context widened in `docker-compose.yml`.