Files
velox-bot/docs/database.md
T
FernandoJVideiraandClaude Sonnet 5.5 acdb5ead35 Tickets: settings, category edit and ticket list from Discord; NOTIFY triggers in schema.sql
- /tickets settings (on or off, panel title and text, open tickets per member), /tickets category edit, /tickets list
- schema.sql now creates the NOTIFY triggers, so the integration tests run on a database built from it, and two stale expectations (tickets start off) are fixed

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

5.3 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

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.

schema.sql also creates the NOTIFY triggers on the three *_commands tables (see below), so anything that inserts a request wakes the bot, with no NOTIFY of its own.

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.