ai generated solutions to our ai generated problems
This commit is contained in:
@@ -201,6 +201,12 @@ struct GamesResponse {
|
||||
games: Vec<GameRow>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GameResponse {
|
||||
game: GameRow,
|
||||
participants: Vec<GameParticipant>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GameRow {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -230,6 +236,14 @@ struct GameStats {
|
||||
team_kills_stat: i64,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct GameParticipant {
|
||||
team_name: String,
|
||||
result: String,
|
||||
player_count: i64,
|
||||
stats: GameStats,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct PlayerSearchResponse {
|
||||
players: Vec<PlayerRef>,
|
||||
@@ -302,6 +316,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.route("/health", get(health))
|
||||
.route("/api/tss/leaderboard/teams", get(leaderboard))
|
||||
.route("/api/tss/games/recent", get(recent_games))
|
||||
.route("/api/tss/games/{session_id}", get(game_detail))
|
||||
.route("/api/tss/teams/resolve", get(resolve_team))
|
||||
.route("/api/tss/teams/search", get(search_teams))
|
||||
.route("/api/tss/teams/{team}", get(team_detail))
|
||||
@@ -494,6 +509,18 @@ async fn recent_games(
|
||||
Ok(Json(RecentGamesResponse { matches }))
|
||||
}
|
||||
|
||||
async fn game_detail(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(session_id): Path<String>,
|
||||
) -> ApiResult<GameResponse> {
|
||||
let session_id = validate_session_id(&session_id)?;
|
||||
let battles_conn = open_db(&state.battles_db)?;
|
||||
let game = game_for(&battles_conn, session_id)?
|
||||
.ok_or_else(|| ApiError::not_found("Game not found"))?;
|
||||
let participants = game_participants_for(&battles_conn, session_id)?;
|
||||
Ok(Json(GameResponse { game, participants }))
|
||||
}
|
||||
|
||||
async fn resolve_team(
|
||||
State(state): State<Arc<AppState>>,
|
||||
Query(query): Query<ResolveQuery>,
|
||||
@@ -1259,6 +1286,121 @@ fn recent_games_for(conn: &Connection, limit: i64) -> Result<Vec<GameRow>, ApiEr
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
fn game_for(conn: &Connection, session_id: &str) -> Result<Option<GameRow>, ApiError> {
|
||||
conn.query_row(
|
||||
"SELECT
|
||||
p.session_id,
|
||||
COALESCE(m.endtime_unix, MAX(p.endtime_unix), 0) AS timestamp,
|
||||
m.mission_name,
|
||||
m.mission_mode,
|
||||
COUNT(DISTINCT p.UID),
|
||||
COALESCE(SUM(p.ground_kills), 0),
|
||||
COALESCE(SUM(p.air_kills), 0),
|
||||
COALESCE(SUM(p.assists), 0),
|
||||
COALESCE(SUM(p.captures), 0),
|
||||
COALESCE(SUM(p.deaths), 0),
|
||||
COALESCE(SUM(p.score), 0),
|
||||
COALESCE(SUM(p.missile_evades), 0),
|
||||
COALESCE(SUM(p.shell_interceptions), 0),
|
||||
COALESCE(SUM(p.team_kills_stat), 0),
|
||||
m.winning_slot,
|
||||
m.losing_slot
|
||||
FROM player_games_hist p
|
||||
LEFT JOIN match_summary m ON m.session_id = p.session_id
|
||||
WHERE p.session_id = ?1
|
||||
GROUP BY p.session_id",
|
||||
params![session_id],
|
||||
|row| {
|
||||
let timestamp: i64 = row.get(1)?;
|
||||
Ok(GameRow {
|
||||
team_name: None,
|
||||
session_id: row.get(0)?,
|
||||
timestamp,
|
||||
endtime_unix: timestamp,
|
||||
map_name: row.get(2)?,
|
||||
mission_mode: row.get(3)?,
|
||||
result: String::new(),
|
||||
player_count: row.get(4)?,
|
||||
winning_team: row.get(14)?,
|
||||
losing_team: row.get(15)?,
|
||||
stats: GameStats {
|
||||
ground_kills: row.get(5)?,
|
||||
air_kills: row.get(6)?,
|
||||
assists: row.get(7)?,
|
||||
captures: row.get(8)?,
|
||||
deaths: row.get(9)?,
|
||||
score: row.get(10)?,
|
||||
missile_evades: row.get(11)?,
|
||||
shell_interceptions: row.get(12)?,
|
||||
team_kills_stat: row.get(13)?,
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
.optional()
|
||||
.map_err(db_error)
|
||||
}
|
||||
|
||||
fn game_participants_for(
|
||||
conn: &Connection,
|
||||
session_id: &str,
|
||||
) -> Result<Vec<GameParticipant>, ApiError> {
|
||||
let mut stmt = conn
|
||||
.prepare(
|
||||
"SELECT
|
||||
p.team_name,
|
||||
CASE
|
||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 'Win'
|
||||
ELSE 'Loss'
|
||||
END AS result,
|
||||
COUNT(DISTINCT p.UID),
|
||||
COALESCE(SUM(p.ground_kills), 0),
|
||||
COALESCE(SUM(p.air_kills), 0),
|
||||
COALESCE(SUM(p.assists), 0),
|
||||
COALESCE(SUM(p.captures), 0),
|
||||
COALESCE(SUM(p.deaths), 0),
|
||||
COALESCE(SUM(p.score), 0),
|
||||
COALESCE(SUM(p.missile_evades), 0),
|
||||
COALESCE(SUM(p.shell_interceptions), 0),
|
||||
COALESCE(SUM(p.team_kills_stat), 0)
|
||||
FROM player_games_hist p
|
||||
WHERE p.session_id = ?1 AND p.team_name IS NOT NULL AND p.team_name != ''
|
||||
GROUP BY p.team_name COLLATE NOCASE
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN MAX(CASE WHEN p.victor_bool = 'Win' THEN 1 ELSE 0 END) = 1 THEN 0
|
||||
ELSE 1
|
||||
END,
|
||||
p.team_name COLLATE NOCASE",
|
||||
)
|
||||
.map_err(db_error)?;
|
||||
|
||||
let rows = stmt
|
||||
.query_map(params![session_id], |row| {
|
||||
Ok(GameParticipant {
|
||||
team_name: row.get(0)?,
|
||||
result: row.get(1)?,
|
||||
player_count: row.get(2)?,
|
||||
stats: GameStats {
|
||||
ground_kills: row.get(3)?,
|
||||
air_kills: row.get(4)?,
|
||||
assists: row.get(5)?,
|
||||
captures: row.get(6)?,
|
||||
deaths: row.get(7)?,
|
||||
score: row.get(8)?,
|
||||
missile_evades: row.get(9)?,
|
||||
shell_interceptions: row.get(10)?,
|
||||
team_kills_stat: row.get(11)?,
|
||||
},
|
||||
})
|
||||
})
|
||||
.map_err(db_error)?
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(db_error)?;
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
fn validate_team_name(name: &str) -> Result<&str, ApiError> {
|
||||
let trimmed = name.trim();
|
||||
if trimmed.len() < 2 || trimmed.len() > MAX_TEAM_NAME_LENGTH {
|
||||
@@ -1287,6 +1429,19 @@ fn validate_uid(value: &str) -> Result<String, ApiError> {
|
||||
Ok(trimmed.to_string())
|
||||
}
|
||||
|
||||
fn validate_session_id(value: &str) -> Result<&str, ApiError> {
|
||||
if value.is_empty()
|
||||
|| value.len() > 96
|
||||
|| !value
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
|
||||
{
|
||||
return Err(ApiError::bad_request("Invalid game ID"));
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
fn decode_path_team(value: &str) -> Result<String, ApiError> {
|
||||
let decoded = urlencoding::decode(value)
|
||||
.map_err(|_| ApiError::bad_request("Invalid team name"))?
|
||||
|
||||
Reference in New Issue
Block a user