Files
velox-bot/docs/database.md
T
FernandoJVideiraandClaude Sonnet 5.5 02b7db1813 Docs: say plainly that only the bot is open source; the dashboard and API are proprietary
- README, license section and architecture no longer point to the dashboard repositories
- schema.sql is the schema for contributors; database changes only need schema.sql and a note in the PR
- self-hosting: settings without a dashboard (SQL for security thresholds, beta settings, opted-in servers)
- beta features, deployment and troubleshooting reworded for people who don't have the dashboard

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
2026-10-23 01:54:08 +01:00

73 lines
5.6 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 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.