Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
67 lines
5.3 KiB
Markdown
67 lines
5.3 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
|
|
|
|
The bot and the dashboard API share one database, so two files describe it:
|
|
|
|
- **`schema.sql`** in this repository: the tables the bot reads and writes. It is safe to run more than once (`CREATE TABLE IF NOT EXISTS`). It is enough to run the bot **on its own**.
|
|
- **The API's migrations** (`velox-dashboard-api/cmd/db/migrations`): when the API is deployed, it creates and changes the tables, and runs its migrations on start. The bot never runs migrations.
|
|
|
|
Some tables are older and were created by the bot (levels, welcome, and so on). The API's migration `000005_bot_owned_tables` creates them for a fresh combined database. Newer modules (music, tickets, security, meeting lobbies, beta settings) were created by API migrations, and `schema.sql` carries a copy so the bot can run alone.
|
|
|
|
**The rule:** a table must have the same shape in `schema.sql` and in the API migrations. When you change one, change the other in the same release.
|
|
|
|
Two things only exist in the API's migrations, because the bot never inserts into those tables:
|
|
|
|
- the `NOTIFY` triggers on the three `*_commands` tables (see below),
|
|
- the migration history.
|
|
|
|
## 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 for the dashboard's player, plus requests from the dashboard |
|
|
| 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` | One row: the beta server and the Beta Tester role. Written by the dashboard, read by the bot |
|
|
|
|
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 the dashboard, and the difference matters.
|
|
|
|
**Settings.** The 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. The dashboard API 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 API checks 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. Decide who owns the table (see above). New tables for the dashboard's modules are created by an API migration.
|
|
2. Write the API migration, **and** update `schema.sql` to match.
|
|
3. Add or change the repo method in `internal/db/repos/<area>repo`. Use `IF NOT EXISTS` and avoid destructive changes: the bot and the dashboard may be deployed a few minutes apart, so the old bot must keep working against the new schema for a moment. Add a column or table first, and remove old ones in a later release.
|
|
4. If the dashboard must make the bot do something, add a `command_type` to the `CHECK` in the right `*_commands` table and handle it in the listener.
|
|
5. Say in the release notes if the API, bot and dashboard have to deploy together.
|
|
|
|
## 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.
|