Files
core-ranks/docs/04_Schema.md
T
2026-05-26 23:16:33 -04:00

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
  • id is the canonical group name. Hytale's built-ins use the hytale: prefix (e.g. hytale:Admin); your custom ranks should just be lowercase (e.g. vip).
  • parent_id is null or another rank id. Inheritance walks the chain.
  • built_in = 1 rows 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;