Add multi-server-per-admin support via sa_admins_servers table#287
Open
Ravid-A wants to merge 2 commits into
Open
Add multi-server-per-admin support via sa_admins_servers table#287Ravid-A wants to merge 2 commits into
Ravid-A wants to merge 2 commits into
Conversation
Ravid-A
added a commit
to Ravid-A/CS2-SimpleAdmin
that referenced
this pull request
May 31, 2026
Add multi-server-per-admin support via sa_admins_servers table daffyyyy#287
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Admin records are bound to a single server through the
sa_admins.server_idcolumn (NULL= global). Granting one admin to several specific servers means duplicating the whole admin row (plus its flag rows) per server, and "all servers" is modelled as aNULLforeign-key value, which is awkward to query and easy to get wrong.This PR introduces a
sa_admins_serverslink table — mirroring the existingsa_groups/sa_groups_serverspattern — so a single admin record can be attached to many servers. It also adds an explicitglobalflag onsa_adminsfor all-servers admins, replacing theNULL-server_idconvention. Existing data is migrated automatically and behaviour is preserved.Changes
All changes are additive/mechanical against
eea700b.017_CreateAdminsServersTable.sql(MySQL + SQLite) — createssa_admins_servers (id, admin_id, server_id NOT NULL, FK admin_id → sa_admins(id) ON DELETE CASCADE); addsglobalcolumn tosa_admins(default0); backfills (server-specific admins → one link row each, oldNULL-server admins →global = 1); then dropssa_admins.server_id. Both new files are registered in the.csprojforCopyToOutputDirectory, matching every other migration.IDatabaseProvider— addsGetAddAdminServerQuery()andGetDeleteOrphanedAdminsQuery().MysqlDatabaseProvider/SqliteDatabaseProvider(symmetric):GetAdminsQuery— now selects admins whereglobal = 1OR asa_admins_serversrow exists for the current server (viaEXISTS, so the flag join doesn't multiply rows). Previously filtered onsa_admins.server_id.GetAddAdminQuery— inserts theglobalcolumn instead ofserver_id.GetAddAdminServerQuery— inserts the per-server link row.GetDeleteAdminQuery(globalDelete: false)— deletes only the current server's link row, leaving the admin and its other server assignments intact. Global delete still removes thesa_adminsrow (cascading the links).GetDeleteOrphanedAdminsQuery— deletes non-global admins that have no remainingsa_admins_serversrows (see Notes).PermissionManager—AddAdminBySteamId:-gsetsglobal = 1and writes no link row (applies everywhere); otherwiseglobal = 0and a link row is written for the current server. NewDeleteOrphanedAdmins()runs the orphan-cleanup query.PlayerManager—DeleteOrphanedAdmins()is added to the existing expire-timer task batch (alongsideDeleteOldAdmins()), so orphans are swept on the same cadence as expired bans/mutes/warns.globalidentifier is a MySQL reserved word, so it is backticked in every statement.Test plan
sa_admins(oneNULL-server, two server-specific) run through017:NULLadmin →global = 1with no link row; server-specific admins → one link row each with correctserver_id;sa_admins.server_iddropped;sa_admins_servers.server_idenforcedNOT NULL. (SQLite 3.50.4 via System.Data.SQLite.Core 1.0.119.)GetAdminsQueryreturns, per server: server 5 → global + server-5 admin; server 7 → global + server-7 admin; unregistered server 9 → global only.GetDeleteOrphanedAdminsQueryremoves exactly that admin (global = 0, no links) while leaving the global admin and a still-linked admin untouched.dotnet build(Rebuild, net8.0) — clean, 0 warnings / 0 errors.sa_groups_serverspatterns and existing admin queries;EXISTS, theglobalbackfill,DROP COLUMN, and the orphanDELETE … NOT IN (subquery on a different table)are all standard MySQL. A second pair of eyes on a MySQL deployment before merge would be welcome.css_addadmin(with and without-g) andcss_deladminon a running server at merge time to confirm permission load/unload end-to-end.Notes
global = 0and zero links — invisible to every server (never loaded) but still occupying rows.DeleteOrphanedAdmins()sweeps these on the expire timer so they don't accumulate.SqliteDatabaseProviderdon't issuePRAGMA foreign_keys = ON, soON DELETE CASCADEdoesn't fire on SQLite — exactly as with the existingsa_admins_flagstable. This means an orphaned admin'ssa_admins_flagsrows can linger on SQLite after the admin row is swept; MySQL cascades them. Enabling the pragma globally would fix this class of issue but is a separate cleanup, out of scope here.globalAdmin/-gplumbing inbasecommands.csis left as-is; it now drives theglobalcolumn instead of aNULLserver_id.Tested SA version:
eea700b. Submitted fromRavid-A:feature/multi-server-per-admin.