Panel: game server integration API

Game servers (Paper plugin, Fabric/Forge mods) authenticate with a
per-server token and report to /api/server/v1:

- hello: records software/version, returns the auth server URL
- login: allow/deny with a message (account status + reason, access
  all/members/groups, optional "must join through the launcher" check
  matching the player's recent launch IP)
- sync: online players, TPS, stat deltas and events; answers with
  players to kick when their account was disabled meanwhile

Admins create servers (token shown once, stored hashed), regenerate
tokens, and read leaderboards, events and per-player activity. The
dashboard stats include who's online right now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011ARcGWxLx21FwXJ3yfGriS
This commit is contained in:
Claude committed 2026-09-28 09:04:11 +00:00
1 parent 77a15cab8b
commit a50963f9e0
4 files changed
+885

No files matched your search

+13
View File
@@ -2,6 +2,7 @@ pub mod account;
pub mod admin;
pub mod meta;
pub mod public;
pub mod servers;
use crate::state::AppState;
use axum::extract::DefaultBodyLimit;
@@ -48,6 +49,10 @@ pub fn api(state: &AppState) -> Router<AppState> {
.route("/capes", get(admin::list_capes).post(admin::create_cape))
.route("/capes/{id}", axum::routing::put(admin::update_cape).delete(admin::delete_cape))
.route("/auth-server", get(admin::auth_server_info))
.route("/servers", get(servers::list).post(servers::create))
.route("/servers/{id}", get(servers::detail).put(servers::update).delete(servers::remove))
.route("/servers/{id}/token", post(servers::regenerate_token))
.route("/users/{id}/activity", get(servers::user_activity))
.route("/meta/minecraft", get(meta::minecraft))
.route("/meta/loaders/{loader}", get(meta::loaders))
.route("/modrinth/search", get(meta::modrinth_search))
@@ -56,8 +61,16 @@ pub fn api(state: &AppState) -> Router<AppState> {
.route("/curseforge/mod/{id}/files", get(meta::curseforge_files))
.layer(DefaultBodyLimit::max(upload_limit));
// Called by the SCOPENET Paper plugin and Fabric/Forge mods.
let game = Router::new()
.route("/hello", post(servers::hello))
.route("/login", post(servers::login))
.route("/sync", post(servers::sync))
.layer(DefaultBodyLimit::max(2 * 1024 * 1024));
Router::new()
.nest("/api/v1", launcher)
.nest("/api/server/v1", game)
.nest("/api/admin", admin)
.merge(crate::yggdrasil::routes().layer(DefaultBodyLimit::max(4 * 1024 * 1024)))
}