align backend with TSS schema: drop SRE-specific fields, fix cross-tournament queries

- Remove teams_points, clanrating, tag/short/long name, description, region,
  guild_id, clan_id — none of these exist in the TSS DB schema
- Rename long_name → name throughout (TSS teams have one name, not long+short)
- Cross-tournament stat queries now use team_name (string) from player_games_hist
  instead of team_id, since team_id is assigned per-tournament by Spectra
- Leaderboard deduplicates teams_data by name with GROUP BY, MAX(team_id) for roster ref
- team_members roster still uses team_id (correct within a single tournament)
- Fix player_teams_for: was grouping by non-existent team_tag column, now team_id
- Fix games_for: winning_team/losing_team → winning_slot/losing_slot; add mission_name
- Remove joined_unix, points, sqb_points from PlayerSummary; nick resolved from battles_db
- Remove rating_hourly from HistoryResponse (teams_points never existed)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
deploy
2026-06-08 01:07:57 +00:00
parent 3436c91fdc
commit 65ad10ad11
+85 -195
View File
@@ -105,51 +105,34 @@ struct SearchResponse {
#[derive(Serialize)] #[derive(Serialize)]
struct ResolveResponse { struct ResolveResponse {
team_id: i64, team_id: i64,
long_name: String,
tag_name: Option<String>,
name: String, name: String,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct TeamSearchRow { struct TeamSearchRow {
team_id: i64, team_id: i64,
long_name: String, name: String,
tag_name: Option<String>,
members: i64, members: i64,
clanrating: Option<i64>,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct TeamLeaderboardRow { struct TeamLeaderboardRow {
team_id: i64, team_id: i64,
clan_id: i64, name: String,
long_name: String,
tag_name: Option<String>,
short_name: Option<String>,
player_count: i64, player_count: i64,
total_battles: i64, total_battles: i64,
wins: i64, wins: i64,
losses: i64, losses: i64,
win_rate: f64, win_rate: f64,
total_kills: i64, total_kills: i64,
points: Points,
} }
#[derive(Serialize)] #[derive(Serialize)]
struct TeamDetail { struct TeamDetail {
team_id: i64, team_id: i64,
clan_id: i64, name: String,
long_name: String,
tag_name: Option<String>,
short_name: Option<String>,
description: Option<String>,
region: Option<String>,
members: i64, members: i64,
captain_uid: Option<String>, captain_uid: Option<String>,
guild_id: Option<String>,
created_unix: Option<i64>,
updated_unix: Option<i64>,
clanrating: Option<i64>,
data_set: &'static str, data_set: &'static str,
team_summary: TeamSummary, team_summary: TeamSummary,
players: Vec<PlayerSummary>, players: Vec<PlayerSummary>,
@@ -164,12 +147,6 @@ struct TeamSummary {
win_rate: f64, win_rate: f64,
kdr: f64, kdr: f64,
total_kills: i64, total_kills: i64,
points: Points,
total_points: i64,
}
#[derive(Serialize)]
struct Points {
total_points: i64, total_points: i64,
} }
@@ -178,9 +155,6 @@ struct PlayerSummary {
uid: String, uid: String,
nick: Option<String>, nick: Option<String>,
role: String, role: String,
joined_unix: Option<i64>,
points: i64,
sqb_points: i64,
total_battles: i64, total_battles: i64,
wins: i64, wins: i64,
losses: i64, losses: i64,
@@ -196,10 +170,8 @@ struct PlayerSummary {
#[derive(Serialize)] #[derive(Serialize)]
struct HistoryResponse { struct HistoryResponse {
team_id: i64, team_id: i64,
long_name: String, name: String,
tag_name: Option<String>,
history: Vec<PeriodHistory>, history: Vec<PeriodHistory>,
rating_hourly: Vec<RatingPoint>,
} }
#[derive(Serialize)] #[derive(Serialize)]
@@ -211,17 +183,10 @@ struct PeriodHistory {
win_rate: f64, win_rate: f64,
} }
#[derive(Serialize)]
struct RatingPoint {
timestamp: i64,
rating: i64,
}
#[derive(Serialize)] #[derive(Serialize)]
struct GamesResponse { struct GamesResponse {
team_id: i64, team_id: i64,
long_name: String, name: String,
tag_name: Option<String>,
games: Vec<GameRow>, games: Vec<GameRow>,
} }
@@ -290,7 +255,6 @@ struct PlayerCareer {
#[derive(Serialize)] #[derive(Serialize)]
struct PlayerTeamRef { struct PlayerTeamRef {
team_id: Option<i64>, team_id: Option<i64>,
team_tag: Option<String>,
team_name: Option<String>, team_name: Option<String>,
games: i64, games: i64,
last_seen: i64, last_seen: i64,
@@ -298,16 +262,9 @@ struct PlayerTeamRef {
struct TeamRecord { struct TeamRecord {
team_id: i64, team_id: i64,
long_name: String, name: String,
tag_name: Option<String>,
description: Option<String>,
region: Option<String>,
members: i64, members: i64,
captain_uid: Option<String>, captain_uid: Option<String>,
guild_id: Option<String>,
created_unix: Option<i64>,
updated_unix: Option<i64>,
clanrating: Option<i64>,
} }
#[tokio::main] #[tokio::main]
@@ -411,12 +368,14 @@ async fn leaderboard(
let limit = i64::from(query.limit.unwrap_or(100).clamp(1, 100)); let limit = i64::from(query.limit.unwrap_or(100).clamp(1, 100));
let teams_conn = open_db(&state.teams_db)?; let teams_conn = open_db(&state.teams_db)?;
let battles_conn = open_db(&state.battles_db)?; let battles_conn = open_db(&state.battles_db)?;
// Deduplicate teams by name across tournaments — pick the highest team_id
// (most recent) per name for the roster count, but stats come from team_name.
let mut stmt = teams_conn let mut stmt = teams_conn
.prepare( .prepare(
"SELECT team_id, long_name, tag_name, description, region, members, captain_uid, guild_id, "SELECT MAX(team_id), name, MAX(members), MAX(captain_uid)
created_unix, updated_unix, clanrating
FROM teams_data FROM teams_data
ORDER BY COALESCE(clanrating, 0) DESC, members DESC, long_name COLLATE NOCASE ASC GROUP BY name COLLATE NOCASE
ORDER BY MAX(members) DESC, name COLLATE NOCASE ASC
LIMIT ?1", LIMIT ?1",
) )
.map_err(db_error)?; .map_err(db_error)?;
@@ -429,22 +388,16 @@ async fn leaderboard(
let mut rows = Vec::with_capacity(teams.len()); let mut rows = Vec::with_capacity(teams.len());
for team in teams { for team in teams {
let summary = team_summary_for(&battles_conn, team.team_id)?; let summary = team_summary_for(&battles_conn, &team.name)?;
rows.push(TeamLeaderboardRow { rows.push(TeamLeaderboardRow {
team_id: team.team_id, team_id: team.team_id,
clan_id: team.team_id, name: team.name,
long_name: team.long_name.clone(),
tag_name: team.tag_name.clone(),
short_name: team.tag_name.clone(),
player_count: team.members, player_count: team.members,
total_battles: summary.total_battles, total_battles: summary.total_battles,
wins: summary.wins, wins: summary.wins,
losses: summary.losses, losses: summary.losses,
win_rate: summary.win_rate, win_rate: summary.win_rate,
total_kills: summary.total_kills, total_kills: summary.total_kills,
points: Points {
total_points: team.clanrating.unwrap_or(summary.total_points),
},
}); });
} }
@@ -460,9 +413,7 @@ async fn resolve_team(
let team = find_team(&conn, name)?.ok_or_else(|| ApiError::not_found("Team not found"))?; let team = find_team(&conn, name)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
Ok(Json(ResolveResponse { Ok(Json(ResolveResponse {
team_id: team.team_id, team_id: team.team_id,
long_name: team.long_name.clone(), name: team.name,
tag_name: team.tag_name.clone(),
name: team.tag_name.clone().unwrap_or(team.long_name),
})) }))
} }
@@ -477,17 +428,13 @@ async fn search_teams(
let conn = open_db(&state.teams_db)?; let conn = open_db(&state.teams_db)?;
let mut stmt = conn let mut stmt = conn
.prepare( .prepare(
"SELECT team_id, long_name, tag_name, members, clanrating "SELECT team_id, name, members
FROM teams_data FROM teams_data
WHERE long_name LIKE ?1 ESCAPE '\\' OR tag_name LIKE ?1 ESCAPE '\\' WHERE name LIKE ?1 ESCAPE '\\'
ORDER BY ORDER BY
CASE CASE WHEN name = ?2 COLLATE NOCASE THEN 0 ELSE 1 END,
WHEN tag_name = ?2 COLLATE NOCASE THEN 0 members DESC,
WHEN long_name = ?2 COLLATE NOCASE THEN 1 name COLLATE NOCASE ASC
ELSE 2
END,
COALESCE(clanrating, 0) DESC,
long_name COLLATE NOCASE ASC
LIMIT ?3", LIMIT ?3",
) )
.map_err(db_error)?; .map_err(db_error)?;
@@ -496,10 +443,8 @@ async fn search_teams(
.query_map(params![like, name, limit], |row| { .query_map(params![like, name, limit], |row| {
Ok(TeamSearchRow { Ok(TeamSearchRow {
team_id: row.get(0)?, team_id: row.get(0)?,
long_name: row.get(1)?, name: row.get(1)?,
tag_name: row.get(2)?, members: row.get(2)?,
members: row.get(3)?,
clanrating: row.get(4)?,
}) })
}) })
.map_err(db_error)? .map_err(db_error)?
@@ -518,23 +463,15 @@ async fn team_detail(
let battles_conn = open_db(&state.battles_db)?; let battles_conn = open_db(&state.battles_db)?;
let team = let team =
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?; find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
let summary = team_summary_for(&battles_conn, team.team_id)?; let summary = team_summary_for(&battles_conn, &team.name)?;
let players = player_summaries_for(&teams_conn, &battles_conn, team.team_id)?; // team_id is the most recent tournament entry — used only for the roster lookup.
let players = player_summaries_for(&teams_conn, &battles_conn, team.team_id, &team.name)?;
Ok(Json(TeamDetail { Ok(Json(TeamDetail {
team_id: team.team_id, team_id: team.team_id,
clan_id: team.team_id, name: team.name,
long_name: team.long_name,
tag_name: team.tag_name.clone(),
short_name: team.tag_name,
description: team.description,
region: team.region,
members: team.members, members: team.members,
captain_uid: team.captain_uid, captain_uid: team.captain_uid,
guild_id: team.guild_id,
created_unix: team.created_unix,
updated_unix: team.updated_unix,
clanrating: team.clanrating,
data_set: "tss", data_set: "tss",
team_summary: summary, team_summary: summary,
players, players,
@@ -551,15 +488,12 @@ async fn team_history(
let team = let team =
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?; find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
let history = period_history_for(&battles_conn, team.team_id)?; let history = period_history_for(&battles_conn, &team.name)?;
let rating_hourly = rating_history_for(&teams_conn, team.team_id)?;
Ok(Json(HistoryResponse { Ok(Json(HistoryResponse {
team_id: team.team_id, team_id: team.team_id,
long_name: team.long_name, name: team.name,
tag_name: team.tag_name,
history, history,
rating_hourly,
})) }))
} }
@@ -572,12 +506,11 @@ async fn team_games(
let battles_conn = open_db(&state.battles_db)?; let battles_conn = open_db(&state.battles_db)?;
let team = let team =
find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?; find_team(&teams_conn, &decoded)?.ok_or_else(|| ApiError::not_found("Team not found"))?;
let games = games_for(&battles_conn, team.team_id)?; let games = games_for(&battles_conn, &team.name)?;
Ok(Json(GamesResponse { Ok(Json(GamesResponse {
team_id: team.team_id, team_id: team.team_id,
long_name: team.long_name, name: team.name,
tag_name: team.tag_name,
games, games,
})) }))
} }
@@ -639,33 +572,19 @@ fn db_error(error: rusqlite::Error) -> ApiError {
fn read_team_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<TeamRecord> { fn read_team_record(row: &rusqlite::Row<'_>) -> rusqlite::Result<TeamRecord> {
Ok(TeamRecord { Ok(TeamRecord {
team_id: row.get(0)?, team_id: row.get(0)?,
long_name: row.get(1)?, name: row.get(1)?,
tag_name: row.get(2)?, members: row.get(2)?,
description: row.get(3)?, captain_uid: row.get(3)?,
region: row.get(4)?,
members: row.get(5)?,
captain_uid: row.get(6)?,
guild_id: row.get(7)?,
created_unix: row.get(8)?,
updated_unix: row.get(9)?,
clanrating: row.get(10)?,
}) })
} }
fn find_team(conn: &Connection, name: &str) -> Result<Option<TeamRecord>, ApiError> { fn find_team(conn: &Connection, name: &str) -> Result<Option<TeamRecord>, ApiError> {
// Return the most recent tournament entry for this team (highest team_id).
conn.query_row( conn.query_row(
"SELECT team_id, long_name, tag_name, description, region, members, captain_uid, guild_id, "SELECT team_id, name, members, captain_uid
created_unix, updated_unix, clanrating
FROM teams_data FROM teams_data
WHERE team_id = ?1 WHERE name = ?2 COLLATE NOCASE OR team_id = ?1
OR long_name = ?2 COLLATE NOCASE ORDER BY team_id DESC
OR tag_name = ?2 COLLATE NOCASE
ORDER BY
CASE
WHEN tag_name = ?2 COLLATE NOCASE THEN 0
WHEN long_name = ?2 COLLATE NOCASE THEN 1
ELSE 2
END
LIMIT 1", LIMIT 1",
params![name.parse::<i64>().ok(), name], params![name.parse::<i64>().ok(), name],
read_team_record, read_team_record,
@@ -674,7 +593,7 @@ fn find_team(conn: &Connection, name: &str) -> Result<Option<TeamRecord>, ApiErr
.map_err(db_error) .map_err(db_error)
} }
fn team_summary_for(conn: &Connection, team_id: i64) -> Result<TeamSummary, ApiError> { fn team_summary_for(conn: &Connection, team_name: &str) -> Result<TeamSummary, ApiError> {
conn.query_row( conn.query_row(
"SELECT "SELECT
COUNT(DISTINCT session_id), COUNT(DISTINCT session_id),
@@ -687,8 +606,8 @@ fn team_summary_for(conn: &Connection, team_id: i64) -> Result<TeamSummary, ApiE
COALESCE(SUM(score), 0), COALESCE(SUM(score), 0),
COUNT(DISTINCT UID) COUNT(DISTINCT UID)
FROM player_games_hist FROM player_games_hist
WHERE team_id = ?1", WHERE team_name = ?1 COLLATE NOCASE",
params![team_id], params![team_name],
|row| { |row| {
let battles: i64 = row.get(0)?; let battles: i64 = row.get(0)?;
let wins: i64 = row.get(1)?; let wins: i64 = row.get(1)?;
@@ -708,9 +627,6 @@ fn team_summary_for(conn: &Connection, team_id: i64) -> Result<TeamSummary, ApiE
win_rate: percent(wins, battles), win_rate: percent(wins, battles),
kdr: ratio(total_kills, deaths), kdr: ratio(total_kills, deaths),
total_kills, total_kills,
points: Points {
total_points: score + assists + total_kills,
},
total_points: score + assists + total_kills, total_points: score + assists + total_kills,
}) })
}, },
@@ -721,24 +637,24 @@ fn team_summary_for(conn: &Connection, team_id: i64) -> Result<TeamSummary, ApiE
fn player_summaries_for( fn player_summaries_for(
teams_conn: &Connection, teams_conn: &Connection,
battles_conn: &Connection, battles_conn: &Connection,
// team_id: roster for this specific tournament entry only
team_id: i64, team_id: i64,
// team_name: cross-tournament stats key
team_name: &str,
) -> Result<Vec<PlayerSummary>, ApiError> { ) -> Result<Vec<PlayerSummary>, ApiError> {
let mut stmt = teams_conn let mut stmt = teams_conn
.prepare( .prepare(
"SELECT uid, nick, role, joined_unix, points "SELECT uid, role
FROM team_members FROM team_members
WHERE team_id = ?1 WHERE team_id = ?1
ORDER BY points DESC, nick COLLATE NOCASE ASC", ORDER BY uid",
) )
.map_err(db_error)?; .map_err(db_error)?;
let members = stmt let members = stmt
.query_map(params![team_id], |row| { .query_map(params![team_id], |row| {
Ok(( Ok((
row.get::<_, String>(0)?, row.get::<_, String>(0)?,
row.get::<_, Option<String>>(1)?, row.get::<_, String>(1)?,
row.get::<_, String>(2)?,
row.get::<_, Option<i64>>(3)?,
row.get::<_, i64>(4)?,
)) ))
}) })
.map_err(db_error)? .map_err(db_error)?
@@ -756,15 +672,17 @@ fn player_summaries_for(
COALESCE(SUM(air_kills), 0), COALESCE(SUM(air_kills), 0),
COALESCE(SUM(assists), 0), COALESCE(SUM(assists), 0),
COALESCE(SUM(deaths), 0), COALESCE(SUM(deaths), 0),
COALESCE(SUM(score), 0) (SELECT nick FROM player_games_hist
WHERE UID = ?2 AND nick IS NOT NULL
ORDER BY endtime_unix DESC LIMIT 1)
FROM player_games_hist FROM player_games_hist
WHERE team_id = ?1 AND UID = ?2", WHERE team_name = ?1 COLLATE NOCASE AND UID = ?2",
) )
.map_err(db_error)?; .map_err(db_error)?;
for (uid, nick, role, joined_unix, points) in members { for (uid, role) in members {
let summary = stats_stmt let summary = stats_stmt
.query_row(params![team_id, uid], |row| { .query_row(params![team_name, uid], |row| {
let battles: i64 = row.get(0)?; let battles: i64 = row.get(0)?;
let wins: i64 = row.get(1)?; let wins: i64 = row.get(1)?;
let losses: i64 = row.get(2)?; let losses: i64 = row.get(2)?;
@@ -772,19 +690,12 @@ fn player_summaries_for(
let air: i64 = row.get(4)?; let air: i64 = row.get(4)?;
let assists: i64 = row.get(5)?; let assists: i64 = row.get(5)?;
let deaths: i64 = row.get(6)?; let deaths: i64 = row.get(6)?;
let score: i64 = row.get(7)?; let nick: Option<String> = row.get(7)?;
let total_kills = ground + air; let total_kills = ground + air;
Ok(PlayerSummary { Ok(PlayerSummary {
uid: uid.clone(), uid: uid.clone(),
nick: nick.clone(), nick,
role: role.clone(), role: role.clone(),
joined_unix,
points,
sqb_points: if points == 0 {
score + assists + total_kills
} else {
points
},
total_battles: battles, total_battles: battles,
wins, wins,
losses, losses,
@@ -801,6 +712,7 @@ fn player_summaries_for(
out.push(summary); out.push(summary);
} }
out.sort_by(|a, b| b.total_battles.cmp(&a.total_battles));
Ok(out) Ok(out)
} }
@@ -931,22 +843,21 @@ fn player_career_for(conn: &Connection, uid: &str) -> Result<Option<PlayerCareer
fn player_teams_for(conn: &Connection, uid: &str) -> Result<Vec<PlayerTeamRef>, ApiError> { fn player_teams_for(conn: &Connection, uid: &str) -> Result<Vec<PlayerTeamRef>, ApiError> {
let mut stmt = conn let mut stmt = conn
.prepare( .prepare(
"SELECT team_tag, MAX(team_name) AS team_name, team_id, "SELECT team_id, MAX(team_name) AS team_name,
COUNT(DISTINCT session_id) AS games, MAX(endtime_unix) AS last_seen COUNT(DISTINCT session_id) AS games, MAX(endtime_unix) AS last_seen
FROM player_games_hist FROM player_games_hist
WHERE UID = ?1 WHERE UID = ?1 AND team_id IS NOT NULL
GROUP BY team_tag GROUP BY team_id
ORDER BY last_seen DESC", ORDER BY last_seen DESC",
) )
.map_err(db_error)?; .map_err(db_error)?;
let teams = stmt let teams = stmt
.query_map(params![uid], |row| { .query_map(params![uid], |row| {
Ok(PlayerTeamRef { Ok(PlayerTeamRef {
team_tag: row.get(0)?, team_id: row.get(0)?,
team_name: row.get(1)?, team_name: row.get(1)?,
team_id: row.get(2)?, games: row.get(2)?,
games: row.get(3)?, last_seen: row.get(3)?,
last_seen: row.get(4)?,
}) })
}) })
.map_err(db_error)? .map_err(db_error)?
@@ -955,7 +866,7 @@ fn player_teams_for(conn: &Connection, uid: &str) -> Result<Vec<PlayerTeamRef>,
Ok(teams) Ok(teams)
} }
fn period_history_for(conn: &Connection, team_id: i64) -> Result<Vec<PeriodHistory>, ApiError> { fn period_history_for(conn: &Connection, team_name: &str) -> Result<Vec<PeriodHistory>, ApiError> {
let mut stmt = conn let mut stmt = conn
.prepare( .prepare(
"SELECT "SELECT
@@ -964,14 +875,14 @@ fn period_history_for(conn: &Connection, team_id: i64) -> Result<Vec<PeriodHisto
SUM(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END), SUM(CASE WHEN victor_bool = 'Win' THEN 1 ELSE 0 END),
SUM(CASE WHEN victor_bool != 'Win' THEN 1 ELSE 0 END) SUM(CASE WHEN victor_bool != 'Win' THEN 1 ELSE 0 END)
FROM player_games_hist FROM player_games_hist
WHERE team_id = ?1 AND endtime_unix > 0 WHERE team_name = ?1 COLLATE NOCASE AND endtime_unix > 0
GROUP BY period GROUP BY period
ORDER BY period ASC", ORDER BY period ASC",
) )
.map_err(db_error)?; .map_err(db_error)?;
let rows = stmt let rows = stmt
.query_map(params![team_id], |row| { .query_map(params![team_name], |row| {
let period: String = row.get(0)?; let period: String = row.get(0)?;
let battles: i64 = row.get(1)?; let battles: i64 = row.get(1)?;
let wins: i64 = row.get(2)?; let wins: i64 = row.get(2)?;
@@ -990,35 +901,13 @@ fn period_history_for(conn: &Connection, team_id: i64) -> Result<Vec<PeriodHisto
Ok(rows) Ok(rows)
} }
fn rating_history_for(conn: &Connection, team_id: i64) -> Result<Vec<RatingPoint>, ApiError> { fn games_for(conn: &Connection, team_name: &str) -> Result<Vec<GameRow>, ApiError> {
let mut stmt = conn
.prepare(
"SELECT unix_time, COALESCE(total_score, 0)
FROM teams_points
WHERE team_id = ?1
ORDER BY unix_time ASC",
)
.map_err(db_error)?;
let rows = stmt
.query_map(params![team_id], |row| {
Ok(RatingPoint {
timestamp: row.get(0)?,
rating: row.get(1)?,
})
})
.map_err(db_error)?
.collect::<Result<Vec<_>, _>>()
.map_err(db_error)?;
Ok(rows)
}
fn games_for(conn: &Connection, team_id: i64) -> Result<Vec<GameRow>, ApiError> {
let mut stmt = conn let mut stmt = conn
.prepare( .prepare(
"SELECT "SELECT
p.session_id, p.session_id,
COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp, COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp,
m.mission_name,
m.mission_mode, m.mission_mode,
CASE CASE
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win' WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
@@ -1034,11 +923,11 @@ fn games_for(conn: &Connection, team_id: i64) -> Result<Vec<GameRow>, ApiError>
COALESCE(SUM(p.missile_evades), 0), COALESCE(SUM(p.missile_evades), 0),
COALESCE(SUM(p.shell_interceptions), 0), COALESCE(SUM(p.shell_interceptions), 0),
COALESCE(SUM(p.team_kills_stat), 0), COALESCE(SUM(p.team_kills_stat), 0),
m.winning_team, m.winning_slot,
m.losing_team m.losing_slot
FROM player_games_hist p FROM player_games_hist p
LEFT JOIN match_summary m ON m.session_id = p.session_id LEFT JOIN match_summary m ON m.session_id = p.session_id
WHERE p.team_id = ?1 WHERE p.team_name = ?1 COLLATE NOCASE
GROUP BY p.session_id GROUP BY p.session_id
ORDER BY timestamp DESC ORDER BY timestamp DESC
LIMIT 100", LIMIT 100",
@@ -1046,30 +935,31 @@ fn games_for(conn: &Connection, team_id: i64) -> Result<Vec<GameRow>, ApiError>
.map_err(db_error)?; .map_err(db_error)?;
let rows = stmt let rows = stmt
.query_map(params![team_id], |row| { .query_map(params![team_name], |row| {
let session_id: String = row.get(0)?; let session_id: String = row.get(0)?;
let timestamp: i64 = row.get(1)?; let timestamp: i64 = row.get(1)?;
let mission_mode: Option<String> = row.get(2)?; let map_name: Option<String> = row.get(2)?;
let mission_mode: Option<String> = row.get(3)?;
Ok(GameRow { Ok(GameRow {
session_id, session_id,
timestamp, timestamp,
endtime_unix: timestamp, endtime_unix: timestamp,
map_name: mission_mode.clone(), map_name,
mission_mode, mission_mode,
result: row.get(3)?, result: row.get(4)?,
player_count: row.get(4)?, player_count: row.get(5)?,
winning_team: row.get(14)?, winning_team: row.get(15)?,
losing_team: row.get(15)?, losing_team: row.get(16)?,
stats: GameStats { stats: GameStats {
ground_kills: row.get(5)?, ground_kills: row.get(6)?,
air_kills: row.get(6)?, air_kills: row.get(7)?,
assists: row.get(7)?, assists: row.get(8)?,
captures: row.get(8)?, captures: row.get(9)?,
deaths: row.get(9)?, deaths: row.get(10)?,
score: row.get(10)?, score: row.get(11)?,
missile_evades: row.get(11)?, missile_evades: row.get(12)?,
shell_interceptions: row.get(12)?, shell_interceptions: row.get(13)?,
team_kills_stat: row.get(13)?, team_kills_stat: row.get(14)?,
}, },
}) })
}) })