BanEntry¶
The BanEntry table maintains a cluster-wide record of all player bans. Every ban — whether permanent or temporary, active or revoked — is stored as a row in this table. Game hooks subscribe to the full table to enforce bans in real time across all servers simultaneously.
Scope¶
🌍 Global — All game servers subscribe to the complete BanEntry table. When a ban is issued or revoked, every server in the cluster receives the update within milliseconds via SpacetimeDB's subscription push.
Schema¶
| Column | Type | Constraints | Description |
|---|---|---|---|
ban_id |
u64 |
Primary Key, auto-increment | Unique identifier for this ban record |
player_id |
String |
Indexed | Platform-wide player identifier of the banned player |
player_name |
String |
— | Display name of the banned player at time of ban |
reason |
String |
— | Human-readable reason for the ban |
banned_by |
String |
— | Identifier of the admin who issued the ban |
banned_at |
Timestamp |
— | When the ban was issued |
expires_at |
Option<Timestamp> |
— | When the ban expires (None = permanent) |
revoked |
bool |
— | Whether the ban has been manually revoked |
revoked_by |
String |
— | Identifier of the admin who revoked the ban (empty if not revoked) |
revoked_at |
Option<Timestamp> |
— | When the ban was revoked (None if still active) |
Rust Definition¶
#[spacetimedb::table(public, name = ban_entry)]
pub struct BanEntry {
#[primary_key]
#[auto_inc]
pub ban_id: u64,
#[index(btree)]
pub player_id: String,
pub player_name: String,
pub reason: String,
pub banned_by: String,
pub banned_at: Timestamp,
pub expires_at: Option<Timestamp>,
pub revoked: bool,
pub revoked_by: String,
pub revoked_at: Option<Timestamp>,
}
Usage Patterns¶
Ban Enforcement¶
Game hooks determine whether a player is banned by querying the subscribed BanEntry data. A player is considered actively banned when all of the following are true:
- A
BanEntryexists with a matchingplayer_id. revokedisfalse.- Either
expires_atisNone(permanent) orexpires_atis in the future.
// Pseudocode — game hook ban check on player connect
fn is_player_banned(player_id: &str) -> bool {
BanEntry::filter_by_player_id(player_id)
.any(|ban| !ban.revoked && !is_expired(&ban))
}
fn is_expired(ban: &BanEntry) -> bool {
match ban.expires_at {
Some(expiry) => Timestamp::now() > expiry,
None => false, // Permanent ban — never expires
}
}
Soft-Delete Model¶
Bans are never physically deleted from the table. Instead, revocation is recorded by setting revoked = true, revoked_by, and revoked_at. This preserves a complete audit trail — administrators can review a player's full ban history, including bans that were later overturned.
Multiple Bans Per Player¶
The index on player_id is a non-unique index, meaning a single player can accumulate multiple ban records over time. The enforcement logic must check all matching rows to determine current ban status.
Temporary Bans¶
When expires_at is set to a future timestamp, the ban automatically becomes inactive after that time. Game hooks are responsible for checking the expiry during connection attempts. No reducer or scheduled task is needed to "expire" bans — the expiry is evaluated at query time.
Related Reducers¶
| Reducer | Operation |
|---|---|
admin_ban |
Inserts a new BanEntry row |
admin_unban |
Sets revoked = true on matching ban(s) |
panel_ban_player |
Panel wrapper → admin_ban |
panel_unban_player |
Panel wrapper → admin_unban |
Related Subscriptions¶
| Subscriber | Query | Purpose |
|---|---|---|
| Game Hook | SELECT * FROM ban_entry |
Real-time ban enforcement on all servers |
| Admin Panel | SELECT * FROM ban_entry |
Ban management UI, player history |
For full subscription architecture, see Subscriptions.
Related Pages¶
- Platform Overview — How BanEntry fits in the 12-table platform model
- WhitelistEntry — Complementary access control table
- AdminRole — Roles that authorize ban operations
- PanelAuditLog — Audit trail for ban/unban actions via panel
- Multi-Server Architecture — Global ban sync as a cross-server capability