Rework BlueMap claiming and expand ranks guilds chat and warps
This commit is contained in:
1 parent
434ed5338a
commit
6a2944a88e
68 files changed
+1453
-2500
No files matched your search
@@ -1,16 +1,14 @@
|
||||
//! Live map endpoints: ingest from game servers, the authenticated Vantage
|
||||
//! proxy for launchers/admin, and admin status.
|
||||
//! Live player positions and legacy world-mirror ingest from game servers.
|
||||
|
||||
use super::servers::{get_server, GameServer};
|
||||
use crate::auth::{AdminUser, AuthUser};
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::livemap::{parse_chunks, Dim, LivePlayer};
|
||||
use crate::state::AppState;
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::body::Bytes;
|
||||
use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::http::{header, HeaderMap, HeaderValue, StatusCode};
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::response::Response;
|
||||
use axum::Json;
|
||||
use base64::Engine;
|
||||
use serde::Deserialize;
|
||||
@@ -199,7 +197,6 @@ fn label(d: Dim) -> &'static str {
|
||||
|
||||
async fn info(state: &AppState, id: i64, enabled: bool) -> Value {
|
||||
let summary = state.livemap.world_summary(id);
|
||||
let (generator, assets) = state.livemap.generator_ready();
|
||||
let dims: Vec<Value> = summary["dimensions"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
@@ -209,43 +206,35 @@ async fn info(state: &AppState, id: i64, enabled: bool) -> Value {
|
||||
Some(json!({ "id": dim.id(), "slug": dim.slug(), "label": label(dim), "available": d["regions"].as_u64().unwrap_or(0) > 0 }))
|
||||
})
|
||||
.collect();
|
||||
let has_world = summary["level_dat"].as_bool().unwrap_or(false) && dims.iter().any(|d| d["available"] == true);
|
||||
let message = if !enabled {
|
||||
"Live Map is off for this server."
|
||||
} else if !generator || !assets {
|
||||
"The map generator isn't set up on the panel yet."
|
||||
} else if !has_world {
|
||||
"Waiting for the game server to upload its world."
|
||||
"Live player positions are off for this server."
|
||||
} else {
|
||||
""
|
||||
};
|
||||
json!({
|
||||
"enabled": enabled,
|
||||
"ready": enabled && generator && assets && has_world,
|
||||
"ready": enabled,
|
||||
"message": message,
|
||||
"base": format!("/api/livemap/{id}"),
|
||||
"dimensions": dims,
|
||||
"players": state.livemap.live_players(id).len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// What the launcher needs to decide whether to offer the Live Map button.
|
||||
pub async fn viewer_info(_: AuthUser, State(state): State<AppState>, Path(id): Path<i64>) -> AppResult<Json<Value>> {
|
||||
/// A signed-in player's own live position for the territory map.
|
||||
pub async fn viewer_info(AuthUser(auth): AuthUser, State(state): State<AppState>, Path(id): Path<i64>) -> AppResult<Json<Value>> {
|
||||
let server = get_server(&state, id).await?;
|
||||
Ok(Json(info(&state, id, server.live_map_enabled).await))
|
||||
let mut result = info(&state, id, server.live_map_enabled).await;
|
||||
result["me"] = serde_json::to_value(state.livemap.live_players(id).into_iter()
|
||||
.find(|player| player.uuid.eq_ignore_ascii_case(&auth.uuid)))?;
|
||||
Ok(Json(result))
|
||||
}
|
||||
|
||||
pub async fn admin_status(_: AdminUser, State(state): State<AppState>, Path(id): Path<i64>) -> AppResult<Json<Value>> {
|
||||
let server = get_server(&state, id).await?;
|
||||
let mut v = info(&state, id, server.live_map_enabled).await;
|
||||
let (generator, assets) = state.livemap.generator_ready();
|
||||
let stats = state.livemap.stats(id);
|
||||
v["generator"] = json!({ "installed": generator, "path": state.livemap.generator_path() });
|
||||
v["assets"] = json!({ "configured": assets, "path": state.livemap.assets_path() });
|
||||
v["world"] = state.livemap.world_summary(id);
|
||||
v["stats"] = serde_json::to_value(stats)?;
|
||||
v["running"] = json!(state.livemap.sidecars_running(id).await);
|
||||
v["last_error"] = json!(state.livemap.last_error(id));
|
||||
v["roster"] = serde_json::to_value(state.livemap.live_players(id))?;
|
||||
Ok(Json(v))
|
||||
}
|
||||
@@ -255,42 +244,3 @@ pub async fn admin_reset(_: AdminUser, State(state): State<AppState>, Path(id):
|
||||
state.livemap.reset(id).await?;
|
||||
Ok(Json(json!({ "ok": true })))
|
||||
}
|
||||
|
||||
/// Authenticated reverse proxy to the per-server Vantage sidecar. Clients
|
||||
/// use `…/api/livemap/{server}/{dimension}/v1/worlds/default/manifest.json`
|
||||
/// and everything the manifest references.
|
||||
pub async fn proxy(
|
||||
_: AuthUser,
|
||||
State(state): State<AppState>,
|
||||
Path((id, dim, path)): Path<(i64, String, String)>,
|
||||
headers: HeaderMap,
|
||||
) -> AppResult<Response> {
|
||||
let server = get_server(&state, id).await?;
|
||||
require_enabled(server.live_map_enabled)?;
|
||||
let dim = dim_of(&dim)?;
|
||||
if path.split('/').any(|s| s == ".." || s.is_empty() || s.contains('\\')) || !path.starts_with("v1/worlds/default/") {
|
||||
return Err(AppError::not_found("not found"));
|
||||
}
|
||||
if path == "v1/worlds/default/players.json" {
|
||||
let mut resp = Json(state.livemap.players_json(id, dim)).into_response();
|
||||
resp.headers_mut().insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
|
||||
return Ok(resp);
|
||||
}
|
||||
let (port, token) = state.livemap.ensure_sidecar(id, dim).await.map_err(|m| AppError::new(StatusCode::SERVICE_UNAVAILABLE, m))?;
|
||||
let mut req = state.http.get(format!("http://127.0.0.1:{port}/{path}")).bearer_auth(token).timeout(std::time::Duration::from_secs(120));
|
||||
if let Some(etag) = headers.get(header::IF_NONE_MATCH) {
|
||||
req = req.header(header::IF_NONE_MATCH, etag);
|
||||
}
|
||||
let upstream = req.send().await.map_err(|e| AppError::new(StatusCode::BAD_GATEWAY, format!("map generator unreachable: {e}")))?;
|
||||
let mut out = Response::builder().status(upstream.status());
|
||||
for name in [header::CONTENT_TYPE, header::ETAG, header::CACHE_CONTROL, header::LAST_MODIFIED] {
|
||||
if let Some(v) = upstream.headers().get(&name) {
|
||||
out = out.header(name, v);
|
||||
}
|
||||
}
|
||||
// Tiles are immutable per etag; make sure shared caches never keep them.
|
||||
Ok(out
|
||||
.header(header::VARY, "Authorization")
|
||||
.body(Body::from_stream(upstream.bytes_stream()))
|
||||
.map_err(|e| AppError::new(StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?)
|
||||
}
|
||||
Reference in new issue
Block a user