4.3 KiB
Schema
Four MySQL tables, created on start() via CREATE TABLE IF NOT EXISTS. Prefixed ranks_ (except for ranks itself).
ranks
CREATE TABLE IF NOT EXISTS ranks (
id VARCHAR(64) NOT NULL,
parent_id VARCHAR(64) NULL,
display_name VARCHAR(64) NOT NULL,
prefix VARCHAR(64) NOT NULL DEFAULT '',
priority INT NOT NULL DEFAULT 0,
built_in TINYINT(1) NOT NULL DEFAULT 0,
created_at BIGINT NOT NULL,
PRIMARY KEY (id),
KEY idx_priority (priority)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
idis the canonical group name. Hytale's built-ins use thehytale:prefix (e.g.hytale:Admin); your custom ranks should just be lowercase (e.g.vip).parent_idis null or another rank id. Inheritance walks the chain.built_in = 1rows can't be deleted via/rank delete. Set by the bootstrap seeding.
ranks_perms
CREATE TABLE IF NOT EXISTS ranks_perms (
rank_id VARCHAR(64) NOT NULL,
permission VARCHAR(255) NOT NULL,
PRIMARY KEY (rank_id, permission),
KEY idx_rank (rank_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Direct permissions for each rank. * is the wildcard (hytale:Admin has it by default).
ranks_user_perms
CREATE TABLE IF NOT EXISTS ranks_user_perms (
user_uuid CHAR(36) NOT NULL,
permission VARCHAR(255) NOT NULL,
PRIMARY KEY (user_uuid, permission)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Direct (per-user) permissions, bypassing ranks. We don't expose commands for this in v1 - it exists because the PermissionProvider SPI requires it and Hytale's /perm user add writes here through addUserPermissions.
ranks_grants
CREATE TABLE IF NOT EXISTS ranks_grants (
id BIGINT NOT NULL AUTO_INCREMENT,
user_uuid CHAR(36) NOT NULL,
rank_id VARCHAR(64) NOT NULL,
granted_at BIGINT NOT NULL,
granted_by_uuid CHAR(36) NOT NULL,
granted_by_name VARCHAR(64) NOT NULL,
expires_at BIGINT NULL,
primary_rank TINYINT(1) NOT NULL DEFAULT 0,
revoked TINYINT(1) NOT NULL DEFAULT 0,
revoked_at BIGINT NULL,
revoked_by_uuid CHAR(36) NULL,
revoked_by_name VARCHAR(64) NULL,
PRIMARY KEY (id),
KEY idx_user (user_uuid),
KEY idx_user_active (user_uuid, revoked, expires_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Each grant is a separate row. Revokes don't delete - they flip revoked = 1 and fill the revoked_* columns. Useful for "who took my rank away" forensics.
A user can have multiple active rows of the same rank. getActiveRankIds uses DISTINCT rank_id so the set is unique.
primary_rank flag is OUR extension on top of Hytale's group model. Only one row per user should have it set; RankService.setPrimary enforces this by clearing first.
Built-in group seeding
On first boot, SchemaBootstrap.seedBuiltInGroups inserts these rows if absent:
| id | parent_id | display | built_in |
|---|---|---|---|
hytale:None |
null | None | 1 |
hytale:Adventurer |
hytale:None |
Adventurer | 1 |
hytale:Builder |
hytale:Adventurer |
Builder | 1 |
hytale:WorldEditor |
hytale:Builder |
WorldEditor | 1 |
hytale:ServerEditor |
hytale:WorldEditor |
ServerEditor | 1 |
hytale:Admin |
hytale:ServerEditor |
Admin | 1 |
And one permission row: hytale:Admin → * (wildcard).
This mirrors what Hytale's HytalePermissionsProvider ships with - so /op continuing to grant Admin and Admin having all perms keeps working without any extra setup.
Migrations
There aren't any yet. If you change the schema, add a migrations/00X_what.sql and a small runner in SchemaBootstrap that tracks applied migrations in a ranks_schema_version table. Don't reach for Flyway/Liquibase until you have at least three migrations.
Useful queries
Active ranks of a player:
SELECT DISTINCT rank_id
FROM ranks_grants
WHERE user_uuid = ?
AND revoked = 0
AND (expires_at IS NULL OR expires_at > UNIX_TIMESTAMP() * 1000)
ORDER BY primary_rank DESC, rank_id;
How many players hold each rank right now:
SELECT rank_id, COUNT(DISTINCT user_uuid) AS holders
FROM ranks_grants
WHERE revoked = 0
AND (expires_at IS NULL OR expires_at > UNIX_TIMESTAMP() * 1000)
GROUP BY rank_id
ORDER BY holders DESC;
All-time grants by staff member (audit):
SELECT granted_at, user_uuid, rank_id, expires_at
FROM ranks_grants
WHERE granted_by_name = 'StaffName'
ORDER BY granted_at DESC;