init
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# Overview
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
RanksPlugin (entry)
|
||||
+-- RanksPermissionProvider implements Hytale's PermissionProvider SPI
|
||||
| +-- RankRepository SQL: ranks, ranks_perms
|
||||
| +-- GrantRepository SQL: ranks_grants
|
||||
| +-- UserPermRepository SQL: ranks_user_perms
|
||||
| +-- RanksCache in-memory per-user effective perms + group memberships
|
||||
+-- RankService high-level business API used by commands
|
||||
+-- ChatPrefixListener PlayerChatEvent -> rewrites formatter with primary rank prefix
|
||||
+-- Commands: /rank, /grant, /tempgrant, /revoke, /primary, /ranks
|
||||
```
|
||||
|
||||
External:
|
||||
- **NetworkCore.getDatabase()** for the MySQL pool
|
||||
- **NetworkCore.getMessageBus()** for cross-server cache invalidation
|
||||
- **PermissionsModule.get()** for `addProvider`/`removeProvider`/`getProviders`
|
||||
|
||||
## Mental model: rank vs group
|
||||
|
||||
In Hytale's permission API the unit is a **group**. We call ours a **rank** in user-facing speak but they're the same thing under the hood. A `Rank` row in the DB IS a Hytale group. Every method on `PermissionProvider` operates on group IDs - we just pass `Rank.id()` through.
|
||||
|
||||
A player can hold multiple ranks at once via `Grant` rows. Hytale's `getGroupsForUser(uuid)` returns the set of active rank IDs (not revoked, not expired). The "primary" flag is OUR extension - Hytale doesn't have a notion of primary. We use it for chat prefix selection.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
- `setup()` - load config.
|
||||
- `start()`:
|
||||
1. Require NetworkCore's DB + bus.
|
||||
2. Bootstrap schema; seed Hytale's built-in groups (`hytale:Adventurer`, `hytale:Admin`, etc.) if absent.
|
||||
3. Build cache + provider.
|
||||
4. If `provider.replace_default`: remove every existing provider from `PermissionsModule`, remember them, add ours.
|
||||
5. Register the chat prefix listener and all commands.
|
||||
- `shutdown()`:
|
||||
1. Remove our provider.
|
||||
2. Re-add any providers we removed at start (so we leave the module in the state we found it).
|
||||
3. Stop the cache (closes the bus subscription).
|
||||
|
||||
## File map
|
||||
|
||||
| File | Responsibility |
|
||||
|---|---|
|
||||
| [RanksPlugin.java](../src/main/java/net/kewwbec/ranks/RanksPlugin.java) | Entry point. Lifecycle, provider registration. |
|
||||
| [RanksPermissionsNodes.java](../src/main/java/net/kewwbec/ranks/RanksPermissionsNodes.java) | String constants for `networkranks.*` nodes. |
|
||||
| [config/](../src/main/java/net/kewwbec/ranks/config/) | Config record + JSON loader. |
|
||||
| [model/Rank.java](../src/main/java/net/kewwbec/ranks/model/Rank.java) | `(id, parentId, displayName, prefix, priority, builtIn)`. |
|
||||
| [model/Grant.java](../src/main/java/net/kewwbec/ranks/model/Grant.java) | One row of `ranks_grants`. |
|
||||
| [db/SchemaBootstrap.java](../src/main/java/net/kewwbec/ranks/db/SchemaBootstrap.java) | `CREATE TABLE IF NOT EXISTS` + seeds built-in groups. |
|
||||
| [db/RankRepository.java](../src/main/java/net/kewwbec/ranks/db/RankRepository.java) | CRUD on `ranks` + `ranks_perms`, includes inheritance walk. |
|
||||
| [db/GrantRepository.java](../src/main/java/net/kewwbec/ranks/db/GrantRepository.java) | CRUD on `ranks_grants`. |
|
||||
| [db/UserPermRepository.java](../src/main/java/net/kewwbec/ranks/db/UserPermRepository.java) | CRUD on `ranks_user_perms`. |
|
||||
| [provider/RanksPermissionProvider.java](../src/main/java/net/kewwbec/ranks/provider/RanksPermissionProvider.java) | Implements `PermissionProvider`. Reads cache, writes through to MySQL. |
|
||||
| [service/RanksCache.java](../src/main/java/net/kewwbec/ranks/service/RanksCache.java) | In-memory cache + cross-server invalidation publisher/subscriber. |
|
||||
| [service/InvalidationPayload.java](../src/main/java/net/kewwbec/ranks/service/InvalidationPayload.java) | Wire payload on the invalidation channel. |
|
||||
| [service/RankService.java](../src/main/java/net/kewwbec/ranks/service/RankService.java) | High-level business API used by commands. |
|
||||
| [listener/ChatPrefixListener.java](../src/main/java/net/kewwbec/ranks/listener/ChatPrefixListener.java) | Sets `PlayerChatEvent` formatter using primary rank's prefix. |
|
||||
| [command/rank/RankCommand.java](../src/main/java/net/kewwbec/ranks/command/rank/RankCommand.java) | `/rank create|delete|addperm|removeperm|setparent|setprefix|list|info`. |
|
||||
| [command/GrantCommands.java](../src/main/java/net/kewwbec/ranks/command/GrantCommands.java) | `/grant`, `/tempgrant`, `/revoke`, `/primary`, `/ranks`. |
|
||||
| [util/DurationParser.java](../src/main/java/net/kewwbec/ranks/util/DurationParser.java) | Parses `30d`, `1mo`, `1y`, etc. |
|
||||
| [util/TargetResolver.java](../src/main/java/net/kewwbec/ranks/util/TargetResolver.java) | Resolves a name (online players only) or UUID string to a UUID. |
|
||||
|
||||
## Why this shape
|
||||
|
||||
- **One provider per server, single source of truth**: replacing Hytale's default removes the risk of two providers giving conflicting answers.
|
||||
- **Cache around the provider**: hasPermission gets called many times per tick (chat enforcement, command parsing, world actions). Hitting MySQL each time would be a disaster; caching by UUID with bus-based invalidation is the standard pattern.
|
||||
- **Repositories return models, the service orchestrates**: keeps SQL out of the provider and command handlers.
|
||||
- **`built_in` flag on ranks**: stops `/rank delete hytale:Admin` from breaking the world.
|
||||
Reference in New Issue
Block a user