Skip to main content

Discord rank-to-role sync

Platform guild Role rows (ranks) can map to multiple Discord role snowflakes for the linked Organization.discordServerId. Sync uses the existing bot (DiscordService.assignRole / removeRole). Event-based Discord roles (Event.discordRoleId, signup flows) stay separate: rank sync only touches roles listed in RoleDiscordRole.

Data modelโ€‹

  • RoleDiscordRole: organizationRoleId โ†’ discordRoleId, optional sortOrder. Unique (organizationRoleId, discordRoleId).
  • DiscordManagedRoleSuppression: userId, organizationId, discordRoleId (optional discordGuildId). Unique (userId, organizationId, discordRoleId). Means: do not auto-add this Discord role for this user in this org until cleared.

Triggersโ€‹

  1. Rank change โ€” membership.role.changed runs syncOnRankChange: removes Discord roles that belonged only to the previous rank, adds roles for the new rank (skips adds if suppressed).
  2. Discord account linked โ€” After Better Auth updates User.discordId, notifyDiscordLinked runs reconcileAllForUser (full reconcile per active membership in orgs with a linked Discord server).
  3. guildMemberUpdate โ€” If a managed Discord role (one mapped to the memberโ€™s current platform rank) disappears from the member, a suppression row is upserted so the next sync does not re-add it (mod or self-removal are indistinguishable without audit log).
  4. Manual โ€” POST /api/v1/organizations/:guildId/discord-rank-roles/reconcile (officers).
  5. Scheduled โ€” Daily at 05:00 UTC, reconcileAllOrganizationsWithRankMappings for orgs that have at least one RoleDiscordRole row.

Pausing sync: Organization.settings.discordRankRoleSyncEnabled โ€” when explicitly false (Integrations toggle), triggers 1โ€“5 skip Discord role changes for that guild; POST .../reconcile returns 400. Officers can still edit mappings and clear suppressions. Unset is treated as enabled for backward compatibility.

Reconcile vs rank changeโ€‹

  • Rank change uses a diff between previous and new rank mappings (efficient).
  • Reconcile loads the memberโ€™s current Discord roles, intersects with all rank-managed role IDs for the org, then removes extras and adds missing (respecting suppressions).

Officer APIโ€‹

  • GET /organizations/:guildId/discord-rank-roles โ€” mappings keyed by platform role id.
  • PUT /organizations/:guildId/discord-rank-roles/roles/:roleId โ€” body { discordRoleIds: string[] } (replace).
  • POST .../clear-suppressions โ€” { userId } or { clearAll: true }.
  • POST .../reconcile โ€” full org reconcile.

Requires Update Guild (same as other guild settings).

Operational constraintsโ€‹

  • The botโ€™s highest role in Discord must be above any role it assigns.
  • Bot needs Manage Roles (already required for announcements/events).

Product policyโ€‹

Sticky suppression (default): once a managed role is removed in Discord, it is not re-added until an officer clears suppression for that user (or clearAll for the org).

Optional future: clear suppressions on platform rank change (not implemented; would โ€œrefreshโ€ Discord packages on promotion).