The bot remembers where it registered beta commands (beta_registered) and removes them from servers that are no longer in the beta at the next start. Failed cleanups are retried; a server the bot has left is forgotten. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
5.5 KiB
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.sqlin 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
NOTIFYtriggers on the three*_commandstables (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, 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 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:
- The dashboard API inserts a row (
command_type, a JSONpayload,requested_by,processed_atempty). - A trigger in the database runs
pg_notify('<table>', id). - The bot holds one connection that ran
LISTEN <table>. It wakes, loads every unprocessed row, carries each out, and setsprocessed_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
- Decide who owns the table (see above). New tables for the dashboard's modules are created by an API migration.
- Write the API migration, and update
schema.sqlto match. - Add or change the repo method in
internal/db/repos/<area>repo. UseIF NOT EXISTSand 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. - If the dashboard must make the bot do something, add a
command_typeto theCHECKin the right*_commandstable and handle it in the listener. - 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.