player filter by steamid

This commit is contained in:
Robin Appelman 2023-04-16 14:42:30 +02:00
commit 8fa199f735
5 changed files with 65 additions and 31 deletions

View file

@ -3,22 +3,29 @@ import {ready} from "./ready";
import {FilterBar} from "./filterbar" import {FilterBar} from "./filterbar"
import {Api} from "./api"; import {Api} from "./api";
let lastFilter = {
mode: "",
map: "",
players: []
};
ready(() => { ready(() => {
const filterBar = document.getElementById('filter-bar'); const filterBar = document.getElementById('filter-bar');
const maps = filterBar.dataset.maps.split(","); const maps = filterBar.dataset.maps.split(",");
const apiBase = filterBar.dataset.apiBase; const apiBase = filterBar.dataset.apiBase;
const api = new Api(apiBase); const api = new Api(apiBase);
const demoListBody = document.querySelector('.demolist tbody'); const demoListBody = document.querySelector('.demolist tbody');
const searchParams = new URLSearchParams(window.location.search);
render(() => <FilterBar maps={maps} api={api} onChange={onFilter.bind(null, api, demoListBody)}/>, filterBar); lastFilter = {
}); mode: searchParams.get("mode") || "",
map: searchParams.get("map") || "",
let lastFilter = { players: (searchParams.get("players") || "").split(",")
mode: "",
map: "",
players: []
}; };
render(() => <FilterBar maps={maps} api={api} initialFilter={lastFilter} onChange={onFilter.bind(null, api, demoListBody)}/>, filterBar);
});
const filterEq = (a, b) => { const filterEq = (a, b) => {
return (a.mode || "") === (b.mode || "") return (a.mode || "") === (b.mode || "")
&& (a.map || "") === (b.map || "") && (a.map || "") === (b.map || "")
@ -32,7 +39,7 @@ const onFilter = async (api, demoListBody, filter) => {
lastFilter = filter; lastFilter = filter;
let queryParams = new URLSearchParams({ let queryParams = new URLSearchParams({
players: filter.players.map(player => player.id).join(','), players: filter.players.map(player => player.steamid).join(','),
mode: (filter.mode || "").toLowerCase(), mode: (filter.mode || "").toLowerCase(),
map: filter.map || "", map: filter.map || "",
}); });

View file

@ -5,10 +5,11 @@ import {createEffect, createSignal} from "solid-js";
export interface FilterBarProps { export interface FilterBarProps {
maps: string[], maps: string[],
api: Api, api: Api,
initialFilter: FilterSet,
onChange: (FilterSet) => void, onChange: (FilterSet) => void,
} }
export const FilterBar = ({maps, api, onChange}: FilterBarProps) => { export const FilterBar = ({maps, api, onChange, initialFilter}: FilterBarProps) => {
const modes = createOptions(["4v4", "6v6", "Highlander"]); const modes = createOptions(["4v4", "6v6", "Highlander"]);
const mapOptions = createOptions(maps, { const mapOptions = createOptions(maps, {
createable: true createable: true
@ -16,14 +17,10 @@ export const FilterBar = ({maps, api, onChange}: FilterBarProps) => {
const playerOptions = createAsyncOptions(search => api.searchPlayer(search)); const playerOptions = createAsyncOptions(search => api.searchPlayer(search));
const playerFormat = player => player.name; const playerFormat = player => player.name;
const [initialMode, setInitialMode] = createSignal("", {equals: false}); const [initialMode, setInitialMode] = createSignal(initialFilter.mode, {equals: false});
const [initialMap, setInitialMap] = createSignal("", {equals: false}); const [initialMap, setInitialMap] = createSignal(initialFilter.map, {equals: false});
const [filterSet, setFilterSet] = createSignal({ const [filterSet, setFilterSet] = createSignal(initialFilter);
mode: "",
map: "",
players: []
});
createEffect(() => onChange(filterSet())); createEffect(() => onChange(filterSet()));
return <div class="filter-bar"> return <div class="filter-bar">

View file

@ -1,6 +1,6 @@
use crate::data::chat::Chat; use crate::data::chat::Chat;
use crate::data::player::Player; use crate::data::player::Player;
use crate::data::schema::{ArrayAgg, CleanMapName, Demos, Players}; use crate::data::schema::{ArrayAgg, CleanMapName, Demos, Players, Users};
use crate::data::steam_id::SteamId; use crate::data::steam_id::SteamId;
use crate::Result; use crate::Result;
use maud::Render; use maud::Render;
@ -180,15 +180,15 @@ impl ListDemo {
} else { } else {
let mut query = Query::select(); let mut query = Query::select();
query query
.column((Demos::Table, Demos::Id))
.column((Demos::Table, Demos::Name))
.columns([ .columns([
Demos::Map, (Demos::Table, Demos::Id),
Demos::Red, (Demos::Table, Demos::Name),
Demos::Blu, (Demos::Table, Demos::Map),
Demos::Duration, (Demos::Table, Demos::Red),
Demos::Server, (Demos::Table, Demos::Blu),
Demos::CreatedAt, (Demos::Table, Demos::Duration),
(Demos::Table, Demos::Server),
(Demos::Table, Demos::CreatedAt),
]) ])
.expr_as(Expr::col(Demos::PlayerCount), Alias::new("player_count")) .expr_as(Expr::col(Demos::PlayerCount), Alias::new("player_count"))
.from(Demos::Table) .from(Demos::Table)
@ -375,7 +375,7 @@ pub struct Filter {
map: String, map: String,
#[serde(default)] #[serde(default)]
#[serde(deserialize_with = "deserialize_array")] #[serde(deserialize_with = "deserialize_array")]
players: Vec<i32>, players: Vec<SteamId>,
#[serde(default)] #[serde(default)]
before: Option<i32>, before: Option<i32>,
#[serde(default)] #[serde(default)]
@ -388,6 +388,9 @@ where
T: Deserialize<'de> + FromStr, T: Deserialize<'de> + FromStr,
{ {
let s = <Cow<str>>::deserialize(deserializer)?; let s = <Cow<str>>::deserialize(deserializer)?;
if s.is_empty() {
return Ok(Vec::new());
}
Ok(s.split(",").map(T::from_str).flatten().collect()) Ok(s.split(",").map(T::from_str).flatten().collect())
} }
@ -423,7 +426,7 @@ impl Filter {
let mut player = self.players.iter(); let mut player = self.players.iter();
let mut players_arr = format!("array[{}", player.next().unwrap()); let mut players_arr = format!("array[{}", player.next().unwrap());
for player in player { for player in player {
write!(&mut players_arr, ",{}", player).unwrap(); write!(&mut players_arr, r#","{}""#, player).unwrap();
} }
players_arr.push_str("]"); players_arr.push_str("]");
@ -432,12 +435,16 @@ impl Filter {
Players::Table, Players::Table,
Expr::col((Demos::Table, Demos::Id)).equals((Players::Table, Players::DemoId)), Expr::col((Demos::Table, Demos::Id)).equals((Players::Table, Players::DemoId)),
) )
.and_where(Expr::col(Players::UserId).is_in(self.players.clone())); .inner_join(
Users::Table,
Expr::col((Users::Table, Users::Id)).equals((Players::Table, Players::UserId)),
)
.and_where(Expr::col(Users::SteamId).is_in(self.players.clone()));
query.group_by_col((Demos::Table, Players::Id)); query.group_by_col((Demos::Table, Players::Id));
query.and_having( query.and_having(
Expr::cust(&players_arr).contained( Expr::cust(&players_arr)
Func::cust(ArrayAgg).arg(Expr::col((Players::Table, Players::UserId))), .cast_as(Alias::new("varchar[]"))
), .contained(Func::cust(ArrayAgg).arg(Expr::col((Users::Table, Users::SteamId)))),
); );
} }
} }

View file

@ -92,3 +92,19 @@ pub enum Players {
#[iden = "deaths"] #[iden = "deaths"]
Deaths, Deaths,
} }
#[allow(dead_code)]
#[derive(Iden)]
pub enum Users {
Table,
#[iden = "id"]
Id,
#[iden = "steamid"]
SteamId,
#[iden = "name"]
Name,
#[iden = "avatar"]
Avatar,
#[iden = "token"]
Token,
}

View file

@ -1,4 +1,5 @@
use maud::Render; use maud::Render;
use sea_query::Value;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::database::HasValueRef; use sqlx::database::HasValueRef;
use sqlx::error::BoxDynError; use sqlx::error::BoxDynError;
@ -154,3 +155,9 @@ impl FromStr for SteamId {
} }
} }
} }
impl From<SteamId> for Value {
fn from(value: SteamId) -> Self {
Value::String(Some(Box::new(value.steamid64())))
}
}