321 lines
16 KiB
Markdown
321 lines
16 KiB
Markdown
# ECS Full Stack
|
|
|
|
A full-stack example project that integrates a React frontend, an Express backend, and a Discord bot. The app provides a dashboard for server admins to manage bot settings and invites, plus Discord moderation/integration features via a bot running with discord.js.
|
|
|
|
This README documents how to get the project running, what environment variables are required, where to get Discord keys, how the invite token flow works, and basic troubleshooting tips.
|
|
|
|
Note: The backend has been updated to support Postgres persistence (see `CHANGELOG.md`). The backend now requires `DATABASE_URL` to run in the default configuration; if you prefer the legacy encrypted file store, see the notes under "Developer notes".
|
|
|
|
## Repository layout
|
|
|
|
- `frontend/` — React (Create React App) frontend. Uses `REACT_APP_API_BASE` to communicate with the backend in dev and production.
|
|
- `backend/` — Express backend and API server that also coordinates with the `discord-bot` library to manage guilds, invites, and settings. Uses environment variables for configuration.
|
|
- `discord-bot/` — small wrapper that logs the bot in and exposes the discord.js client used by the backend.
|
|
- `checklist.md`, `README.md`, other docs and small scripts at repo root.
|
|
|
|
## What this project does
|
|
|
|
- Provides a React dashboard where a user can view servers the bot is connected to and manage per-server settings (welcome/leave messages, autorole, toggling commands, invite creation/listing/deletion).
|
|
- Runs a Discord bot (discord.js) that performs moderation and server features. The backend and bot are closely integrated: the backend hosts the API and the bot client is shared to fetch guild data and manipulate invites/channels/roles.
|
|
- Uses a short-lived token flow to authorize invite deletions from the frontend without embedding long-lived secrets in the client.
|
|
|
|
Expanded: what this app does
|
|
|
|
- Hosts a dashboard (React) that lists Discord guilds where the bot is present and lets server admins:
|
|
- create and manage invites (create invites with options, view persisted invites, copy and revoke)
|
|
- configure Welcome and Leave messages and channels
|
|
- enable/disable bot commands per server
|
|
- set autorole behavior for new members
|
|
- Provides a backend API (Express) that coordinates with a discord.js bot to perform live guild operations (fetch channels/roles, create invites, leave guilds)
|
|
- Stores configuration and invites in Postgres (recommended) or a legacy encrypted `db.json`
|
|
|
|
## Quickstart — prerequisites
|
|
|
|
- Node.js (recommended 18.x or later) and npm
|
|
- A Discord application with a Bot user (to get `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET`) — see below for setup steps
|
|
- Optional: a VPS or Tailscale IP if you want to run the frontend/backend on a non-localhost address
|
|
|
|
## Environment configuration (.env)
|
|
|
|
There are env files used by the backend and frontend. Create `.env` files in the `backend/` and `frontend/` folders for local development. Examples follow.
|
|
|
|
### backend/.env (example)
|
|
|
|
PORT=3002
|
|
HOST=0.0.0.0
|
|
BACKEND_BASE=http://your-server-or-ip:3002
|
|
FRONTEND_BASE=http://your-server-or-ip:3001
|
|
CORS_ORIGIN=http://your-server-or-ip:3001
|
|
DISCORD_CLIENT_ID=your_discord_client_id
|
|
DISCORD_CLIENT_SECRET=your_discord_client_secret
|
|
ENCRYPTION_KEY=a-32-byte-or-longer-secret
|
|
INVITE_TOKEN_SECRET=optional-second-secret-for-invite-tokens
|
|
|
|
# Postgres example (optional but recommended)
|
|
# DATABASE_URL=postgres://dbuser:dbpass@100.111.50.59:5432/ecs_db
|
|
|
|
- `PORT` / `HOST`: where the backend listens.
|
|
- `BACKEND_BASE` and `FRONTEND_BASE`: used for constructing OAuth redirect URIs and links.
|
|
- `CORS_ORIGIN`: optional; set to your frontend origin to restrict CORS.
|
|
- `DISCORD_CLIENT_ID` / `DISCORD_CLIENT_SECRET`: from the Discord Developer Portal (see below).
|
|
- `ENCRYPTION_KEY` or `INVITE_TOKEN_SECRET`: used to sign short-lived invite tokens. Keep this secret.
|
|
|
|
Note: This project previously supported an `INVITE_API_KEY` static secret; that requirement has been removed. Invite deletes are authorized via short-lived invite tokens by default.
|
|
|
|
### Twitch Live Notifications (optional)
|
|
|
|
This project can detect when watched Twitch users go live and post notifications to a configured Discord channel for each guild. To enable this feature, add the following to `backend/.env`:
|
|
|
|
- `TWITCH_CLIENT_ID` — your Twitch app client id
|
|
- `TWITCH_CLIENT_SECRET` — your Twitch app client secret
|
|
- `TWITCH_POLL_INTERVAL_MS` — optional, poll interval in milliseconds (default 30000)
|
|
|
|
When configured, the backend exposes:
|
|
|
|
- GET /api/twitch/streams?users=user1,user2 — returns stream info for the listed usernames (used by the frontend and bot watcher)
|
|
|
|
The bot includes a watcher that polls watched usernames per-guild and posts a message to the configured channel when a streamer goes live. The message includes the stream title and a link to the Twitch stream.
|
|
|
|
If you run the backend and the bot on separate hosts, you can configure the backend to push setting updates to the bot so toggles and watched users propagate immediately:
|
|
|
|
- `BOT_PUSH_URL` — the URL the bot will expose for the backend to POST setting updates to (e.g., http://bot-host:4002)
|
|
- `BOT_SECRET` — a shared secret used by the backend and bot to secure push requests
|
|
- `BOT_PUSH_PORT` — optional, the port the bot listens on for push requests (if set the bot starts a small HTTP receiver)
|
|
|
|
|
|
### frontend/.env (example)
|
|
|
|
HOST=0.0.0.0
|
|
PORT=3001
|
|
REACT_APP_API_BASE=http://your-server-or-ip:3002
|
|
|
|
Set `REACT_APP_API_BASE` to point at the backend so the frontend can call API endpoints.
|
|
|
|
## Create a Discord Application and Bot (short)
|
|
|
|
1. Go to the Discord Developer Portal: https://discord.com/developers/applications
|
|
2. Create a new application.
|
|
3. Under "OAuth2" -> "General", add your redirect URI:
|
|
- For dev: `http://your-server-or-ip:3002/auth/discord/callback`
|
|
- Make sure `BACKEND_BASE` matches the host/port you set in `backend/.env`.
|
|
4. Under "Bot" create a Bot user and copy the Bot token (NOT committed to source).
|
|
5. Under "OAuth2" -> "URL Generator" select scopes `bot` and `applications.commands` and select permissions (e.g., Administrator if you want full access for testing). Use the generated URL to invite the bot to your guild during testing.
|
|
|
|
Store the Client ID / Client Secret in your `backend/.env` as `DISCORD_CLIENT_ID` and `DISCORD_CLIENT_SECRET`.
|
|
|
|
## Invite token flow (why and how)
|
|
|
|
- To avoid embedding long-lived secrets in a web client, invite deletions are authorized with a short-lived HMAC-signed token.
|
|
- The frontend requests a token with:
|
|
GET /api/servers/:guildId/invite-token
|
|
- The backend returns `{ token: '...' }`. The frontend then calls
|
|
DELETE /api/servers/:guildId/invites/:code
|
|
with header `x-invite-token: <token>`
|
|
- Token TTL is short (default 5 minutes) and is signed using `INVITE_TOKEN_SECRET` or `ENCRYPTION_KEY` from backend `.env`.
|
|
|
|
Security note: Currently the `/invite-token` endpoint issues tokens to any caller. For production you should restrict this endpoint by requiring OAuth authentication and checking that the requesting user is authorized for the target guild.
|
|
|
|
## Run the app locally
|
|
|
|
1. Backend
|
|
|
|
```powershell
|
|
cd backend
|
|
npm install
|
|
# create backend/.env from the example above
|
|
npm start
|
|
```
|
|
|
|
Optional: using Postgres (recommended)
|
|
|
|
1. Create a Postgres database and user (pgAdmin or psql)
|
|
2. Set `DATABASE_URL` in `backend/.env`, e.g.:
|
|
DATABASE_URL=postgres://dbuser:dbpass@100.111.50.59:5432/ecs_db
|
|
3. Start the backend; on startup the backend will create simple tables if missing.
|
|
|
|
Migration note:
|
|
- If you have existing data in `backend/db.json`, a migration script is planned to import invites and server settings into Postgres. I can add that script on request.
|
|
|
|
2. Frontend
|
|
|
|
```powershell
|
|
cd frontend
|
|
npm install
|
|
# create frontend/.env with REACT_APP_API_BASE pointing to the backend
|
|
npm run start
|
|
```
|
|
|
|
3. Discord bot
|
|
|
|
- The backend boots the bot client (see `discord-bot/`), so if the backend is started and credentials are correct, the bot will log in and register slash commands. You can also run the `discord-bot` project separately if you prefer.
|
|
|
|
## Troubleshooting
|
|
|
|
- Backend refuses to start or missing package.json: ensure you run `npm install` in the `backend` folder and run `npm start` from that folder.
|
|
- If the backend exits with "DATABASE_URL is not set": either set `DATABASE_URL` in `backend/.env` pointing to your Postgres DB, or restore the legacy behavior by editing `backend/index.js` to re-enable the encrypted `db.json` fallback (not recommended for production).
|
|
- CORS errors: verify `CORS_ORIGIN` and `REACT_APP_API_BASE` match your frontend origin.
|
|
- Invite delete unauthorized: ensure backend `INVITE_TOKEN_SECRET` or `ENCRYPTION_KEY` is present and token TTL has not expired. Check the backend logs for validation details.
|
|
- Token issues: clock skew can cause tokens to appear expired — ensure server and client clocks are reasonably in sync.
|
|
|
|
## Developer notes
|
|
|
|
- The dashboard UI is in `frontend/src/components/` (notable files: `Dashboard.js`, `ServerSettings.js`, `Login.js`).
|
|
- The Express API is in `backend/index.js` and uses `discord-bot` (discord.js client) to operate on guilds, invites, channels and roles.
|
|
- Invite delete flow: frontend fetches a short-lived token then requests DELETE with header `x-invite-token`.
|
|
|
|
## Next steps / suggestions
|
|
|
|
- Harden `/api/servers/:guildId/invite-token` to require an authenticated user and verify the user has admin permissions for the guild.
|
|
- Add rate-limiting to token issuance and optionally keep the old `INVITE_API_KEY` option for server-to-server automation.
|
|
- Updated docs: the README and CHANGELOG were updated to reflect Postgres integration and recent frontend/backend changes. See `CHANGELOG.md` and `checklist.md` for details.
|
|
|
|
If you want, I can add step-by-step instructions to create the `.env` files from templates, or implement the production safe option of authenticating `/invite-token` requests. Tell me which you'd prefer.
|
|
|
|
---
|
|
Updated: Oct 4, 2025
|
|
|
|
## Full setup guide (detailed)
|
|
|
|
This section walks through the exact steps to get the project running locally or on a machine reachable via Tailscale/Nginx Proxy Manager.
|
|
|
|
Prerequisites
|
|
1. Node.js 18+ and npm
|
|
2. Postgres (local or remote) or use an existing Postgres server reachable over your network/Tailscale
|
|
3. Discord application + Bot credentials and (optional) Twitch app credentials
|
|
|
|
Database (Postgres) setup
|
|
1. Create a Postgres database and user. Example psql commands:
|
|
|
|
```bash
|
|
sudo -u postgres psql
|
|
CREATE DATABASE ecs_fullstack;
|
|
CREATE USER ecs_user WITH PASSWORD 'supersecret';
|
|
GRANT ALL PRIVILEGES ON DATABASE ecs_fullstack TO ecs_user;
|
|
\q
|
|
```
|
|
|
|
2. Set the `DATABASE_URL` in `backend/.env`:
|
|
|
|
```
|
|
DATABASE_URL=postgres://ecs_user:supersecret@127.0.0.1:5432/ecs_fullstack
|
|
```
|
|
|
|
3. Start the backend (it will run migrations / ensure tables at startup):
|
|
|
|
```powershell
|
|
cd backend
|
|
npm install
|
|
npm start
|
|
```
|
|
|
|
Backend configuration (.env)
|
|
- `DATABASE_URL` - required for Postgres persistence
|
|
- `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET`, `DISCORD_BOT_TOKEN` - from Discord Developer Portal
|
|
- `FRONTEND_BASE` - public frontend URL (used for OAuth redirect)
|
|
- `PORT`, `HOST` - where backend listens
|
|
- `CORS_ORIGIN` - optional restrict origin to your frontend URL
|
|
- `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET` - optional for Twitch integration
|
|
|
|
Frontend configuration
|
|
1. In `frontend/.env` set:
|
|
|
|
```
|
|
REACT_APP_API_BASE=https://your-domain-or-ip:3002
|
|
```
|
|
|
|
2. For development behind an HTTPS domain (Nginx Proxy Manager), ensure the CRA dev client uses `wss` by setting the `WDS_SOCKET_*` variables in `frontend/.env` (see docs if using a TLS domain)
|
|
|
|
Start the frontend dev server:
|
|
|
|
```powershell
|
|
cd frontend
|
|
npm install
|
|
npm start
|
|
```
|
|
|
|
Bot behaviour and deployment
|
|
- The `backend` process will boot the Discord bot client when valid `DISCORD_BOT_TOKEN` is present. The bot registers slash commands per guild on startup and responds to backend pushes for setting updates.
|
|
- If you prefer to run the bot separately, you can run the `discord-bot` module separately; ensure `BOT_PUSH_URL`/`BOT_SECRET` are configured if backend and bot are on different hosts.
|
|
|
|
Useful endpoints
|
|
- `GET /api/servers/:guildId/commands` — returns the authoritative list of commands and per-guild enabled/locked status.
|
|
- `GET/POST /api/servers/:guildId/live-notifications` — get/update live notification settings
|
|
- `GET /api/twitch/streams?users=user1,user2` — proxy to twitch helix for streams (backend caches app-token)
|
|
- `GET /api/events?guildId=...` — Server-Sent Events for real-time updates (ServerSettings subscribes to this)
|
|
|
|
### Twitch Live Notification Settings (Detailed)
|
|
|
|
Endpoint: `GET/POST /api/servers/:guildId/live-notifications`
|
|
|
|
Shape returned by GET:
|
|
```json
|
|
{
|
|
"enabled": true,
|
|
"channelId": "123456789012345678",
|
|
"twitchUser": "deprecated-single-user-field",
|
|
"message": "🔴 {user} is now live!",
|
|
"customMessage": "Custom promo text with link etc"
|
|
}
|
|
```
|
|
|
|
Notes:
|
|
- `twitchUser` is a legacy single-user field retained for backward compatibility. The active watched users list lives under `/api/servers/:guildId/twitch-users`.
|
|
- `message` (default message) and `customMessage` (override) are persisted. If `customMessage` is non-empty it is used when announcing a live stream; otherwise `message` is used. If both are empty the bot falls back to `🔴 {user} is now live!`.
|
|
- Update by POSTing the same shape (omit fields you don't change is okay; unspecified become empty unless preserved on server).
|
|
|
|
### Discord Bot Twitch Embed Layout
|
|
|
|
When a watched streamer goes live the bot posts a standardized embed. The layout is fixed to keep consistency:
|
|
|
|
Embed fields:
|
|
1. Title: Stream title (hyperlinked to Twitch URL) or fallback "{user} is live".
|
|
2. Author: Twitch display name with avatar and link.
|
|
3. Thumbnail: Stream thumbnail (or profile image fallback).
|
|
4. Fields:
|
|
- Category: Game / category name (or "Unknown").
|
|
- Viewers: Current viewer count.
|
|
5. Description: Twitch user bio (if available via Helix `users` endpoint) else truncated stream description (200 chars).
|
|
6. Footer: `ehchadservices • Started: <localized start time>`.
|
|
|
|
Pre-Embed Message (optional):
|
|
- If `customMessage` is set it is posted as normal message content above the embed.
|
|
- Else if `message` is set it is posted above the embed.
|
|
- Else no prefix content is posted (embed alone).
|
|
|
|
Variables:
|
|
- `{user}` in messages will not be auto-replaced server-side yet; include the username manually if desired. (Can add template replacement in a future iteration.)
|
|
|
|
### Watched Users
|
|
|
|
- Add/remove watched Twitch usernames via `POST /api/servers/:guildId/twitch-users` and `DELETE /api/servers/:guildId/twitch-users/:username`.
|
|
- Frontend polls `/api/twitch/streams` every ~15s to refresh live status and renders a "Watch Live" button per user.
|
|
- The watcher announces a stream only once per live session; when a user goes offline the session marker clears so a future live event re-announces.
|
|
|
|
### SSE Event Types Relevant to Twitch
|
|
|
|
- `twitchUsersUpdate`: `{ users: ["user1", "user2"], guildId: "..." }`
|
|
- `liveNotificationsUpdate`: `{ enabled, channelId, twitchUser, message, customMessage, guildId }`
|
|
|
|
Consume these to live-update UI without refresh (the `BackendContext` exposes an `eventTarget`).
|
|
|
|
### Customizing Messages
|
|
|
|
- In the dashboard under Live Notifications you can set both a Default Message and a Custom Message.
|
|
- Clear Custom to fall back to Default.
|
|
- Save persists to backend and pushes an SSE `liveNotificationsUpdate`.
|
|
|
|
### Future Improvements
|
|
|
|
- Template variable replacement: support `{user}`, `{title}`, `{category}`, `{viewers}` inside message strings.
|
|
- Per-user custom messages (different prefix for each watched streamer).
|
|
- Embed image improvements (dynamic preview resolution trimming for Twitch thumbnails).
|
|
|
|
Notes about Postgres requirement
|
|
- The backend now assumes Postgres persistence (via `DATABASE_URL`). If `DATABASE_URL` is not set the server will exit and complain. This change makes server settings authoritative and persistent across restarts.
|
|
|
|
Logs and verbosity
|
|
- The bot and watcher log messages have been reduced to avoid per-guild spam. You will see concise messages like "🔁 TwitchWatcher started" and "✅ ECS - Full Stack Bot Online!" rather than one-line-per-guild spam.
|
|
|
|
Troubleshooting
|
|
- If you see mixed-content errors in the browser when using a TLS domain with the CRA dev server, configure Nginx to proxy websockets and set CRA `WDS_SOCKET_*` env vars (see docs/nginx-proxy-manager.md)
|