Integrate Vantage live map for guild claims [skip ci]
This commit is contained in:
1 parent
52de8c3031
commit
602e7fd0f0
17 files changed
+470
-21
No files matched your search
@@ -506,6 +506,9 @@ const MIGRATIONS: &[&str] = &[
|
||||
PRIMARY KEY (post_id, user_uuid)
|
||||
);
|
||||
"#,
|
||||
r#"
|
||||
ALTER TABLE game_servers ADD COLUMN map_url TEXT NOT NULL DEFAULT '';
|
||||
"#,
|
||||
];
|
||||
|
||||
pub async fn connect(data_dir: &Path) -> Result<SqlitePool> {
|
||||
|
||||
@@ -61,6 +61,7 @@ fn clip(s: &str, max: usize) -> String {
|
||||
#[derive(Debug, Clone, sqlx::FromRow, Serialize)]
|
||||
pub struct ServerRow {
|
||||
pub instance_id: String,
|
||||
pub map_url: String,
|
||||
pub id: i64,
|
||||
pub name: String,
|
||||
#[serde(skip)]
|
||||
@@ -832,6 +833,8 @@ pub struct ServerInput {
|
||||
allowed_groups: Vec<String>,
|
||||
#[serde(default)]
|
||||
require_launcher: bool,
|
||||
#[serde(default)]
|
||||
map_url: String,
|
||||
}
|
||||
|
||||
fn default_access() -> String {
|
||||
@@ -849,6 +852,18 @@ impl ServerInput {
|
||||
if self.access == "groups" && self.allowed_groups.is_empty() {
|
||||
return Err(AppError::bad_request("pick at least one group"));
|
||||
}
|
||||
if !self.map_url.trim().is_empty() {
|
||||
let url = reqwest::Url::parse(self.map_url.trim()).map_err(|_| AppError::bad_request("map URL must be an absolute URL"))?;
|
||||
if url.scheme() != "https" && !(url.scheme() == "http" && matches!(url.host_str(), Some("localhost" | "127.0.0.1"))) {
|
||||
return Err(AppError::bad_request("map URL must use HTTPS (or localhost for development)"));
|
||||
}
|
||||
if url.username() != "" || url.password().is_some() || url.fragment().is_some() || url.query().is_some() {
|
||||
return Err(AppError::bad_request("map URL cannot contain credentials, query parameters, or a fragment"));
|
||||
}
|
||||
if !matches!(url.path().rsplit('/').next(), Some("manifest.json" | "world.json")) {
|
||||
return Err(AppError::bad_request("map URL must end with manifest.json or world.json"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -865,7 +880,7 @@ pub async fn create(_: AdminUser, State(state): State<AppState>, Json(input): Js
|
||||
input.validate()?;
|
||||
let token = new_token();
|
||||
let id: i64 = sqlx::query_scalar(
|
||||
"INSERT INTO game_servers (name, token_hash, token_hint, access, allowed_groups, require_launcher, created_at, instance_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
|
||||
"INSERT INTO game_servers (name, token_hash, token_hint, access, allowed_groups, require_launcher, created_at, instance_id, map_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
|
||||
)
|
||||
.bind(input.name.trim())
|
||||
.bind(hash_token(&token))
|
||||
@@ -875,6 +890,7 @@ pub async fn create(_: AdminUser, State(state): State<AppState>, Json(input): Js
|
||||
.bind(input.require_launcher)
|
||||
.bind(now())
|
||||
.bind(&input.instance_id)
|
||||
.bind(input.map_url.trim())
|
||||
.fetch_one(&state.db)
|
||||
.await?;
|
||||
let server = get_server(&state, id).await?;
|
||||
@@ -889,12 +905,13 @@ pub async fn update(
|
||||
) -> AppResult<Json<ServerView>> {
|
||||
input.validate()?;
|
||||
get_server(&state, id).await?;
|
||||
sqlx::query("UPDATE game_servers SET name = ?, access = ?, allowed_groups = ?, require_launcher = ?, instance_id = ? WHERE id = ?")
|
||||
sqlx::query("UPDATE game_servers SET name = ?, access = ?, allowed_groups = ?, require_launcher = ?, instance_id = ?, map_url = ? WHERE id = ?")
|
||||
.bind(input.name.trim())
|
||||
.bind(&input.access)
|
||||
.bind(serde_json::to_string(&input.allowed_groups).unwrap_or_else(|_| "[]".into()))
|
||||
.bind(input.require_launcher)
|
||||
.bind(&input.instance_id)
|
||||
.bind(input.map_url.trim())
|
||||
.bind(id)
|
||||
.execute(&state.db)
|
||||
.await?;
|
||||
@@ -1078,6 +1095,7 @@ pub async fn public_servers(State(state): State<AppState>) -> AppResult<Json<Val
|
||||
"id": s.id,
|
||||
"name": s.name,
|
||||
"instance_id": s.instance_id,
|
||||
"map_url": s.map_url,
|
||||
"online": is_online,
|
||||
"players_online": if is_online { s.online_count } else { 0 },
|
||||
"players_max": s.max_players,
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
<label class="field">Name<input bind:value={draft.name} maxlength="48" placeholder="Survival SMP" /></label>
|
||||
<label class="field">Launcher instance<select bind:value={draft.instance_id}><option value="">Unlinked</option>{#each instances as instance}<option value={instance.id}>{instance.name}</option>{/each}</select></label>
|
||||
<label class="field">Vantage live map manifest URL<input type="url" bind:value={draft.map_url} placeholder="https://maps.example.com/world.json" /><span class="tiny muted">Optional. Use a public Vantage render or a session-gated same-origin endpoint. The URL is visible to launcher users.</span></label>
|
||||
<div class="field">
|
||||
<span class="lbl">Who can join</span>
|
||||
<div class="opts">
|
||||
|
||||
@@ -92,6 +92,7 @@ export interface AuthServerInfo { public_url: string; yggdrasil_url: string; pub
|
||||
|
||||
export interface GameServer {
|
||||
instance_id: string;
|
||||
map_url: string;
|
||||
id: number;
|
||||
name: string;
|
||||
token_hint: string;
|
||||
@@ -140,7 +141,7 @@ export interface ServerDetail {
|
||||
|
||||
export interface UserActivity { servers: PlayerStats[]; events: ServerEvent[]; online_on: { id: number; name: string }[] }
|
||||
|
||||
export type ServerDraft = { instance_id?: string; name: string; access: 'all' | 'members' | 'groups'; allowed_groups: string[]; require_launcher: boolean };
|
||||
export type ServerDraft = { instance_id?: string; name: string; map_url?: string; access: 'all' | 'members' | 'groups'; allowed_groups: string[]; require_launcher: boolean };
|
||||
|
||||
export interface HostedDownload {
|
||||
platform: 'windows' | 'mac' | 'linux';
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
|
||||
function openEdit() {
|
||||
if (!s) return;
|
||||
draft = { instance_id: s.instance_id, name: s.name, access: s.access, allowed_groups: [...s.allowed_groups], require_launcher: s.require_launcher };
|
||||
draft = { instance_id: s.instance_id, name: s.name, map_url: s.map_url, access: s.access, allowed_groups: [...s.allowed_groups], require_launcher: s.require_launcher };
|
||||
editOpen = true;
|
||||
}
|
||||
async function saveEdit() {
|
||||
|
||||
Reference in new issue
Block a user