- /security settings, joingate check|account-age, words add|remove|list, raid, antinuke set|limit, heat, alert-dms - Same rule as the rest of the configuration: owner or trusted, validated in the engine - schema.sql was missing security_settings.alert_dm, so the bot couldn't read security settings on a database built from it. Fixed, with an ALTER for existing installs - A test checks every command against Discord's limits Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
73 lines
5.8 KiB
Markdown
73 lines
5.8 KiB
Markdown
# Database
|
|
|
|
The bot uses PostgreSQL through `database/sql` with the pgx driver. All SQL lives in repos under `internal/db/repos`.
|
|
|
|
## Who owns the schema
|
|
|
|
**`schema.sql`** in this repository is the schema. It describes every table the bot reads and writes, and it is enough to run the bot. It is safe to run more than once (`CREATE TABLE IF NOT EXISTS`). The bot never runs migrations itself.
|
|
|
|
The hosted Velox also has a proprietary dashboard API that keeps its own migrations for the same tables. That is the maintainer's concern: if your change adds or alters a table, update `schema.sql` and say so in your pull request, and the maintainer ports it. You never need the API to develop or run the bot.
|
|
|
|
Two things are not in `schema.sql`, because only a dashboard needs them: the `NOTIFY` triggers on the three `*_commands` tables (see below), and a migration history. If you write a tool that inserts command rows, add the trigger yourself:
|
|
|
|
```sql
|
|
CREATE OR REPLACE FUNCTION notify_music_command() RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
PERFORM pg_notify('music_commands', NEW.id::text);
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
CREATE TRIGGER music_commands_notify
|
|
AFTER INSERT ON music_commands
|
|
FOR EACH ROW EXECUTE FUNCTION notify_music_command();
|
|
```
|
|
|
|
The same works for `ticket_commands` and `security_commands`. Without the trigger the bot still picks the rows up, but only the next time it starts or is notified for another reason.
|
|
|
|
## Tables
|
|
|
|
| Area | Tables | Notes |
|
|
| --- | --- | --- |
|
|
| Leveling | `levels`, `levelsettings`, `levelrewards`, `levelup` | XP and level per member, settings, role rewards, the level-up channel |
|
|
| Welcome | `welcome`, `defaultrole` | Welcome channel, templates, GIF, default role |
|
|
| Logging | `logsettings` | Log channel and on/off |
|
|
| Twitch | `twitch`, `twitch_config` | Streamers and the notification channel |
|
|
| Fun | `rps` | Rock-paper-scissors stats |
|
|
| Meeting rooms | `meeting_lobbies`, `meeting_temp_channels`, `meeting_settings` | Lobbies (10 per server, enforced by a trigger), the temporary rooms and who created them. `meeting_settings.lobby_channel_id` is the old single lobby column and is no longer read |
|
|
| Projects | `projects`, `project_members` | |
|
|
| Scheduling | `schedules`, `schedule_messages`, `user_settings` | Sessions, the DM messages to update, and each member's timezone |
|
|
| Music | `music_settings`, `music_now_playing`, `music_queue`, `music_commands` | State a dashboard can show, plus requests to the bot |
|
|
| Tickets | `ticket_settings`, `ticket_categories`, `tickets`, `ticket_commands` | |
|
|
| Security | `security_settings`, `security_trusted`, `security_quarantines`, `security_lockdowns`, `security_raids`, `security_raid_caught`, `security_snapshots`, `security_events`, `security_commands` | Settings, who is trusted, quarantines (with the roles to give back), locked channels (with the permission to restore), raids in progress, snapshots, an event history, and dashboard requests |
|
|
| Beta | `beta_settings`, `beta_guilds` | `beta_settings` is one row: the beta server and the Beta Tester role. `beta_guilds` lists servers whose owner opted in to beta features. Both are written by the dashboard and read by the bot. `beta_registered` is the bot's own note of where it registered beta commands |
|
|
|
|
Discord IDs are `BIGINT` (snowflakes fit in 64 bits). In Go they are `int64`; Discord's own API gives them to you as strings, so convert at the edge.
|
|
|
|
## Dashboard to bot: settings and requests
|
|
|
|
There are two kinds of traffic from a dashboard to the bot, and the difference matters.
|
|
|
|
**Settings.** A dashboard writes a row, and the bot reads it when it next needs it. Nothing wakes the bot up. Hot paths cache settings for a short time. The bot's beta settings are polled every 30 seconds.
|
|
|
|
**Requests that need the bot to act.** `music_commands`, `ticket_commands` and `security_commands` work as a small queue:
|
|
|
|
1. A dashboard inserts a row (`command_type`, a JSON `payload`, `requested_by`, `processed_at` empty).
|
|
2. A trigger in the database runs `pg_notify('<table>', id)`.
|
|
3. The bot holds one connection that ran `LISTEN <table>`. It wakes, loads **every** unprocessed row, carries each out, and sets `processed_at`.
|
|
|
|
Because the bot loads all unprocessed rows (also when it starts), nothing is lost if it was down when a request arrived. A command that fails is logged and marked processed anyway, since retrying would fail the same way.
|
|
|
|
Every listener **validates the request again**. The dashboard should check that the requester is allowed, and the bot checks too, because it should not trust a row just because it exists. The listeners live in `internal/music/listener.go`, `internal/tickets/listener.go` and `internal/security/listener.go`, and each reconnects after 10 seconds if its connection drops.
|
|
|
|
## Changing the schema
|
|
|
|
1. Update `schema.sql`. Use `CREATE TABLE IF NOT EXISTS`, and for a new column on an existing table also add `ALTER TABLE ... ADD COLUMN IF NOT EXISTS ...` right after it, so servers set up earlier get the column when they run the file again. Avoid destructive changes: a bot that is a version behind (or a tool reading the same tables) must keep working against the new schema for a moment. Add a column or table first, and remove old ones in a later release.
|
|
2. Add or change the repo method in `internal/db/repos/<area>repo`.
|
|
3. If something outside the bot must make it do something, add a `command_type` to the `CHECK` in the right `*_commands` table and handle it in the listener.
|
|
4. In your pull request, say that the schema changed and whether an existing table is affected, so the maintainer can port the change to the dashboard's migrations.
|
|
|
|
## Connection settings
|
|
|
|
`internal/db/db.go` opens the pool with up to 100 open and 10 idle connections, and recycles them after an hour. The listeners use their own separate connections, because `LISTEN` needs one that stays open.
|