mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-04 02:34:13 +02:00
demo filtering
This commit is contained in:
parent
cb56c80555
commit
57d034159b
14 changed files with 402 additions and 35 deletions
147
src/data/demo.rs
147
src/data/demo.rs
|
|
@ -1,10 +1,20 @@
|
|||
use crate::data::chat::Chat;
|
||||
use crate::data::player::Player;
|
||||
use crate::data::schema::{ArrayAgg, CleanMapName, Demos, Players};
|
||||
use crate::data::steam_id::SteamId;
|
||||
use crate::Result;
|
||||
use maud::Render;
|
||||
use sea_query::extension::postgres::PgExpr;
|
||||
use sea_query::{
|
||||
Alias, Expr, Func, Order, PostgresQueryBuilder, Query, SelectStatement, SimpleExpr,
|
||||
};
|
||||
use sea_query_binder::SqlxBinder;
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use sqlx::{query_as, Executor, FromRow, Postgres};
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::ops::Range;
|
||||
use std::str::FromStr;
|
||||
use time::format_description::well_known::Iso8601;
|
||||
use time::{OffsetDateTime, PrimitiveDateTime, UtcOffset};
|
||||
use tracing::instrument;
|
||||
|
|
@ -156,19 +166,9 @@ impl ListDemo {
|
|||
#[instrument(skip(connection))]
|
||||
pub async fn list(
|
||||
connection: impl Executor<'_, Database = Postgres>,
|
||||
before: Option<u32>,
|
||||
filter: Filter,
|
||||
) -> Result<Vec<Self>> {
|
||||
if let Some(before) = before {
|
||||
Ok(query_as!(
|
||||
ListDemo,
|
||||
r#"SELECT
|
||||
id, name, map, red, blu, duration, created_at, server, "playerCount" as player_count
|
||||
FROM demos WHERE deleted_at IS NULL and id < $1 ORDER BY id DESC LIMIT 50"#,
|
||||
before as i32
|
||||
)
|
||||
.fetch_all(connection)
|
||||
.await?)
|
||||
} else {
|
||||
if filter.is_empty() {
|
||||
Ok(query_as!(
|
||||
ListDemo,
|
||||
r#"SELECT
|
||||
|
|
@ -177,6 +177,31 @@ impl ListDemo {
|
|||
)
|
||||
.fetch_all(connection)
|
||||
.await?)
|
||||
} else {
|
||||
let mut query = Query::select();
|
||||
query
|
||||
.column((Demos::Table, Demos::Id))
|
||||
.column((Demos::Table, Demos::Name))
|
||||
.columns([
|
||||
Demos::Map,
|
||||
Demos::Red,
|
||||
Demos::Blu,
|
||||
Demos::Duration,
|
||||
Demos::Server,
|
||||
Demos::CreatedAt,
|
||||
])
|
||||
.expr_as(Expr::col(Demos::PlayerCount), Alias::new("player_count"))
|
||||
.from(Demos::Table)
|
||||
.and_where(Expr::col(Demos::DeletedAt).is_null())
|
||||
.order_by(Demos::Id, Order::Desc)
|
||||
.limit(50);
|
||||
filter.apply(&mut query);
|
||||
|
||||
let (sql, values) = query.build_sqlx(PostgresQueryBuilder);
|
||||
|
||||
Ok(sqlx::query_as_with::<_, ListDemo, _>(&sql, values)
|
||||
.fetch_all(connection)
|
||||
.await?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -314,3 +339,101 @@ impl Render for RelativeDate {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
pub enum GameMode {
|
||||
#[serde(rename = "4v4")]
|
||||
Fours,
|
||||
#[serde(rename = "6v6")]
|
||||
Sixes,
|
||||
#[serde(rename = "prolander")]
|
||||
Prolander,
|
||||
#[serde(rename = "highlander")]
|
||||
HighLander,
|
||||
#[default]
|
||||
#[serde(other)]
|
||||
Any,
|
||||
}
|
||||
|
||||
impl GameMode {
|
||||
fn player_count(&self) -> Option<Range<i32>> {
|
||||
match self {
|
||||
GameMode::Fours => Some(7..9),
|
||||
GameMode::Sixes => Some(11..13),
|
||||
GameMode::Prolander => Some(14..15),
|
||||
GameMode::HighLander => Some(17..19),
|
||||
GameMode::Any => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Deserialize)]
|
||||
pub struct Filter {
|
||||
#[serde(default)]
|
||||
mode: GameMode,
|
||||
#[serde(default)]
|
||||
map: String,
|
||||
#[serde(default)]
|
||||
#[serde(deserialize_with = "deserialize_array")]
|
||||
players: Vec<i32>,
|
||||
#[serde(default)]
|
||||
before: Option<i32>,
|
||||
}
|
||||
|
||||
fn deserialize_array<'de, D, T>(deserializer: D) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: Deserialize<'de> + FromStr,
|
||||
{
|
||||
let s = <Cow<str>>::deserialize(deserializer)?;
|
||||
Ok(s.split(",").map(T::from_str).flatten().collect())
|
||||
}
|
||||
|
||||
impl Filter {
|
||||
fn is_empty(&self) -> bool {
|
||||
self.mode != GameMode::default()
|
||||
&& self.map.is_empty()
|
||||
&& self.before.is_none()
|
||||
&& self.players.is_empty()
|
||||
}
|
||||
|
||||
fn apply(&self, query: &mut SelectStatement) {
|
||||
if let Some(count) = self.mode.player_count() {
|
||||
query.and_where(Expr::col(Demos::PlayerCount).between(count.start, count.end));
|
||||
}
|
||||
if !self.map.is_empty() {
|
||||
let val = Expr::value(&self.map);
|
||||
query.and_where(
|
||||
Expr::col(Demos::Map).eq(val.clone()).or(SimpleExpr::from(
|
||||
Func::cust(CleanMapName).arg(Expr::col(Demos::Map)),
|
||||
)
|
||||
.eq(val)),
|
||||
);
|
||||
}
|
||||
if let Some(before) = &self.before {
|
||||
query.and_where(Expr::col(Demos::Id).lt(*before));
|
||||
}
|
||||
if !self.players.is_empty() && self.players.len() < 19 {
|
||||
let mut player_iter = self.players.iter();
|
||||
let mut players = format!("array[{}", player_iter.next().unwrap());
|
||||
for player in player_iter {
|
||||
write!(&mut players, ",{}", player).unwrap();
|
||||
}
|
||||
players.push_str("]");
|
||||
// query.and_where(Expr::cust(&players).contained(sub_array));
|
||||
|
||||
query
|
||||
.inner_join(
|
||||
Players::Table,
|
||||
Expr::col((Demos::Table, Demos::Id)).equals((Players::Table, Players::DemoId)),
|
||||
)
|
||||
.and_where(Expr::col(Players::UserId).is_in(self.players.clone()));
|
||||
query.group_by_col((Demos::Table, Players::Id));
|
||||
query.and_having(
|
||||
Expr::cust(&players).contained(
|
||||
Func::cust(ArrayAgg).arg(Expr::col((Players::Table, Players::UserId))),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ pub mod chat;
|
|||
pub mod demo;
|
||||
pub mod maps;
|
||||
pub mod player;
|
||||
mod schema;
|
||||
pub mod steam_id;
|
||||
pub mod user;
|
||||
|
|
|
|||
94
src/data/schema.rs
Normal file
94
src/data/schema.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use sea_query::Iden;
|
||||
use std::fmt::Write;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Iden)]
|
||||
pub enum Demos {
|
||||
Table,
|
||||
#[iden = "id"]
|
||||
Id,
|
||||
#[iden = "name"]
|
||||
Name,
|
||||
#[iden = "url"]
|
||||
Url,
|
||||
#[iden = "map"]
|
||||
Map,
|
||||
#[iden = "red"]
|
||||
Red,
|
||||
#[iden = "blu"]
|
||||
Blu,
|
||||
#[iden = "uploader"]
|
||||
Uploader,
|
||||
#[iden = "duration"]
|
||||
Duration,
|
||||
#[iden = "created_at"]
|
||||
CreatedAt,
|
||||
#[iden = "updated_at"]
|
||||
UpdatedAt,
|
||||
#[iden = "backend"]
|
||||
Backend,
|
||||
#[iden = "path"]
|
||||
Path,
|
||||
#[iden = "scoreBlue"]
|
||||
ScoreBlue,
|
||||
#[iden = "scoreRed"]
|
||||
ScoreRed,
|
||||
#[iden = "version"]
|
||||
Version,
|
||||
#[iden = "server"]
|
||||
Server,
|
||||
#[iden = "nick"]
|
||||
Nick,
|
||||
#[iden = "deleted_at"]
|
||||
DeletedAt,
|
||||
#[iden = "playerCount"]
|
||||
PlayerCount,
|
||||
#[iden = "hash"]
|
||||
Hash,
|
||||
#[iden = "blue_team_id"]
|
||||
BlueTeamId,
|
||||
#[iden = "red_team_id"]
|
||||
RedTeamId,
|
||||
}
|
||||
|
||||
pub struct CleanMapName;
|
||||
|
||||
impl Iden for CleanMapName {
|
||||
fn unquoted(&self, s: &mut dyn Write) {
|
||||
write!(s, "clean_map_name").unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Iden)]
|
||||
#[iden = "ARRAY"]
|
||||
pub struct ArrayFunc;
|
||||
|
||||
#[derive(Iden)]
|
||||
#[iden = "array_agg"]
|
||||
pub struct ArrayAgg;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Iden)]
|
||||
pub enum Players {
|
||||
Table,
|
||||
#[iden = "id"]
|
||||
Id,
|
||||
#[iden = "demo_id"]
|
||||
DemoId,
|
||||
#[iden = "demo_user_id"]
|
||||
DemoUserId,
|
||||
#[iden = "user_id"]
|
||||
UserId,
|
||||
#[iden = "name"]
|
||||
Name,
|
||||
#[iden = "team"]
|
||||
Team,
|
||||
#[iden = "class"]
|
||||
Class,
|
||||
#[iden = "kills"]
|
||||
Kills,
|
||||
#[iden = "assists"]
|
||||
Assists,
|
||||
#[iden = "deaths"]
|
||||
Deaths,
|
||||
}
|
||||
|
|
@ -4,8 +4,10 @@ use sqlx::database::HasValueRef;
|
|||
use sqlx::error::BoxDynError;
|
||||
use sqlx::{Database, Decode, Type};
|
||||
use std::borrow::Cow;
|
||||
use std::convert::Infallible;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fmt::{Display, Write};
|
||||
use std::str::FromStr;
|
||||
use steamid_ng::SteamID;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
|
|
@ -90,15 +92,7 @@ where
|
|||
{
|
||||
fn decode(value: <DB as HasValueRef<'r>>::ValueRef) -> Result<Self, BoxDynError> {
|
||||
let str = <&str as Decode<DB>>::decode(value)?;
|
||||
if let Ok(id) = str.parse() {
|
||||
Ok(Self::Id(id))
|
||||
} else if str == "serveme.tf" {
|
||||
Ok(Self::Raw("serveme.tf".into()))
|
||||
} else if str == "essentialstf" {
|
||||
Ok(Self::Raw("essentialstf".into()))
|
||||
} else {
|
||||
Ok(Self::Raw(str.to_string().into()))
|
||||
}
|
||||
Ok(str.parse().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,3 +138,19 @@ impl SteamId {
|
|||
ProfileLink(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for SteamId {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if let Ok(id) = s.parse() {
|
||||
Ok(Self::Id(id))
|
||||
} else if s == "serveme.tf" {
|
||||
Ok(Self::Raw("serveme.tf".into()))
|
||||
} else if s == "essentialstf" {
|
||||
Ok(Self::Raw("essentialstf".into()))
|
||||
} else {
|
||||
Ok(Self::Raw(s.to_string().into()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue