Tweak search sorting (#5464)
* Tweak search sorting * Tweak search sorting * fix ping impl * remove port field, add server regions * fix compile * fix tests * update frontend banner upload size limit * feat: use server project region instead of country * remove java and bedrock port in frontend * add helper text * allow filtering by if server is online * add server status online offline filter * use region in instance * pre-collapse status in app discovery * pnpm prepr * remove server discovery flag * add servers into mobile nav tabs * parse port from address if present --------- Co-authored-by: tdgao <mr.trumgao@gmail.com>
This commit is contained in:
@@ -21,7 +21,7 @@ actix-ws = { workspace = true }
|
||||
arc-swap = { workspace = true }
|
||||
argon2 = { workspace = true }
|
||||
ariadne = { workspace = true }
|
||||
async-minecraft-ping = { workspace = true }
|
||||
async-minecraft-ping = { workspace = true, features = ["srv"] }
|
||||
async-stripe = { workspace = true, features = [
|
||||
"billing",
|
||||
"checkout",
|
||||
@@ -44,7 +44,6 @@ deadpool-redis.workspace = true
|
||||
derive_more = { workspace = true, features = ["deref", "deref_mut"] }
|
||||
dotenvy = { workspace = true }
|
||||
either = { workspace = true }
|
||||
elytra-ping = { workspace = true }
|
||||
eyre = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
|
||||
@@ -172,7 +172,6 @@ pub async fn init_client_with_database(
|
||||
recorded DateTime64(4),
|
||||
project_id UInt64,
|
||||
address String,
|
||||
port UInt16,
|
||||
online Bool,
|
||||
latency_ms Nullable(UInt32),
|
||||
description Nullable(String),
|
||||
@@ -219,5 +218,15 @@ pub async fn init_client_with_database(
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
client
|
||||
.query(&format!(
|
||||
"
|
||||
ALTER TABLE {database}.{MINECRAFT_JAVA_SERVER_PINGS} {cluster_line}
|
||||
DROP COLUMN IF EXISTS port
|
||||
"
|
||||
))
|
||||
.execute()
|
||||
.await?;
|
||||
|
||||
Ok(client.with_database(database))
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ mod tests {
|
||||
minecraft_server: Some(ServerProject {
|
||||
max_players: None,
|
||||
country: None,
|
||||
region: None,
|
||||
languages: vec![],
|
||||
active_version: None,
|
||||
}),
|
||||
|
||||
@@ -101,6 +101,15 @@ component::define! {
|
||||
#[validate(length(min = 2, max = 2))]
|
||||
pub country: Option<String>,
|
||||
#[base(serde(default))]
|
||||
#[edit(serde(
|
||||
default,
|
||||
skip_serializing_if = "Option::is_none",
|
||||
with = "serde_with::rust::double_option"
|
||||
))]
|
||||
#[create(optional)]
|
||||
/// Geographical region which this server is hosted in.
|
||||
pub region: Option<ServerRegion>,
|
||||
#[base(serde(default))]
|
||||
#[edit(serde(default))]
|
||||
#[create(default)]
|
||||
/// Languages which the owners of this server prefer.
|
||||
@@ -129,11 +138,6 @@ component::define! {
|
||||
/// Address (IP or domain name) of the Bedrock server, excluding port.
|
||||
#[validate(length(max = 255))]
|
||||
pub address: String,
|
||||
#[base()]
|
||||
#[edit(serde(default))]
|
||||
#[create(required)]
|
||||
/// Port which the server runs on.
|
||||
pub port: u16,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +171,6 @@ pub struct JavaServerProject {
|
||||
/// Address (IP or domain name) of the Java server, excluding port.
|
||||
#[validate(length(max = 255))]
|
||||
pub address: String,
|
||||
/// Port which the server runs on.
|
||||
pub port: u16,
|
||||
/// What game content this server is using.
|
||||
#[serde(default)]
|
||||
pub content: ServerContent,
|
||||
@@ -180,15 +182,12 @@ pub struct JavaServerProjectEdit {
|
||||
#[serde(default)]
|
||||
pub address: Option<String>,
|
||||
#[serde(default)]
|
||||
pub port: Option<u16>,
|
||||
#[serde(default)]
|
||||
pub content: Option<ServerContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct JavaServerProjectQuery {
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
pub content: ServerContentQuery,
|
||||
pub ping: Option<JavaServerPing>,
|
||||
pub verified_plays_2w: Option<u64>,
|
||||
@@ -229,7 +228,6 @@ impl ComponentQuery for JavaServerProjectQuery {
|
||||
let analytics = context.minecraft_server_analytics.get(&project_id);
|
||||
Ok(Self {
|
||||
address: serial.address,
|
||||
port: serial.port,
|
||||
content: match serial.content {
|
||||
ServerContent::Vanilla {
|
||||
supported_game_versions,
|
||||
@@ -276,7 +274,6 @@ impl ComponentEdit for JavaServerProjectEdit {
|
||||
fn create(self) -> Result<Self::Component> {
|
||||
Ok(JavaServerProject {
|
||||
address: self.address.wrap_err("missing `address`")?,
|
||||
port: self.port.wrap_err("missing `port`")?,
|
||||
content: self.content.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
@@ -285,9 +282,6 @@ impl ComponentEdit for JavaServerProjectEdit {
|
||||
if let Some(address) = self.address {
|
||||
component.address = address;
|
||||
}
|
||||
if let Some(port) = self.port {
|
||||
component.port = port;
|
||||
}
|
||||
if let Some(content) = self.content {
|
||||
component.content = content;
|
||||
}
|
||||
@@ -347,6 +341,29 @@ impl Default for ServerContent {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
utoipa::ToSchema,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ServerRegion {
|
||||
UsEast,
|
||||
UsWest,
|
||||
Europe,
|
||||
Asia,
|
||||
Australia,
|
||||
SouthAmerica,
|
||||
MiddleEast,
|
||||
Russia,
|
||||
}
|
||||
|
||||
/// Recorded ping attempt that Labrinth made to a Minecraft Java server project.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct JavaServerPing {
|
||||
@@ -354,8 +371,6 @@ pub struct JavaServerPing {
|
||||
pub when: DateTime<Utc>,
|
||||
/// Address of the server at the time of the ping.
|
||||
pub address: String,
|
||||
/// Port of the server at the time of the ping.
|
||||
pub port: u16,
|
||||
/// If the ping was successful, info on the ping response.
|
||||
pub data: Option<JavaServerPingData>,
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::sync::Semaphore;
|
||||
use tokio::task::JoinSet;
|
||||
use tracing::{Instrument, debug, info, info_span, trace, warn};
|
||||
use tracing::{Instrument, info, info_span, trace, warn};
|
||||
|
||||
pub struct ServerPingQueue {
|
||||
pub db: PgPool,
|
||||
@@ -44,17 +44,16 @@ impl ServerPingQueue {
|
||||
let pings = server_projects
|
||||
.into_iter()
|
||||
.map(|(project_id, java_server)| {
|
||||
let address = java_server.address.to_string();
|
||||
let port = java_server.port;
|
||||
let span = info_span!("ping", %project_id, %address, %port);
|
||||
let span = info_span!("ping", %project_id, address = %java_server.address);
|
||||
|
||||
let active_pings = active_pings.clone();
|
||||
let address = java_server.address;
|
||||
let task = async move {
|
||||
let _permit = active_pings.acquire().await.expect("semaphore should not be closed now");
|
||||
|
||||
let mut retries = ENV.SERVER_PING_RETRIES;
|
||||
let result = loop {
|
||||
match ping_server(&address, port, None).await {
|
||||
match ping_server(&address, None).await {
|
||||
Ok(ping) => {
|
||||
info!(?ping, "Received successful ping");
|
||||
break Ok(ping);
|
||||
@@ -73,8 +72,7 @@ impl ServerPingQueue {
|
||||
|
||||
(project_id, exp::minecraft::JavaServerPing {
|
||||
when: Utc::now(),
|
||||
address: address.to_string(),
|
||||
port,
|
||||
address,
|
||||
data: result.ok(),
|
||||
})
|
||||
};
|
||||
@@ -108,7 +106,6 @@ impl ServerPingQueue {
|
||||
/ 100_000,
|
||||
project_id: project_id.0,
|
||||
address: ping.address.clone(),
|
||||
port: ping.port,
|
||||
latency_ms: data.map(|d| d.latency.as_millis() as u32),
|
||||
description: data.map(|d| d.description.clone()),
|
||||
version_name: data.map(|d| d.version_name.clone()),
|
||||
@@ -251,7 +248,6 @@ impl ServerPingQueue {
|
||||
|
||||
pub async fn ping_server(
|
||||
address: &str,
|
||||
port: u16,
|
||||
timeout: Option<Duration>,
|
||||
) -> eyre::Result<exp::minecraft::JavaServerPingData> {
|
||||
let start = Instant::now();
|
||||
@@ -260,97 +256,45 @@ pub async fn ping_server(
|
||||
.map(|duration| duration.min(default_duration))
|
||||
.unwrap_or(default_duration);
|
||||
|
||||
let task_ep = async move {
|
||||
fn map_component(c: elytra_ping::parse::TextComponent) -> String {
|
||||
match c {
|
||||
elytra_ping::parse::TextComponent::Plain(t) => t,
|
||||
elytra_ping::parse::TextComponent::Fancy(t) => {
|
||||
t.text.unwrap_or_default()
|
||||
}
|
||||
elytra_ping::parse::TextComponent::Extra(e) => e
|
||||
.into_iter()
|
||||
.map(map_component)
|
||||
.collect::<Vec<String>>()
|
||||
.join(""),
|
||||
}
|
||||
let (address, port) = match address.rsplit_once(':') {
|
||||
Some((addr, port)) => {
|
||||
let port = port.parse::<u16>().wrap_err("invalid port number")?;
|
||||
(addr, port)
|
||||
}
|
||||
None => (address, 25565),
|
||||
};
|
||||
|
||||
let (result, latency) =
|
||||
elytra_ping::ping_or_timeout((address.to_string(), port), timeout)
|
||||
.await?;
|
||||
let task = async move {
|
||||
let conn = async_minecraft_ping::ConnectionConfig::build(address)
|
||||
.with_port(port)
|
||||
.with_srv_lookup()
|
||||
.connect()
|
||||
.await
|
||||
.wrap_err("failed to connect to server")?;
|
||||
|
||||
let status = conn
|
||||
.status()
|
||||
.await
|
||||
.wrap_err("failed to get server status")?
|
||||
.status;
|
||||
|
||||
eyre::Ok(exp::minecraft::JavaServerPingData {
|
||||
latency,
|
||||
version_name: result
|
||||
.version
|
||||
.as_ref()
|
||||
.map(|v| v.name.to_string())
|
||||
.unwrap_or_default(),
|
||||
version_protocol: result
|
||||
.version
|
||||
.as_ref()
|
||||
.map(|v| v.protocol.cast_unsigned())
|
||||
.unwrap_or_default(),
|
||||
description: map_component(result.description),
|
||||
players_online: result
|
||||
.players
|
||||
.as_ref()
|
||||
.map(|p| p.online)
|
||||
.unwrap_or(0),
|
||||
players_max: result.players.as_ref().map(|p| p.max).unwrap_or(0),
|
||||
latency: start.elapsed(),
|
||||
version_name: status.version.name,
|
||||
version_protocol: status.version.protocol,
|
||||
description: match status.description {
|
||||
ServerDescription::Plain(text)
|
||||
| ServerDescription::Object { text } => text,
|
||||
},
|
||||
players_online: status.players.online,
|
||||
players_max: status.players.max,
|
||||
})
|
||||
};
|
||||
|
||||
let task_amp = async move {
|
||||
let task = async move {
|
||||
let conn = async_minecraft_ping::ConnectionConfig::build(address)
|
||||
.with_port(port)
|
||||
.connect()
|
||||
.await
|
||||
.wrap_err("failed to connect to server")?;
|
||||
|
||||
let status = conn
|
||||
.status()
|
||||
.await
|
||||
.wrap_err("failed to get server status")?
|
||||
.status;
|
||||
|
||||
eyre::Ok(exp::minecraft::JavaServerPingData {
|
||||
latency: start.elapsed(),
|
||||
version_name: status.version.name,
|
||||
version_protocol: status.version.protocol,
|
||||
description: match status.description {
|
||||
ServerDescription::Plain(text)
|
||||
| ServerDescription::Object { text } => text,
|
||||
},
|
||||
players_online: status.players.online,
|
||||
players_max: status.players.max,
|
||||
})
|
||||
};
|
||||
|
||||
tokio::time::timeout(timeout, task)
|
||||
.await
|
||||
.map_err(eyre::Error::new)
|
||||
.flatten()
|
||||
};
|
||||
|
||||
async move {
|
||||
let (result_ep, result_amp) = (task_ep.await, task_amp.await);
|
||||
|
||||
let result_ep = result_ep
|
||||
.inspect(|_| debug!("Successful ping with `elytra_ping`"))
|
||||
.inspect_err(|err| {
|
||||
debug!("Failed to ping with `elytra_ping`: {err:#}")
|
||||
});
|
||||
let result_amp = result_amp
|
||||
.inspect(|_| debug!("Successful ping with `async_minecraft_ping`"))
|
||||
.inspect_err(|err| {
|
||||
debug!("Failed to ping with `async_minecraft_ping`: {err:#}")
|
||||
});
|
||||
|
||||
result_ep.or(result_amp)
|
||||
}
|
||||
.await
|
||||
tokio::time::timeout(timeout, task)
|
||||
.await
|
||||
.map_err(eyre::Error::new)
|
||||
.flatten()
|
||||
}
|
||||
|
||||
#[derive(Debug, Row, Serialize, Clone)]
|
||||
@@ -358,7 +302,6 @@ struct ServerPingRecord {
|
||||
recorded: i64,
|
||||
project_id: u64,
|
||||
address: String,
|
||||
port: u16,
|
||||
latency_ms: Option<u32>,
|
||||
description: Option<String>,
|
||||
version_name: Option<String>,
|
||||
@@ -373,19 +316,22 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_server_success() {
|
||||
let _status = ping_server("mc.hypixel.net", 25565, None).await.unwrap();
|
||||
let _status = ping_server("mc.hypixel.net", None).await.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_follow_srv_record() {
|
||||
_ = ping_server("hypixel.net", None).await.unwrap();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_server_invalid_address() {
|
||||
_ = ping_server("invalid.invalid", 25565, None)
|
||||
.await
|
||||
.unwrap_err();
|
||||
_ = ping_server("invalid.invalid", None).await.unwrap_err();
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_ping_zero_timeout() {
|
||||
_ = ping_server("hypixel.net", 25565, Some(Duration::ZERO))
|
||||
_ = ping_server("mc.hypixel.net", Some(Duration::ZERO))
|
||||
.await
|
||||
.unwrap_err();
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ pub fn config(cfg: &mut utoipa_actix_web::service_config::ServiceConfig) {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
|
||||
pub struct PingRequest {
|
||||
pub address: String,
|
||||
pub port: u16,
|
||||
pub timeout_ms: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -42,7 +41,7 @@ pub async fn ping_minecraft_java(
|
||||
.await?;
|
||||
|
||||
let timeout = request.timeout_ms.map(Duration::from_millis);
|
||||
server_ping::ping_server(&request.address, request.port, timeout)
|
||||
server_ping::ping_server(&request.address, timeout)
|
||||
.await
|
||||
.wrap_request_err("failed to ping server")?;
|
||||
|
||||
|
||||
@@ -646,6 +646,7 @@ const DEFAULT_ATTRIBUTES_FOR_FACETING: &[&str] = &[
|
||||
"minecraft_java_server.content.supported_game_versions",
|
||||
"minecraft_java_server.content.recommended_game_version",
|
||||
"minecraft_java_server.verified_plays_2w",
|
||||
"minecraft_java_server.ping.data",
|
||||
"minecraft_java_server.ping.data.players_online",
|
||||
];
|
||||
|
||||
|
||||
@@ -274,9 +274,9 @@ pub fn get_sort_index(
|
||||
"relevance" => (
|
||||
projects_name,
|
||||
&[
|
||||
"downloads:desc",
|
||||
"minecraft_java_server.verified_plays_2w:desc",
|
||||
"minecraft_java_server.ping.data.players_online:desc",
|
||||
"downloads:desc",
|
||||
],
|
||||
),
|
||||
"downloads" => (projects_filtered_name, &["downloads:desc"]),
|
||||
|
||||
Reference in New Issue
Block a user