# CollabCentral

A multi-bot mass-messaging service for Webex. Each bot has its own token,
avatar, label, and per-user access list, and users can only see the bots
and jobs they're authorized on.

Under one server you can host any number of bots (Novi, TechUpdates, …) —
each one exposes the same UI at:

- `/CollabCentral/<botName>/sendMessage.html` — compose and send.
- `/CollabCentral/<botName>/monitorJobs.html` — track running / scheduled / completed jobs.
- `/CollabCentral/<botName>/jobDetail.html?jobId=…` — detailed per-job view.

## Requirements

- Node.js 20+.
- A single Webex integration used for OAuth on all bots.
- A Webex service account (used server-side for group/people lookups; its
  refresh token is stored in `config/token.json` and rotated automatically).
- One Webex bot per app you want to host.

## Quick start (local)

1. Copy `.env.example` to `.env` and fill in the values:
   ```
   SERVER_PORT=1450
   CRON_TIMEZONE=America/New_York
   OAUTH_CALLBACK_URL_TEMPLATE=https://bot.example.com/CollabCentral/:app/oauth
   WEBEX_INTEGRATION_CLIENT_ID=...
   WEBEX_INTEGRATION_CLIENT_SECRET=...
   WEBEX_SERVICE_ACCOUNT_CLIENT_ID=...
   WEBEX_SERVICE_ACCOUNT_CLIENT_SECRET=...
   GOOGLE_TRANSLATE_API_KEY=...
   ```

2. Copy `config/botTokens.example.json` to `config/botTokens.json` and put
   in the bot tokens for each app:
   ```json
   {
       "novi":        { "token": "…", "enabled": true },
       "techupdates": { "token": "…", "enabled": true }
   }
   ```

3. Create `config/token.json` with the initial service-account OAuth tokens
   (access + refresh). After the first refresh, the cron job rewrites this
   file automatically.

4. Install dependencies and start:
   ```
   npm install
   npm start
   ```

`npm start` uses `node --env-file=.env`, so no external process manager is
required to load env vars during local development.

## Adding a new bot

Say you want to add a bot called `alerts`.

1. **Webex bot** — create the bot at developer.webex.com. Copy its access token.
2. **Bot token** — add an entry to `config/botTokens.json`:
   ```json
   "alerts": { "token": "<bot access token>", "enabled": true }
   ```
   Setting `enabled: false` will keep the bot listed but return 404 for all
   `/CollabCentral/alerts/*` routes.
3. **Bot metadata + authorized users** — edit `config/config.json` and add
   an entry under `webex.bot`:
   ```json
   "alerts": {
       "label": "Ops Alerts",
       "authorized": {
           "<webexPersonId>": {
               "id": "<webexPersonId>",
               "displayName": "…",
               "email": "…",
               "avatar": "…",
               "groups": [ { "name": "…", "alias": "…", "id": "<webexGroupId>" } ]
           }
       }
   }
   ```
   Only person IDs listed under `authorized` can log into that bot's UI.
4. **Icons** — drop `alerts.png` and `alerts.ico` into `html/` (they're
   served by the per-bot static mount).
5. **OAuth redirect URI** — in the Webex integration used for OAuth, register
   `https://<your-host>/CollabCentral/alerts/oauth` as an allowed redirect
   URI. Without this step, users get an OAuth error on login.
6. Restart the server (or the container). The bot's avatar is fetched once
   at startup via Webex `/people/me` using the token you supplied in step 2.

## Docker

`Dockerfile` builds a stateless image. Config, tokens, uploads, and jobs
data are expected to be mounted at runtime.

```
docker build -t collabcentral .
docker run -d \
    --name collabcentral \
    --env-file /path/to/.env \
    -v /path/to/config:/usr/src/app/config \
    -v /path/to/uploads:/usr/src/app/uploads \
    -p 1450:1450 \
    collabcentral
```

The `config/` mount should contain at minimum `config.json`, `botTokens.json`,
`token.json`, `languages.json`, and (if you want to preserve state)
`jobs.json` and `userPrefs.json`.

## File layout

Committed to the repo:

- `index.js` — Express server, OAuth, cron jobs, Webex API integration,
  and the send queue.
- `html/` — frontend (sendMessage / monitorJobs / jobDetail pages, per-bot
  icons, shared JS libraries).
- `config/config.json` — server settings, per-bot labels, and the
  authorized-user table for each bot.
- `config/languages.json` — supported translation languages.
- `config/botTokens.example.json` — template for `botTokens.json`.
- `.env.example` — template for `.env`.
- `Dockerfile`, `.dockerignore`, `.gitignore`, `package.json`,
  `package-lock.json`.

Runtime state (gitignored, not committed):

- `.env` — secrets and deployment-specific URLs.
- `config/botTokens.json` — per-bot access tokens.
- `config/token.json` — service-account OAuth tokens (rotated by the
  refresh cron).
- `config/jobs.json` — building / running / scheduled / completed jobs.
- `config/userPrefs.json` — per-user preferences (language, etc.).
- `uploads/` — CSVs of recipient IDs and any attached images.

## Testing

Unit tests for the pure helpers extracted into `lib/helpers.js` run under
Node's built-in test runner — no additional dev dependencies required.

```
npm test
```

The suite covers bot enable/disable gating (`getBotToken`, `isBotEnabled`,
`getBotConfig`), per-bot draft isolation (`buildingKey`, `jobsForApp`), the
authorization check (`isAuthorized`), OAuth URL construction (`buildAuthUrl`,
`getOAuthRedirectUri`), the retention filter (`cleanCompletedJobs`), and the
duration formatter (`msToTime`). Adding a new helper? Add it to `lib/helpers.js`
and cover it in `test/helpers.test.js` — keeping the state-carrying wrappers
in `index.js` thin means each helper can be tested without booting the server.

## Behavior notes

- **Completed-job retention**: the daily cleanup cron (default `01:10` in
  `CRON_TIMEZONE`) drops entries from `jobs.completed` older than 30 days.
  Change `COMPLETED_RETENTION_DAYS` in `index.js` to adjust.
- **Send concurrency**: a shared `p-queue` limits outbound Webex sends to
  10 in-flight requests.
- **Rate limiting**: `fetchWithRateLimit` transparently retries on Webex 429
  responses honoring `Retry-After`.
- **Per-bot job isolation**: draft jobs are keyed by `cookieId + appName`,
  and job list / detail endpoints filter by `appName`, so authorized users
  of one bot never see another bot's jobs.
