diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..ace10a2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,63 @@ +# Contributing to Velox Bot + +Thanks for wanting to help. This page is the short version of how to do it well. The longer technical material is in the [Development guide](docs/development.md). + +## Ways to help + +- **Report a bug.** Say what you did, what you expected and what happened. Include the bot's log lines around the problem and whether it happens in more than one server. Never paste a bot token, a database password or a `.env` file. +- **Suggest a feature.** Explain the problem it solves for a server, not only the command you imagine. +- **Improve the documentation.** Wrong or missing docs are bugs. A fix to `docs/` is always welcome. +- **Fix something or build something.** Pick an issue, or open one first (see below). + +## Security problems + +If you find a way to get around a permission check, make the bot act for someone who shouldn't be able to ask, or leak a token, **do not open a public issue**. Contact the maintainer privately, using the contact details on the repository's profile, and give them time to fix it before you share it. + +## Before you write code + +1. **Open an issue for anything big**: a new module, a change to how permissions or security work, a change to the database. It saves you from writing something that can't be merged. +2. Read [Architecture](docs/architecture.md) so you know the layers, and the [Development guide](docs/development.md) for the recipe that matches your change. +3. For anything that touches the database, read [Database](docs/database.md#changing-the-schema). A schema change has to keep a bot that is one version behind working for a short while. + +## Making the change + +1. Fork the repository and create a branch **from `dev`**: `git checkout -b my-change dev`. `master` only holds releases. +2. Keep the change focused. One pull request, one purpose. Unrelated cleanup goes in its own pull request. +3. Follow the code around you: naming, comment density, error handling. Comments explain why, not what. +4. Add tests. At minimum, test the permission check (does the wrong person get refused?), limits, and anything that parses input. See [Testing](docs/development.md#testing). +5. Update the documentation that your change makes wrong: `/help` (`internal/commands/help/help.go`), [Command reference](docs/commands.md) and [Features](docs/features.md). + +Before you push: + +```bash +gofmt -l . # format the files you touched +go build ./... && go vet ./... && go test -race ./... +``` + +Some older files are not `gofmt`-clean. Format the files you change, and leave the rest alone so the diff stays readable. + +## The rules that matter most + +These are the things a reviewer will check first. The reasons are in the [Development guide](docs/development.md#style-and-safety-rules). + +- **Fail closed.** If you can't tell whether someone is allowed, they are not. +- **Check permissions in the handler.** `DefaultMemberPermissions` only hides a command from the menu. +- **No SQL outside repos.** Always use query parameters (`$1`), never string formatting. +- **A module that acts on a server starts off.** An admin turns it on. +- **Escape user text and set `AllowedMentions`** so a message can't ping people. +- **Validate everything that comes from users or from other programs writing to the database**: options, custom IDs, command payloads. +- **No secrets** in code, logs, tests or commit messages. + +## Commit messages and pull requests + +- Write the commit subject in the imperative and keep it short ("Add a limit of 10 meeting lobbies"). Use the body to explain why, when it isn't obvious. +- In the pull request, say what changed and why, how you tested it, and whether it needs a database change or a deploy in a particular order. +- Keep the pull request open to review comments. Small follow-up commits are fine, there is no need to squash them yourself. + +## Behaviour + +Be kind and be specific. Disagree with the code, not the person. Harassment, personal attacks and discrimination are not tolerated, and the maintainer may remove comments, close discussions or block people who do it. + +## License + +By contributing, you agree that your contribution is licensed under the [Apache License 2.0](LICENSE), the same as the project (section 5 of the license covers this). You keep the copyright on your own work. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..bbca779 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Velox Bot +Copyright 2026 Fernando Videira + +This product includes software developed by the authors of its dependencies, +each under its own license (see go.mod). diff --git a/README.md b/README.md index 41fdeeb..32fa6be 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,9 @@ Velox is a Discord bot written in Go, backed by PostgreSQL. It covers server security (anti-raid and anti-nuke), support tickets, moderation, leveling, music, meeting rooms, scheduling, Twitch alerts and a few games. Every module is off until a server turns it on. -This repository is the bot. It has two companions that live in their own repositories: +**This repository is the bot, and it is the only open source part of Velox.** The web dashboard and its API, which the hosted Velox uses, are separate proprietary software and are not part of this repository or its license. The bot does not need them: everything in the [command reference](docs/commands.md) works on its own, with PostgreSQL as its only dependency. -- **velox-dashboard-api**: a Go REST API behind the web dashboard. It owns most of the database schema and runs the migrations. -- **velox-dashboard-web**: the Vue dashboard and public website. - -The bot and the API share one PostgreSQL database. They never call each other over HTTP. The dashboard writes settings and requests into tables, and the bot reads them (and is woken through Postgres `NOTIFY` where it has to react at once). See [Architecture](docs/architecture.md). +The dashboard and the bot meet in the database. The dashboard writes settings and requests into tables, and the bot reads them (and is woken through Postgres `NOTIFY` where it has to react at once). Those tables are documented, so you can change the same settings with SQL, or build your own tool on top. See [Architecture](docs/architecture.md) and [Database](docs/database.md). ## Documentation @@ -16,8 +13,9 @@ This README is the front page. The rest of the documentation lives in [`docs/`]( | Page | What is in it | | --- | --- | | [Getting started](docs/getting-started.md) | Requirements, creating the Discord application, configuration, database, running locally | +| [Host your own bot](docs/self-hosting.md) | A step-by-step guide to running your own copy on a server, with Docker Compose | | [Deployment](docs/deployment.md) | Docker, Compose, upgrade order, backups | -| [Architecture](docs/architecture.md) | How the code is laid out, how an interaction flows through it, background loops, the link to the dashboard | +| [Architecture](docs/architecture.md) | How the code is laid out, how an interaction flows through it, background loops, how a dashboard talks to the bot | | [Features](docs/features.md) | What each module does and how it behaves | | [Command reference](docs/commands.md) | Every slash command, its options and who may run it | | [Database](docs/database.md) | Who owns the schema, the tables, the `NOTIFY` channels, how to change them | @@ -46,7 +44,7 @@ On start the bot registers its slash commands in the server named by `GUILD_ID` - **Tickets**: a panel with categories, a private thread per ticket, claim and close, transcripts to a log channel. - **Moderation**: purge, timeout, kick, ban, unban, slowmode, plus server logs for edits, deletes, joins, leaves, role changes, bans and mod actions. - **Leveling**: XP per message, level cards, leaderboard, role rewards. -- **Music**: Lavalink-powered playback with a queue, controlled from Discord or the dashboard. +- **Music**: Lavalink-powered playback with a queue, controlled from Discord. - **Meeting rooms**: join a lobby voice channel and get a temporary room you can lock and invite people to. - **Scheduling and projects**: 1:1 sessions with DM invites and reminders, and a simple project list. - **Twitch alerts, welcome messages, default role**: per-server setup through `/config`. @@ -71,6 +69,19 @@ internal/modlog, rankcard small shared helpers schema.sql mirror of the tables the bot reads and writes ``` +## Host your own bot + +You can run your own copy under your own Discord application. [Host your own bot](docs/self-hosting.md) walks through it on a fresh server with Docker Compose: creating the application and the invite link, configuring the bot, loading the database, optional music and Twitch, backups and updates. + ## Contributing -Start with the [Development guide](docs/development.md). In short: keep a command's definition and its handler in one package, keep SQL in a repo and rules in a service, and run `go build ./... && go vet ./... && go test ./...` before opening a pull request. +Contributions are welcome: bug reports, fixes, documentation and new features. + +- Read [CONTRIBUTING.md](CONTRIBUTING.md) first. It covers how to report a problem, how to propose a change, and what a good pull request looks like. +- The [Development guide](docs/development.md) shows how to add a command, a button, an event handler, a setting or a table, and how to test them. +- For anything big, open an issue to talk about it before you write the code. +- Pull requests go to the `dev` branch. Before opening one, run `gofmt` on the files you touched and `go build ./... && go vet ./... && go test -race ./...`. + +## License + +Velox Bot, the code in this repository, is released under the [Apache License 2.0](LICENSE). Copyright 2026 Fernando Videira. See [NOTICE](NOTICE). The license does not cover the proprietary Velox dashboard and API, or the Velox name and logo as used by the hosted service. The libraries the bot uses are under their own permissive licenses (see `go.mod`). diff --git a/docs/architecture.md b/docs/architecture.md index 61de00a..aafd51f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -19,7 +19,7 @@ This page explains how the bot is put together, so you know where to look and wh | | | +----------- services (rules) <------ repos (SQL) -----+ | - PostgreSQL <----> velox-dashboard-api <----> dashboard + PostgreSQL <----> a dashboard (the hosted Velox has a proprietary one) ``` The bot is one Go process. It keeps one websocket connection to Discord and one connection pool to PostgreSQL. It has no HTTP server of its own. @@ -85,14 +85,14 @@ Some commands have a response that takes longer than three seconds (anything tha | Raid watch | `security` | 30 s | Ends raid mode after a quiet period | | Snapshots | `security` | 6 h | Saves roles and channels while anti-nuke is on (keeps three) | | Beta settings | `bot/bot.go` (`WatchBeta`) | 30 s | Re-reads who the beta testers are and which servers opted in, and adds or removes beta commands to match | -| Command listeners | music, tickets, security | on `NOTIFY` | Carry out dashboard requests | +| Command listeners | music, tickets, security | on `NOTIFY` | Carry out requests written to the `*_commands` tables | -## The link to the dashboard +## How a dashboard talks to the bot -The bot and the dashboard API do not talk to each other directly. They meet in PostgreSQL, in two ways: +The bot has no HTTP server and no API of its own. A web dashboard, such as the proprietary one the hosted Velox uses (not part of this repository), meets the bot in PostgreSQL, in two ways. This is the whole contract, so you can drive the bot the same way with SQL or write your own tool: -1. **Settings tables.** The dashboard writes settings (log channel, security thresholds, ticket categories, beta role). The bot reads them when it needs them. Hot paths cache them for a short time, so a raid of thousands of joins does not become thousands of queries. -2. **Command tables with `NOTIFY`.** When the dashboard needs the bot to *do* something (skip a song, post the ticket panel, release a quarantined member), it inserts a row in `music_commands`, `ticket_commands` or `security_commands` and issues `NOTIFY` on that channel. The bot has a long-lived connection that runs `LISTEN`, picks up the row, validates it again and carries it out. Never trust a row just because it is in the table: the listeners re-check permissions themselves. +1. **Settings tables.** A dashboard writes settings (log channel, security thresholds, ticket categories, beta role). The bot reads them when it needs them. Hot paths cache them for a short time, so a raid of thousands of joins does not become thousands of queries. +2. **Command tables with `NOTIFY`.** When a dashboard needs the bot to *do* something (skip a song, post the ticket panel, release a quarantined member), it inserts a row in `music_commands`, `ticket_commands` or `security_commands` and issues `NOTIFY` on that channel. The bot has a long-lived connection that runs `LISTEN`, picks up the row, validates it again and carries it out. Never trust a row just because it is in the table: the listeners re-check permissions themselves. Both directions are described table by table in [Database](database.md). diff --git a/docs/beta-features.md b/docs/beta-features.md index 15bbf3d..4cc586a 100644 --- a/docs/beta-features.md +++ b/docs/beta-features.md @@ -5,28 +5,27 @@ A beta feature is one that is still being tried out, so only chosen people can u 1. **The person is a beta tester**: a member of the beta server who holds the Beta Tester role. 2. **Beta features are on in the server they use it in**: the beta server itself, or any other server whose owner opted in. -The same rule applies on the bot and on the dashboard. +The rule is the same on the bot and on the hosted Velox dashboard. The dashboard is proprietary and not part of this repository, so this page is about the bot, and mentions the dashboard only where it explains how the settings get into the database. ## Who the testers are -- The **beta server** and the **Beta Tester role** are chosen by the bot's owner on the dashboard, under **Admin, Beta access**. Only the owner can see or change that page. -- Giving someone the role in Discord makes them a tester. Taking it away removes their access: within about 30 seconds on the bot (a few seconds if the bot has them cached), and within about 30 seconds on the dashboard API. +- The **beta server** and the **Beta Tester role** live in the `beta_settings` table, one row. On the hosted Velox the owner sets them on the dashboard. If you host the bot yourself, set them with SQL (see [Settings without a dashboard](self-hosting.md#settings-without-a-dashboard)). +- Giving someone the role in Discord makes them a tester. Taking it away removes their access, within about 30 seconds on the bot (a few seconds if the bot has them cached). - Until a role is chosen, **nobody** has beta access. - Being a tester is about the person, so it is always checked in the **beta server**, also when they use a feature somewhere else. Having a role with the same ID in another server means nothing. ## Which servers have beta features - The beta server always has them. -- Any other server gets them only when **its owner** switches on **Beta features** on that server's page of the dashboard. Beta features can act on a whole server (anti-raid behaviour, for example), so the people who answer for the server decide. A server's admins who are not its owner cannot switch it on. -- The bot's owner can take a server out again from **Admin, Beta access**, but cannot put one in. -- The list of opted-in servers is the `beta_guilds` table. The bot polls it every 30 seconds, together with the beta settings. +- Any other server gets them only when it is in the `beta_guilds` table. Beta features can act on a whole server (anti-raid behaviour, for example), so a server should only be listed with its owner's agreement. On the hosted Velox, only the server's own owner can switch it on, from the dashboard. If you host the bot yourself, you add rows yourself. +- The bot polls the table every 30 seconds, together with the beta settings. Beta never replaces a feature's own permission checks. A beta command that needs Manage Server still needs it, a music command still needs the DJ role, and anything for the owner or trusted users still needs that. ## What a tester sees - **Bot:** beta commands appear only in the beta server and in opted-in servers, and run only for testers. When a server opts in, the commands show up within about 30 seconds. When it opts out, they are removed. -- **Dashboard:** beta pages and links show a **Beta** badge, and only to testers, and only for servers where beta features are on. +- **On the hosted Velox dashboard:** beta pages and links show a **Beta** badge, only to testers and only for servers where beta features are on. ## On the bot @@ -35,7 +34,7 @@ Code: `internal/beta` (the rules), `internal/commands/commands.go` (`RegisterBet - Beta commands are registered in each server where beta features are on, and only there. `/beta` is only in the beta server. - Each beta handler is wrapped in `beta.Gate`. It checks that beta is on in the server of the interaction and that the person is a tester, and otherwise answers "This feature is in beta and isn't available to you or this server yet." (Autocomplete requests get an empty list.) Hiding the command is never the only protection. - For a person using a command in another server, the bot looks up their roles in the beta server: first in its gateway cache, otherwise it asks Discord (remembered for 30 seconds). If anything fails, the answer is no. -- If the owner changes the beta server on the dashboard, the beta commands move within 30 seconds. In the beta server, commands this bot left there that no longer exist are removed too. If the beta server is also `GUILD_ID`, the regular commands are kept. +- If the beta server changes in `beta_settings`, the beta commands move within 30 seconds. In the beta server, commands this bot left there that no longer exist are removed too. If the beta server is also `GUILD_ID`, the regular commands are kept. - `/beta` shows the bot's owner how access is set up. Only the owner of the Discord application can run it, and it is hidden from everyone but administrators. - The bot remembers the servers where it registered beta commands (`beta_registered` table). A server that opted out while the bot was **offline** is cleaned up on the next start, and a cleanup that fails is retried every 30 seconds. A server the bot has left is forgotten. @@ -67,17 +66,14 @@ Do not use this for anything that should only be for testers as people. It means Move the definition into `AllCommands` and the handler into the `handlers` map, and delete the `RegisterBeta` call. On the next start the command appears everywhere, and the beta servers drop it from their own lists because it is now a regular command. -## On the API and dashboard +## On the dashboard -These live in the other two repositories. - -- **API**: put the feature's routes under `/api/v1/beta` (not tied to a server), or add the `betaGuildOnly()` middleware to a route under `/api/v1/guilds/:guild_id/...` (tied to a server). The first asks Discord whether the signed-in user holds the role in the beta server (30-second memory); the second also checks that the server is the beta server or opted in. Both answer `403` otherwise. `GET /users/me` returns `is_beta`, and `GET /guilds/:guild_id/beta` tells the dashboard whether beta features are on for that server and whether the caller owns it. -- **Dashboard**: give a route `meta: { requiresBeta: true }` to keep its page away from non-testers, and mark navigation entries with `beta: true` so testers see them with a **Beta** badge (and nobody else sees them). This is only about display. The API checks access on every request, so a page that was shown by mistake still cannot read or change anything. +The hosted Velox has a proprietary dashboard and API that apply the same rule to their own pages: a beta page is only shown, and its API routes only answer, for a tester in a server where beta features are on. Their code is not part of this repository. If you build your own tool on the same tables, apply the rule on your server side too: never rely on hiding a page. ## Why it works this way - The role in Discord is the single list of testers, so there is no second list to keep in step with it. - Servers choose to take part, because a beta feature can affect everyone in them. -- Access is decided by the server (bot and API), never by the browser. +- Access is decided on the server, never in the browser. - Beta commands do not exist outside the beta server, and the gate checks again when they run. -- The settings are in the database, not in the environment, so the owner can change them without a deploy. +- The settings are in the database, not in the environment, so they can change without a deploy. diff --git a/docs/database.md b/docs/database.md index 4496ddc..aa51d96 100644 --- a/docs/database.md +++ b/docs/database.md @@ -4,19 +4,26 @@ The bot uses PostgreSQL through `database/sql` with the pgx driver. All SQL live ## Who owns the schema -The bot and the dashboard API share one database, so two files describe it: +**`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. -- **`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. +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. -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. +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: -**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. +```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; -Two things only exist in the API's migrations, because the bot never inserts into those tables: +CREATE TRIGGER music_commands_notify +AFTER INSERT ON music_commands +FOR EACH ROW EXECUTE FUNCTION notify_music_command(); +``` -- the `NOTIFY` triggers on the three `*_commands` tables (see below), -- the migration history. +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 @@ -30,7 +37,7 @@ Two things only exist in the API's migrations, because the bot never inserts int | 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 | +| 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 | @@ -39,27 +46,26 @@ Discord IDs are `BIGINT` (snowflakes fit in 64 bits). In Go they are `int64`; Di ## Dashboard to bot: settings and requests -There are two kinds of traffic from the dashboard, and the difference matters. +There are two kinds of traffic from a dashboard to the bot, 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. +**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. The dashboard API inserts a row (`command_type`, a JSON `payload`, `requested_by`, `processed_at` empty). +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('