Add IIHF stats/points functions, integrate into main, add tests

master
Tait Hoyem 1 year ago
parent 4019823dd7
commit 4b1e7d71ae

@ -1,2 +0,0 @@
-- Add down migration script here
--DROP FUNCTION game_results(INTEGER, INTEGER);

@ -1,99 +0,0 @@
-- Add up migration script here
--CREATE FUNCTION periods(game_id INTEGER)
--RETURNS INTEGER AS $$
--BEGIN
-- RETURN (SELECT COUNT(id) FROM periods WHERE periods.game=game_id);
--END;
--$$ LANGUAGE plpgsql;
--
--CREATE FUNCTION goals(game_id INTEGER, team_id INTEGER)
--RETURNS INTEGER AS $$
--DECLARE
-- goals INTEGER;
--BEGIN
-- IF NOT EXISTS (SELECT * FROM games WHERE games.id=game_id) THEN
-- RAISE EXCEPTION 'The game does not exist.';
-- END IF;
-- IF NOT EXISTS (SELECT * FROM teams WHERE teams.id=team_id) THEN
-- RAISE EXCEPTION 'The team does not exist.';
-- END IF;
-- IF NOT EXISTS (SELECT * FROM games JOIN teams ON teams.id=games.team_home OR teams.id=team_away WHERE games.id=game_id) THEN
-- RAISE EXCEPTION 'The team specified did not play this game.';
-- END IF;
--
-- SELECT
-- COUNT(shots.id)
-- INTO
-- goals
-- FROM shots
-- JOIN game_players
-- ON game_players.id=shots.shooter
-- JOIN periods
-- ON periods.id=shots.period
-- WHERE shots.goal=true
-- AND game_players.team=team_id
-- AND periods.game=game_id;
-- -- return 0 if not goals are found given the team and the game
-- RETURN COALESCE(goals, 0);
--END;
--$$ LANGUAGE plpgsql;
--
--CREATE OR REPLACE FUNCTION calculate_iihf_stats(game_id INT, team_id INT)
--RETURNS TABLE (
-- reg_win INT,
-- reg_loss INT,
-- ot_win INT,
-- ot_loss INT,
-- tie INT,
-- game INT,
-- team INT
--) AS $$
--DECLARE
-- opponent_team_id INTEGER;
--BEGIN
-- IF NOT EXISTS (SELECT * FROM games WHERE games.id=game_id) THEN
-- RAISE EXCEPTION 'The game does not exist.';
-- END IF;
-- IF NOT EXISTS (SELECT * FROM teams WHERE teams.id=team_id) THEN
-- RAISE EXCEPTION 'The team does not exist.';
-- END IF;
-- IF NOT EXISTS (SELECT * FROM games JOIN teams ON teams.id=games.team_home OR teams.id=team_away WHERE games.id=game_id) THEN
-- RAISE EXCEPTION 'The team specified did not play this game.';
-- END IF;
--
-- SELECT
-- teams.id
-- INTO
-- opponent_team_id
-- FROM games
-- JOIN teams
-- ON (teams.id=games.team_home
-- OR teams.id=games.team_away)
-- WHERE games.id=game_id
-- AND teams.id!=team_id;
--
-- RETURN QUERY
-- SELECT
-- (CASE WHEN goals(game_id, team_id) > goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END) AS reg_win,
-- (CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END) AS reg_loss,
-- (CASE WHEN goals(game_id, team_id) > goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END) AS ot_win,
-- (CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END) AS ot_loss,
-- (CASE WHEN goals(game_id, team_id) = goals(game_id, opponent_team_id) THEN 1 ELSE 0 END) AS tie,
-- game_id AS game,
-- team_id AS team;
--END;
--$$ LANGUAGE plpgsql;
--
--CREATE OR REPLACE FUNCTION calculate_iihf_points(game_id INT, team_id INT)
--RETURNS INTEGER AS $$
--BEGIN
-- RETURN (
-- SELECT
-- (iihs_stats.reg_win * 3) +
-- (iihs_stats.reg_loss * 0) +
-- (iihs_stats.ot_win * 2) +
-- (iihs_stats.ot_loss * 1) +
-- (iihs_stats.tie * 2) AS points
-- FROM calculate_iihs_stats_stats(game_id, team_id) iihs_stats);
--END;
--$$ LANGUAGE plpgsql;

@ -0,0 +1 @@
DROP FUNCTION reg_win(INTEGER, INTEGER);

@ -0,0 +1,19 @@
CREATE FUNCTION reg_win(game_id INT, team_id INT)
RETURNS INTEGER
AS $$
DECLARE
opponent_team_id INTEGER;
BEGIN
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN (SELECT (CASE WHEN goals(game_id, team_id) > goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END));
END;
$$ LANGUAGE plpgsql;

@ -0,0 +1 @@
DROP FUNCTION reg_loss(INTEGER, INTEGER);

@ -0,0 +1,19 @@
CREATE FUNCTION reg_loss(game_id INT, team_id INT)
RETURNS INTEGER
AS $$
DECLARE
opponent_team_id INTEGER;
BEGIN
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN (SELECT (CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END));
END;
$$ LANGUAGE plpgsql;

@ -0,0 +1 @@
DROP FUNCTION ot_win(INTEGER, INTEGER);

@ -0,0 +1,19 @@
CREATE FUNCTION ot_win(game_id INT, team_id INT)
RETURNS INTEGER
AS $$
DECLARE
opponent_team_id INTEGER;
BEGIN
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN (SELECT (CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END));
END;
$$ LANGUAGE plpgsql;

@ -0,0 +1 @@
DROP FUNCTION ot_loss(INTEGER, INTEGER);

@ -0,0 +1,19 @@
CREATE FUNCTION ot_loss(game_id INT, team_id INT)
RETURNS INTEGER
AS $$
DECLARE
opponent_team_id INTEGER;
BEGIN
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN (SELECT (CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END));
END;
$$ LANGUAGE plpgsql;

@ -0,0 +1 @@
DROP FUNCTION tie(INTEGER, INTEGER);

@ -0,0 +1,19 @@
CREATE FUNCTION tie(game_id INT, team_id INT)
RETURNS INTEGER
AS $$
DECLARE
opponent_team_id INTEGER;
BEGIN
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN (SELECT (CASE WHEN goals(game_id, team_id) = goals(game_id, opponent_team_id) THEN 1 ELSE 0 END));
END;
$$ LANGUAGE plpgsql;

@ -22,24 +22,13 @@ BEGIN
RAISE EXCEPTION 'The team specified did not play this game.';
END IF;
SELECT
teams.id
INTO
opponent_team_id
FROM games
JOIN teams
ON (teams.id=games.team_home
OR teams.id=games.team_away)
WHERE games.id=game_id
AND teams.id!=team_id;
RETURN QUERY
SELECT
(CASE WHEN goals(game_id, team_id) > goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END) AS reg_win,
(CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) <= 3 THEN 1 ELSE 0 END) AS reg_loss,
(CASE WHEN goals(game_id, team_id) > goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END) AS ot_win,
(CASE WHEN goals(game_id, team_id) < goals(game_id, opponent_team_id) AND periods(game_id) > 3 THEN 1 ELSE 0 END) AS ot_loss,
(CASE WHEN goals(game_id, team_id) = goals(game_id, opponent_team_id) THEN 1 ELSE 0 END) AS tie,
reg_win(game_id, team_id) AS reg_win,
reg_loss(game_id, team_id) AS reg_loss,
ot_win(game_id, team_id) AS ot_win,
ot_loss(game_id, team_id) AS ot_loss,
tie(game_id, team_id) AS tie,
game_id AS game,
team_id AS team;
END;

@ -4,11 +4,11 @@ RETURNS INTEGER AS $$
BEGIN
RETURN (
SELECT
(iihs_stats.reg_win * 3) +
(iihs_stats.reg_loss * 0) +
(iihs_stats.ot_win * 2) +
(iihs_stats.ot_loss * 1) +
(iihs_stats.tie * 2) AS points
FROM calculate_iihs_stats_stats(game_id, team_id) iihs_stats);
(iihf_stats.reg_win * 3) +
(iihf_stats.reg_loss * 0) +
(iihf_stats.ot_win * 2) +
(iihf_stats.ot_loss * 1) +
(iihf_stats.tie * 2) AS points
FROM iihf_stats(game_id, team_id) iihf_stats);
END;
$$ LANGUAGE plpgsql;

@ -1,291 +1,303 @@
mod db;
mod model;
mod views;
mod filters;
mod model;
mod translations;
mod views;
use translations::{
SupportedLanguage,
};
use crate::model::{
League,
Team,
Division,
GamePlayer,
Player,
Shot,
Game,
};
use views::{
GoalDetails,
PlayerStats,
TeamStats,
ShotDetails,
get_play_by_play_from_game,
};
use crate::model::{Division, Game, GamePlayer, League, Player, Shot, Team};
use translations::SupportedLanguage;
use views::{GoalDetails, PlayerStats, ShotDetails, TeamStats, IihfStatsI64};
use sqlx::{
Postgres,
Pool,
};
use ormx::Table;
use askama::Template;
use axum::{
Router,
http::StatusCode,
extract::{
Path,
State,
},
response::{
Json,
IntoResponse,
},
routing::get,
extract::{Path, State},
http::StatusCode,
response::{IntoResponse, Json},
routing::get,
Router,
};
use axum_macros::debug_handler;
use ormx::Table;
use sqlx::{Pool, Postgres};
use std::net::SocketAddr;
use std::sync::Arc;
use askama::Template;
#[derive(Template)]
#[template(path="hello.html")]
#[template(path = "hello.html")]
struct HelloTemplate<'a> {
name: &'a str,
years: i32,
name: &'a str,
years: i32,
}
#[derive(Template)]
#[template(path="partials/box_score_table.html")]
#[template(path = "partials/box_score_table.html")]
struct BoxScoreTemplate {
goals: Vec<GoalDetails>,
lang: SupportedLanguage,
goals: Vec<GoalDetails>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="partials/individual_game_points_table.html")]
#[template(path = "partials/individual_game_points_table.html")]
struct IndividualGamePointsTableTemplate {
players: Vec<PlayerStats>,
lang: SupportedLanguage,
players: Vec<PlayerStats>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="partials/team_stats_table.html")]
#[template(path = "partials/team_stats_table.html")]
struct TeamGameStatsTemplate {
teams: Vec<TeamStats>,
lang: SupportedLanguage,
teams: Vec<TeamStats>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="division_list.html")]
#[template(path = "division_list.html")]
struct DivisionListTemplate {
league: League,
divisions: Vec<Division>,
lang: SupportedLanguage,
league: League,
divisions: Vec<Division>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="league_list.html")]
#[template(path = "league_list.html")]
struct LeagueListTemplate {
leagues: Vec<League>,
heading: String,
lang: SupportedLanguage,
leagues: Vec<League>,
heading: String,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="partials/iihf_team_stats_table.html")]
struct IihfTeamStatsTableTemplate {
iihf_stats: Vec<IihfStatsI64>,
}
#[derive(Template)]
#[template(path="game_list.html")]
#[template(path = "game_list.html")]
struct GameListTemplate {
division: Division,
games: Vec<Game>,
lang: SupportedLanguage,
division: Division,
iihf_team_stats_table: IihfTeamStatsTableTemplate,
games: Vec<Game>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="partials/play_by_play_table.html")]
#[template(path = "partials/play_by_play_table.html")]
struct ShotsTableTemplate {
shots: Vec<ShotDetails>,
lang: SupportedLanguage,
shots: Vec<ShotDetails>,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="game_score_page.html")]
#[template(path = "game_score_page.html")]
struct GameScorePageTemplate {
game: Game,
division: Division,
box_score: BoxScoreTemplate,
team_stats: TeamGameStatsTemplate,
individual_stats: IndividualGamePointsTableTemplate,
play_by_play: ShotsTableTemplate,
lang: SupportedLanguage,
game: Game,
division: Division,
box_score: BoxScoreTemplate,
team_stats: TeamGameStatsTemplate,
individual_stats: IndividualGamePointsTableTemplate,
play_by_play: ShotsTableTemplate,
lang: SupportedLanguage,
}
#[derive(Template)]
#[template(path="player_page.html")]
#[template(path = "player_page.html")]
pub struct PlayerPageTemplate {
player: Player,
league: League,
league_stats: PlayerStats,
lifetime_stats: PlayerStats,
lang: SupportedLanguage,
player: Player,
league: League,
league_stats: PlayerStats,
lifetime_stats: PlayerStats,
lang: SupportedLanguage,
}
#[derive(Clone)]
pub struct ServerState {
db_pool: Arc<Pool<Postgres>>,
db_pool: Arc<Pool<Postgres>>,
}
#[tokio::main]
async fn main() {
let pool = db::connect().await;
let xml_en = translations::en_lang();
let xml_fr = translations::fr_lang();
let state = ServerState {
db_pool: Arc::new(pool),
};
let router = Router::new()
.route("/:lang/", get(league_html))
.route("/:lang/shots/", get(shots_all))
.route("/:lang/test/", get(test_template))
.route("/:lang/league/:id/", get(divisions_for_league_html))
.route("/:lang/division/:id/", get(games_for_division_html))
.route("/:lang/game/:id/", get(score_for_game_html))
.route("/:lang/player/:name/", get(player_from_name))
.with_state(state);
let addr = SocketAddr::from(([127, 0, 0, 1], 8000));
println!("Listening on {}", addr);
axum::Server::bind(&addr)
.serve(router.into_make_service())
.await
.unwrap();
let pool = db::connect().await;
let xml_en = translations::en_lang();
let xml_fr = translations::fr_lang();
let state = ServerState {
db_pool: Arc::new(pool),
};
let router = Router::new()
.route("/:lang/", get(league_html))
.route("/:lang/shots/", get(shots_all))
.route("/:lang/test/", get(test_template))
.route("/:lang/league/:id/", get(divisions_for_league_html))
.route("/:lang/division/:id/", get(games_for_division_html))
.route("/:lang/game/:id/", get(score_for_game_html))
.route("/:lang/player/:name/", get(player_from_name))
.with_state(state);
let addr = SocketAddr::from(([127, 0, 0, 1], 8000));
println!("Listening on {}", addr);
axum::Server::bind(&addr)
.serve(router.into_make_service())
.await
.unwrap();
}
async fn player_from_name(State(server_config): State<ServerState>, Path((lang,name)): Path<(SupportedLanguage,String)>) -> impl IntoResponse {
let player = Player::from_name_case_insensitive(&server_config.db_pool, name)
.await
.unwrap();
let latest_league = Player::latest_league(&server_config.db_pool, player.id)
.await
.unwrap()
.unwrap();
let latest_league_stats = League::player_stats(&server_config.db_pool, player.id, latest_league.id)
.await
.unwrap();
let lifetime_stats = Player::lifetime_stats(&server_config.db_pool, player.id)
.await
.unwrap();
let html = PlayerPageTemplate {
player,
lang,
league: latest_league,
league_stats: latest_league_stats,
lifetime_stats,
};
(StatusCode::OK, html)
async fn player_from_name(
State(server_config): State<ServerState>,
Path((lang, name)): Path<(SupportedLanguage, String)>,
) -> impl IntoResponse {
let player = Player::from_name_case_insensitive(&server_config.db_pool, name)
.await
.unwrap();
let latest_league = Player::latest_league(&server_config.db_pool, player.id)
.await
.unwrap()
.unwrap();
let latest_league_stats =
League::player_stats(&server_config.db_pool, player.id, latest_league.id)
.await
.unwrap();
let lifetime_stats = Player::lifetime_stats(&server_config.db_pool, player.id)
.await
.unwrap();
let html = PlayerPageTemplate {
player,
lang,
league: latest_league,
league_stats: latest_league_stats,
lifetime_stats,
};
(StatusCode::OK, html)
}
async fn test_template<'a>() -> HelloTemplate<'a> {
HelloTemplate { name: "Tait", years: 24 }
HelloTemplate {
name: "Tait",
years: 24,
}
}
macro_rules! get_all {
($crud_struct:ident, $func_name:ident) => {
#[debug_handler]
async fn $func_name(State(server_config): State<ServerState>) -> impl IntoResponse {
let cruder = $crud_struct::all(&*server_config.db_pool)
.await
.unwrap();
(StatusCode::OK, Json(cruder))
}
}
($crud_struct:ident, $func_name:ident) => {
#[debug_handler]
async fn $func_name(State(server_config): State<ServerState>) -> impl IntoResponse {
let cruder = $crud_struct::all(&*server_config.db_pool).await.unwrap();
(StatusCode::OK, Json(cruder))
}
};
}
macro_rules! get_by_id {
($crud_struct:ident, $func_name:ident) => {
#[debug_handler]
async fn $func_name(State(server_config): State<ServerState>, Path(id): Path<i32>) -> impl IntoResponse {
let cruder = $crud_struct::get(&*server_config.db_pool, id)
.await
.unwrap();
(StatusCode::OK, Json(cruder))
}
}
($crud_struct:ident, $func_name:ident) => {
#[debug_handler]
async fn $func_name(
State(server_config): State<ServerState>,
Path(id): Path<i32>,
) -> impl IntoResponse {
let cruder = $crud_struct::get(&*server_config.db_pool, id)
.await
.unwrap();
(StatusCode::OK, Json(cruder))
}
};
}
async fn league_html(State(server_config): State<ServerState>, Path(lang): Path<SupportedLanguage>) -> impl IntoResponse {
let leagues = League::all(&*server_config.db_pool)
.await
.unwrap();
let heading = match lang {
SupportedLanguage::English => "IBIHF Leagues",
SupportedLanguage::French => "League de FIDHS",
}.to_string();
let leagues_template = LeagueListTemplate {
leagues,
heading,
lang,
};
(StatusCode::OK, leagues_template)
async fn league_html(
State(server_config): State<ServerState>,
Path(lang): Path<SupportedLanguage>,
) -> impl IntoResponse {
let leagues = League::all(&*server_config.db_pool).await.unwrap();
let heading = match lang {
SupportedLanguage::English => "IBIHF Leagues",
SupportedLanguage::French => "League de FIDHS",
}
.to_string();
let leagues_template = LeagueListTemplate {
leagues,
heading,
lang,
};
(StatusCode::OK, leagues_template)
}
async fn divisions_for_league_html(State(server_config): State<ServerState>, Path((lang,league_id)): Path<(SupportedLanguage, i32)>) -> impl IntoResponse {
let league = League::get(&*server_config.db_pool, league_id)
.await
.unwrap();
let divisions = Division::by_league(&*server_config.db_pool, league_id)
.await
.unwrap();
let html = DivisionListTemplate {
league,
divisions,
lang,
};
(StatusCode::OK, html)
async fn divisions_for_league_html(
State(server_config): State<ServerState>,
Path((lang, league_id)): Path<(SupportedLanguage, i32)>,
) -> impl IntoResponse {
let league = League::get(&*server_config.db_pool, league_id)
.await
.unwrap();
let divisions = Division::by_league(&*server_config.db_pool, league_id)
.await
.unwrap();
let html = DivisionListTemplate {
league,
divisions,
lang,
};
(StatusCode::OK, html)
}
async fn games_for_division_html(State(server_config): State<ServerState>, Path((lang,division_id)): Path<(SupportedLanguage,i32)>) -> impl IntoResponse {
let division = Division::get(&*server_config.db_pool, division_id)
.await
.unwrap();
let games = Game::by_division(&*server_config.db_pool, division.id)
.await
.unwrap();
let games_template = GameListTemplate {
division,
games,
lang,
};
(StatusCode::OK, games_template)
async fn games_for_division_html(
State(server_config): State<ServerState>,
Path((lang, division_id)): Path<(SupportedLanguage, i32)>,
) -> impl IntoResponse {
let division = Division::get(&*server_config.db_pool, division_id)
.await
.unwrap();
let games = Game::by_division(&*server_config.db_pool, division.id)
.await
.unwrap();
let iihf_stats = division.iihf_stats(&*server_config.db_pool)
.await
.unwrap();
let games_template = GameListTemplate {
division,
iihf_team_stats_table: IihfTeamStatsTableTemplate {
iihf_stats,
},
games,
lang,
};
(StatusCode::OK, games_template)
}
async fn score_for_game_html(State(server_config): State<ServerState>, Path((lang,game_id)): Path<(SupportedLanguage, i32)>) -> impl IntoResponse {
let game = sqlx::query_as::<_, Game>(
"SELECT * FROM games WHERE id = $1;"
)
.bind(game_id)
.fetch_one(&*server_config.db_pool)
.await
.unwrap();
let division = Division::get(&*server_config.db_pool, game.division)
.await
.unwrap();
let pbp = get_play_by_play_from_game(&server_config.db_pool, &game).await.unwrap();
let score = Game::score(&server_config.db_pool, game.id).await.unwrap();
let score_html = TeamGameStatsTemplate { teams: score, lang };
let goal_details = Game::box_score(&server_config.db_pool, game.id).await.unwrap();
let goal_details_html = IndividualGamePointsTableTemplate { players: goal_details, lang };
let box_score = Game::goals(&server_config.db_pool, game.id).await.unwrap();
let box_score_html = BoxScoreTemplate { goals: box_score, lang };
let pbp_html = ShotsTableTemplate {
shots: pbp,
lang,
};
let game_template = GameScorePageTemplate {
division,
game,
lang,
box_score: box_score_html,
team_stats: score_html,
individual_stats: goal_details_html,
play_by_play: pbp_html,
};
(StatusCode::OK, game_template)
async fn score_for_game_html(
State(server_config): State<ServerState>,
Path((lang, game_id)): Path<(SupportedLanguage, i32)>,
) -> impl IntoResponse {
let game = sqlx::query_as::<_, Game>("SELECT * FROM games WHERE id = $1;")
.bind(game_id)
.fetch_one(&*server_config.db_pool)
.await
.unwrap();
let division = Division::get(&*server_config.db_pool, game.division)
.await
.unwrap();
let pbp = game.play_by_play(&server_config.db_pool)
.await
.unwrap();
let score = game.score(&server_config.db_pool).await.unwrap();
let score_html = TeamGameStatsTemplate { teams: score, lang };
let goal_details = game.box_score(&server_config.db_pool)
.await
.unwrap();
let goal_details_html = IndividualGamePointsTableTemplate {
players: goal_details,
lang,
};
let box_score = game.goals(&server_config.db_pool).await.unwrap();
let box_score_html = BoxScoreTemplate {
goals: box_score,
lang,
};
let pbp_html = ShotsTableTemplate { shots: pbp, lang };
let game_template = GameScorePageTemplate {
division,
game,
lang,
box_score: box_score_html,
team_stats: score_html,
individual_stats: goal_details_html,
play_by_play: pbp_html,
};
(StatusCode::OK, game_template)
}
/*
@ -303,39 +315,15 @@ macro_rules! insert {
*/
macro_rules! impl_all_query_types {
($ty:ident, $func_all:ident, $func_by_id:ident) => {
get_all!($ty, $func_all);
get_by_id!($ty, $func_by_id);
}
($ty:ident, $func_all:ident, $func_by_id:ident) => {
get_all!($ty, $func_all);
get_by_id!($ty, $func_by_id);
};
}
impl_all_query_types!(
GamePlayer,
game_player_all,
game_player_id
);
impl_all_query_types!(
Player,
player_all,
player_id
);
impl_all_query_types!(
Team,
team_all,
team_id
);
impl_all_query_types!(
Shot,
shots_all,
shots_id
);
impl_all_query_types!(
Division,
division_all,
division_id
);
impl_all_query_types!(
League,
league_all,
league_id
);
impl_all_query_types!(GamePlayer, game_player_all, game_player_id);
impl_all_query_types!(Player, player_all, player_id);
impl_all_query_types!(Team, team_all, team_id);
impl_all_query_types!(Shot, shots_all, shots_id);
impl_all_query_types!(Division, division_all, division_id);
impl_all_query_types!(League, league_all, league_id);

@ -1,78 +1,94 @@
use crate::model::{Division, Game, League, Period, Player};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use sqlx::PgPool;
use crate::model::{
Player,
Game,
League,
Division,
Period,
};
use serde::{Serialize, Deserialize};
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct TeamStats {
pub name: String,
pub goals: i64,
pub shots: i64,
pub name: String,
pub goals: i64,
pub shots: i64,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct IihfGameStats {
pub team_name: String,
pub team_id: i32,
pub points: i32,
pub struct IihfStats {
pub team_name: String,
pub team_id: i32,
pub reg_wins: i32,
pub reg_losses: i32,
pub ot_wins: i32,
pub ot_losses: i32,
pub ties: i32,
pub points: i32,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct IihfStatsI64 {
pub team_name: String,
pub team_id: i32,
pub reg_wins: i64,
pub reg_losses: i64,
pub ot_wins: i64,
pub ot_losses: i64,
pub ties: i64,
pub points: i64,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct IihfPoints {
pub team_name: String,
pub team_id: i32,
pub points: i32,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct Notification {
pub scorer_name: String,
pub scorer_number: i32,
pub position: String,
pub scorer_team_name: String,
pub period_name: String,
pub period_time_left: i32,
pub scorer_name: String,
pub scorer_number: i32,
pub position: String,
pub scorer_team_name: String,
pub period_name: String,
pub period_time_left: i32,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct PlayerStats {
pub name: String,
pub goals: i64,
pub assists: i64,
pub points: i64,
pub name: String,
pub goals: i64,
pub assists: i64,
pub points: i64,
}
impl Game {
pub async fn box_score(pool: &PgPool, id: i32) -> Result<Vec<PlayerStats>, sqlx::Error> {
pub async fn game_box_score(pool: &PgPool, game_id: i32) -> Result<Vec<PlayerStats>, sqlx::Error> {
let query = r#"
SELECT
SELECT
COUNT(shots.id) AS points,
COUNT(CASE WHEN shots.shooter = game_players.id THEN shots.id END) AS goals,
COUNT(CASE WHEN shots.assistant = game_players.id OR shots.assistant_second = game_players.id THEN shots.id END) AS assists,
players.name
FROM game_players
JOIN players ON game_players.player = players.id
LEFT JOIN shots
FROM game_players
JOIN players
ON game_players.player = players.id
LEFT JOIN shots
ON shots.goal=true
AND (shots.shooter=game_players.id
AND (shots.shooter=game_players.id
OR shots.assistant=game_players.id
OR shots.assistant_second=game_players.id)
WHERE game_players.game=$1
GROUP BY
WHERE game_players.game=$1
GROUP BY
game_players.id,
players.name
HAVING COUNT(shots.id) > 0
ORDER BY
HAVING COUNT(shots.id) > 0
ORDER BY
points DESC,
goals DESC;
"#;
"#;
sqlx::query_as::<_, PlayerStats>(query)
.bind(id)
.fetch_all(pool)
.await
}
pub async fn goals(pool: &PgPool, id: i32) -> Result<Vec<GoalDetails>, sqlx::Error> {
.bind(game_id)
.fetch_all(pool)
.await
}
pub async fn game_goals(pool: &PgPool, game_id: i32) -> Result<Vec<GoalDetails>, sqlx::Error> {
sqlx::query_as::<_, GoalDetails>(
r#"
r#"
SELECT
shots.shooter AS player_id,
shots.assistant AS first_assist_id,
@ -104,78 +120,23 @@ impl Game {
ORDER BY
periods.period_type ASC,
shots.period_time DESC;
"#)
.bind(id)
.fetch_all(pool)
.await
}
pub async fn iihf_stats(pool: &PgPool, game_id: i32) -> Result<Vec<IihfGameStats>, sqlx::Error> {
"#,
)
.bind(game_id)
.fetch_all(pool)
.await
}
pub async fn game_iihf_stats(pool: &PgPool, game_id: i32) -> Result<Vec<IihfStats>, sqlx::Error> {
let query = r#"
SELECT
(CASE WHEN
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id=teams.id
THEN shots.id
END) >
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id!=teams.id
THEN shots.id
END)
AND (SELECT COUNT(id) FROM periods WHERE periods.game=games.id) <= 3
THEN 1
ELSE 0
END) AS reg_wins,
(CASE WHEN
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id=teams.id
THEN shots.id
END) <
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id!=teams.id
THEN shots.id
END)
AND (SELECT COUNT(id) FROM periods WHERE periods.game=games.id) <= 3
THEN 1
ELSE 0
END) AS reg_losses,
(CASE WHEN
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id=teams.id
THEN shots.id
END) >
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id!=teams.id
THEN shots.id
END)
AND (SELECT COUNT(id) FROM periods WHERE periods.game=games.id) > 3
THEN 1
ELSE 0
END) AS ot_wins,
(CASE WHEN
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id=teams.id
THEN shots.id
END) <
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id!=teams.id
THEN shots.id
END)
AND (SELECT COUNT(id) FROM periods WHERE periods.game=games.id) > 3
THEN 1
ELSE 0
END) AS ot_losses,
(CASE WHEN
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id=teams.id
THEN shots.id
END) =
COUNT(CASE WHEN shots.goal=true
AND scoring_team.id!=teams.id
THEN shots.id
END)
THEN 1
ELSE 0
END) AS ties
teams.id AS team_id,
teams.name AS team_name,
reg_win(games.id, teams.id) AS reg_wins,
reg_loss(games.id, teams.id) AS reg_losses,
ot_win(games.id, teams.id) AS ot_wins,
ot_loss(games.id, teams.id) AS ot_losses,
tie(games.id, teams.id) AS ties,
iihf_points(games.id, teams.id) AS points
FROM games
JOIN periods ON periods.game=games.id
JOIN shots ON shots.period=periods.id
@ -188,19 +149,19 @@ impl Game {
WHERE games.id=4
GROUP BY teams.id,games.id;
"#;
sqlx::query_as::<_, IihfGameStats>(query)
.bind(game_id)
.fetch_all(pool)
.await
}
/// Returns the number of points using IIHF scoring rules for each team.
/// NOTE: The algorithm used here requires that a 4th period is the "overtime";
/// it does not check if there was only two periods, followed by an overtime.
/// This should be sufficient for most.
pub async fn iihf_score(pool: &PgPool, game_id: i32) -> Result<Vec<IihfGameStats>, sqlx::Error> {
sqlx::query_as::<_, IihfStats>(query)
.bind(game_id)
.fetch_all(pool)
.await
}
/// Returns the number of points using IIHF scoring rules for each team.
/// NOTE: The algorithm used here requires that a 4th period is the "overtime";
/// it does not check if there was only two periods, followed by an overtime.
/// This should be sufficient for most.
pub async fn game_iihf_points(pool: &PgPool, game_id: i32) -> Result<Vec<IihfPoints>, sqlx::Error> {
let query = r#"
SELECT
calculate_iihf_points(games.id, teams.id) AS points,
iihf_points(games.id, teams.id) AS points,
teams.name AS team_name,
teams.id AS team_id
FROM games
@ -210,13 +171,13 @@ impl Game {
WHERE games.id=$1
ORDER BY points;
"#;
sqlx::query_as::<_, IihfGameStats>(query)
.bind(game_id)
.fetch_all(pool)
.await
}
/// Returns the number of shots and goals for each team in the game.
pub async fn score(pool: &PgPool, game_id: i32) -> Result<Vec<TeamStats>, sqlx::Error> {
sqlx::query_as::<_, IihfPoints>(query)
.bind(game_id)
.fetch_all(pool)
.await
}
/// Returns the number of shots and goals for each team in the game.
pub async fn game_score(pool: &PgPool, game_id: i32) -> Result<Vec<TeamStats>, sqlx::Error> {
let query = r#"
SELECT
COUNT(CASE WHEN shots.goal = true THEN shots.id END) AS goals,
@ -231,16 +192,110 @@ impl Game {
GROUP BY teams.id;
"#;
sqlx::query_as::<_, TeamStats>(query)
.bind(game_id)
.fetch_all(pool)
.await
}
.bind(game_id)
.fetch_all(pool)
.await
}
pub async fn game_play_by_play(
pool: &PgPool,
game_id: i32,
) -> Result<Vec<ShotDetails>, sqlx::Error> {
sqlx::query_as::<_, ShotDetails>(
r#"
SELECT
shots.shooter AS player_id,
shots.assistant AS first_assist_id,
shots.assistant_second AS second_assist_id,
shots.goal AS is_goal,
players.name AS player_name,
p_assistant.name AS first_assist_name,
p_assistant_second.name AS second_assist_name,
game_players.player_number AS player_number,
gp_assistant.player_number AS first_assist_number,
gp_assistant_second.player_number AS second_assist_number,
teams.name AS team_name,
teams.id AS team_id,
shots.period_time AS time_remaining,
period_types.id AS period_id,
period_types.short_name AS period_short_name
FROM shots
JOIN game_players ON game_players.id=shots.shooter
JOIN players ON players.id=game_players.player
JOIN teams ON teams.id=game_players.team
LEFT JOIN game_players gp_assistant ON gp_assistant.id=shots.assistant
LEFT JOIN players p_assistant ON p_assistant.id=gp_assistant.player
LEFT JOIN game_players gp_assistant_second ON gp_assistant_second.id=shots.assistant_second
LEFT JOIN players p_assistant_second ON p_assistant_second.id=gp_assistant_second.player
JOIN periods ON shots.period=periods.id
JOIN period_types ON periods.period_type=period_types.id
WHERE periods.game=$1
ORDER BY
periods.period_type ASC,
shots.period_time DESC;
"#,
)
.bind(game_id)
.fetch_all(pool)
.await
}
impl Game {
pub async fn score(&self, pool: &PgPool) -> Result<Vec<TeamStats>, sqlx::Error> {
game_score(pool, self.id).await
}
pub async fn box_score(&self, pool: &PgPool) -> Result<Vec<PlayerStats>, sqlx::Error> {
game_box_score(pool, self.id).await
}
pub async fn iihf_points(&self, pool: &PgPool) -> Result<Vec<IihfPoints>, sqlx::Error> {
game_iihf_points(pool, self.id).await
}
pub async fn iihf_stats(&self, pool: &PgPool) -> Result<Vec<IihfStats>, sqlx::Error> {
game_iihf_stats(pool, self.id).await
}
pub async fn goals(&self, pool: &PgPool) -> Result<Vec<GoalDetails>, sqlx::Error> {
game_goals(pool, self.id).await
}
pub async fn play_by_play(&self, pool: &PgPool) -> Result<Vec<ShotDetails>, sqlx::Error> {
game_play_by_play(pool, self.id).await
}
}
pub async fn division_iihf_stats(pool: &PgPool, division_id: i32) -> Result<Vec<IihfStatsI64>, sqlx::Error> {
sqlx::query_as::<_, IihfStatsI64>(
r#"
SELECT
SUM(reg_win(games.id, teams.id)) AS reg_wins,
SUM(reg_loss(games.id, teams.id)) AS reg_losses,
SUM(ot_win(games.id, teams.id)) AS ot_wins,
SUM(ot_loss(games.id, teams.id)) AS ot_losses,
SUM(tie(games.id, teams.id)) AS ties,
SUM(iihf_points(games.id, teams.id)) AS points,
teams.id AS team_id,
teams.name AS team_name
FROM
games
JOIN teams ON teams.id=games.team_home OR teams.id=games.team_away
WHERE games.division=$1
GROUP BY
teams.id
ORDER BY
points DESC;
"#
)
.bind(division_id)
.fetch_all(pool)
.await
}
impl Division {
pub async fn iihf_stats(&self, pool: &PgPool) -> Result<Vec<IihfStatsI64>, sqlx::Error> {
division_iihf_stats(pool, self.id).await
}
}
impl Player {
pub async fn latest_league(pool: &PgPool, id: i32) -> Result<Option<League>, sqlx::Error> {
let query =
r#"
pub async fn latest_league(pool: &PgPool, id: i32) -> Result<Option<League>, sqlx::Error> {
let query = r#"
SELECT leagues.*
FROM players
JOIN game_players ON game_players.player=players.id
@ -252,14 +307,13 @@ impl Player {
ORDER BY games.end_at DESC
LIMIT 1;
"#;
sqlx::query_as::<_, League>(query)
.bind(id)
.fetch_optional(pool)
.await
}
pub async fn latest_stats(pool: &PgPool, id: i32) -> Result<Vec<GoalDetails>, sqlx::Error> {
let query =
r#"
sqlx::query_as::<_, League>(query)
.bind(id)
.fetch_optional(pool)
.await
}
pub async fn latest_stats(pool: &PgPool, id: i32) -> Result<Vec<GoalDetails>, sqlx::Error> {
let query = r#"
SELECT
players.id AS player_id,
p_assist.id AS first_assist_id,
@ -292,13 +346,13 @@ impl Player {
shots.period_time ASC
LIMIT 5;
"#;
sqlx::query_as::<_, GoalDetails>(&query)
.bind(id)
.fetch_all(pool)
.await
}
pub async fn lifetime_stats(pool: &PgPool, id: i32) -> Result<PlayerStats, sqlx::Error> {
let query =r#"
sqlx::query_as::<_, GoalDetails>(&query)
.bind(id)
.fetch_all(pool)
.await
}
pub async fn lifetime_stats(pool: &PgPool, id: i32) -> Result<PlayerStats, sqlx::Error> {
let query = r#"
SELECT
COUNT(goals) AS goals,
COUNT(assists) AS assists,
@ -321,14 +375,14 @@ impl Player {
WHERE players.id=$1
GROUP BY players.id;
"#;
sqlx::query_as::<_, PlayerStats>(query)
.bind(id)
.fetch_one(pool)
.await
}
sqlx::query_as::<_, PlayerStats>(query)
.bind(id)
.fetch_one(pool)
.await
}
}
async fn get_player_stats_overview(pool: PgPool) -> Result<Vec<PlayerStats>, sqlx::Error> {
let query = r#"
let query = r#"
SELECT
COUNT(shots.id) AS points,
COUNT(CASE WHEN shots.shooter = game_players.id THEN shots.id END) AS goals,
@ -348,14 +402,18 @@ ORDER BY
points DESC,
goals DESC;
"#;
sqlx::query_as::<_, PlayerStats>(query)
.fetch_all(&pool)
.await
sqlx::query_as::<_, PlayerStats>(query)
.fetch_all(&pool)
.await
}
impl League {
pub async fn player_stats(pool: &PgPool, player_id: i32, league_id: i32) -> Result<PlayerStats, sqlx::Error> {
let query = r#"
pub async fn player_stats(
pool: &PgPool,
player_id: i32,
league_id: i32,
) -> Result<PlayerStats, sqlx::Error> {
let query = r#"
SELECT
COUNT(goals.id) AS goals,
COUNT(assists.id) AS assists,
@ -382,256 +440,202 @@ impl League {
AND players.id=$2
GROUP BY players.id;
"#;
sqlx::query_as::<_, PlayerStats>(query)
.bind(league_id)
.bind(player_id)
.fetch_one(pool)
.await
}
sqlx::query_as::<_, PlayerStats>(query)
.bind(league_id)
.bind(player_id)
.fetch_one(pool)
.await
}
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct GoalDetails {
pub player_id: i32,
pub player_name: String,
pub player_number: i32,
pub team_name: String,
pub team_id: i32,
pub time_remaining: i32,
pub period_id: i32,
pub period_short_name: String,
pub first_assist_name: Option<String>,
pub first_assist_number: Option<i32>,
pub first_assist_id: Option<i32>,
pub second_assist_name: Option<String>,
pub second_assist_id: Option<i32>,
pub second_assist_number: Option<i32>,
pub player_id: i32,
pub player_name: String,
pub player_number: i32,
pub team_name: String,
pub team_id: i32,
pub time_remaining: i32,
pub period_id: i32,
pub period_short_name: String,
pub first_assist_name: Option<String>,
pub first_assist_number: Option<i32>,
pub first_assist_id: Option<i32>,
pub second_assist_name: Option<String>,
pub second_assist_id: Option<i32>,
pub second_assist_number: Option<i32>,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct ShotDetails {
pub player_id: i32,
pub player_name: String,
pub player_number: i32,
pub team_name: String,
pub team_id: i32,
pub is_goal: bool,
pub time_remaining: i32,
pub period_short_name: String,
pub first_assist_name: Option<String>,
pub first_assist_number: Option<i32>,
pub first_assist_id: Option<i32>,
pub second_assist_name: Option<String>,
pub second_assist_id: Option<i32>,
pub second_assist_number: Option<i32>,
pub player_id: i32,
pub player_name: String,
pub player_number: i32,
pub team_name: String,
pub team_id: i32,
pub is_goal: bool,
pub time_remaining: i32,
pub period_short_name: String,
pub first_assist_name: Option<String>,
pub first_assist_number: Option<i32>,
pub first_assist_id: Option<i32>,
pub second_assist_name: Option<String>,
pub second_assist_id: Option<i32>,
pub second_assist_number: Option<i32>,
}
#[cfg(test)]
mod tests {
use crate::model::{Game, League, Player};
use crate::views::{game_play_by_play, get_player_stats_overview, Notification, game_score, game_goals, game_iihf_stats, game_iihf_points, game_box_score, division_iihf_stats};
use ormx::Table;
use std::env;
pub async fn get_play_by_play_from_game(pool: &PgPool, game: &Game) -> Result<Vec<ShotDetails>, sqlx::Error> {
sqlx::query_as::<_, ShotDetails>(
r#"
SELECT
shots.shooter AS player_id,
shots.assistant AS first_assist_id,
shots.assistant_second AS second_assist_id,
shots.goal AS is_goal,
players.name AS player_name,
p_assistant.name AS first_assist_name,
p_assistant_second.name AS second_assist_name,
game_players.player_number AS player_number,
gp_assistant.player_number AS first_assist_number,
gp_assistant_second.player_number AS second_assist_number,
teams.name AS team_name,
teams.id AS team_id,
shots.period_time AS time_remaining,
period_types.id AS period_id,
period_types.short_name AS period_short_name
FROM shots
JOIN game_players ON game_players.id=shots.shooter
JOIN players ON players.id=game_players.player
JOIN teams ON teams.id=game_players.team
LEFT JOIN game_players gp_assistant ON gp_assistant.id=shots.assistant
LEFT JOIN players p_assistant ON p_assistant.id=gp_assistant.player
LEFT JOIN game_players gp_assistant_second ON gp_assistant_second.id=shots.assistant_second
LEFT JOIN players p_assistant_second ON p_assistant_second.id=gp_assistant_second.player
JOIN periods ON shots.period=periods.id
JOIN period_types ON periods.period_type=period_types.id
WHERE periods.game=$1
ORDER BY
periods.period_type ASC,
shots.period_time DESC;
"#)
.bind(game.id)
.fetch_all(pool)
.await
}
#[test]
fn check_play_by_play() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let pbp = game_play_by_play(&pool, 3).await.unwrap();
})
}
#[test]
fn get_latest_stats_of_player() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::get(&pool, 2).await.unwrap();
let latest = Player::latest_stats(&pool, player.id).await.unwrap();
})
}
#[test]
fn check_league_player_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let league = League::get(&pool, 1).await.unwrap();
let player = Player::get(&pool, 2).await.unwrap();
let stats = League::player_stats(&pool, player.id, league.id)
.await
.unwrap();
assert_eq!(stats.name, "Hillary Scanlon");
})
}
#[cfg(test)]
mod tests {
use std::env;
use ormx::Table;
use crate::model::{
Game,
Player,
League,
};
use crate::views::{
Notification,
get_player_stats_overview,
get_play_by_play_from_game,
};
#[test]
fn check_play_by_play() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let game = Game::get(&pool, 3)
.await
.unwrap();
let pbp = get_play_by_play_from_game(&pool, &game)
.await
.unwrap();
})
}
#[test]
fn check_latest_league_for_player() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::get(&pool, 5).await.unwrap();
let league = Player::latest_league(&pool, player.id)
.await
.unwrap()
.unwrap();
assert_eq!(league.id, 1);
})
}
#[test]
fn get_latest_stats_of_player() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::get(&pool, 2)
.await
.unwrap();
let latest = Player::latest_stats(&pool, player.id)
.await
.unwrap();
})
}
#[test]
fn check_score_details_from_game() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let scores = game_goals(&pool, 3).await.unwrap();
println!("{scores:?}");
})
}
#[test]
fn check_league_player_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let league = League::get(&pool, 1)
.await
.unwrap();
let player = Player::get(&pool, 2)
.await
.unwrap();
let stats = League::player_stats(&pool, player.id, league.id).await.unwrap();
assert_eq!(stats.name, "Hillary Scanlon");
})
}
#[test]
fn check_box_score_from_game() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let scores = game_box_score(&pool, 4).await.unwrap();
println!("{scores:?}");
let second_top_scorer = scores.get(1).unwrap();
assert_eq!(second_top_scorer.name, "Allyssa Foulds");
assert_eq!(second_top_scorer.goals, 1, "Allysa should have 1 goal..");
assert_eq!(
second_top_scorer.assists, 2,
"Allyssa should have 2 assists."
);
assert_eq!(second_top_scorer.points, 3, "Allysa should have 3 points.");
assert_eq!(
scores.len(),
8,
"Players which did not receive any points should not be in the box score."
);
})
}
#[test]
fn check_latest_league_for_player() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::get(&pool, 5)
.await
.unwrap();
let league = Player::latest_league(&pool, player.id)
.await
.unwrap()
.unwrap();
assert_eq!(league.id, 1);
})
}
#[test]
fn check_division_iihf_points() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let score = division_iihf_stats(&pool, 1).await.unwrap();
assert_eq!(score.get(0).unwrap().points, 10);
assert_eq!(score.get(0).unwrap().team_name, "Bullseye");
assert_eq!(score.get(0).unwrap().reg_losses, 0);
assert_eq!(score.get(0).unwrap().ties, 2);
assert_eq!(score.get(1).unwrap().points, 4);
})
}
#[test]
fn check_score_details_from_game() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let game = Game::get(&pool, 3)
.await
.unwrap();
let scores = Game::goals(&pool, game.id)
.await
.unwrap();
println!("{scores:?}");
})
}
#[test]
fn check_iihf_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let score = game_iihf_stats(&pool, 4).await.unwrap();
assert_eq!(score.get(0).unwrap().points, 2);
assert_eq!(score.get(0).unwrap().team_name, "Bullseye");
assert_eq!(score.get(0).unwrap().reg_losses, 0);
assert_eq!(score.get(0).unwrap().ties, 1);
assert_eq!(score.get(1).unwrap().points, 2);
})
}
#[test]
fn check_box_score_from_game() {
tokio_test::block_on(async move{
let pool = db_connect().await;
let game = Game::get(&pool, 4)
.await
.unwrap();
let scores = Game::box_score(&pool, game.id)
.await
.unwrap();
println!("{scores:?}");
let second_top_scorer = scores.get(1).unwrap();
assert_eq!(second_top_scorer.name, "Allyssa Foulds");
assert_eq!(second_top_scorer.goals, 1, "Allysa should have 1 goal..");
assert_eq!(second_top_scorer.assists, 2, "Allyssa should have 2 assists.");
assert_eq!(second_top_scorer.points, 3, "Allysa should have 3 points.");
assert_eq!(scores.len(), 8, "Players which did not receive any points should not be in the box score.");
})
}
#[test]
fn check_iihf_points() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let score = game_iihf_points(&pool, 4).await.unwrap();
assert_eq!(score.get(0).unwrap().points, 2);
assert_eq!(score.get(0).unwrap().team_name, "Bullseye");
assert_eq!(score.get(1).unwrap().points, 2);
})
}
#[test]
fn check_iihf_score() {
tokio_test::block_on(async move{
let pool = db_connect().await;
let game = Game::get(&pool, 4)
.await
.unwrap();
let score = Game::iihf_score(&pool, game.id)
.await
.unwrap();
assert_eq!(score.get(0).unwrap().points, 2);
assert_eq!(score.get(0).unwrap().team_name, "Bullseye");
assert_eq!(score.get(1).unwrap().points, 2);
})
}
#[test]
fn check_game_score() {
tokio_test::block_on(async move{
let pool = db_connect().await;
let game = Game::get(&pool, 1)
.await
.unwrap();
let score = Game::score(&pool, game.id)
.await
.unwrap();
assert_eq!(score.get(0).unwrap().goals, 1);
assert_eq!(score.get(1).unwrap().goals, 1);
})
}
#[test]
fn check_game_score() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let score = game_score(&pool, 1).await.unwrap();
assert_eq!(score.get(0).unwrap().goals, 1);
assert_eq!(score.get(1).unwrap().goals, 1);
})
}
#[test]
fn check_player_overall_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let players_stats = get_player_stats_overview(pool).await.unwrap();
for player_stats in players_stats {
println!("{player_stats:?}");
}
})
}
#[test]
fn check_player_overall_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let players_stats = get_player_stats_overview(pool).await.unwrap();
for player_stats in players_stats {
println!("{player_stats:?}");
}
})
}
#[test]
fn check_lifetime_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let lifetime_stats = Player::lifetime_stats(&pool, 5)
.await
.unwrap();
})
}
#[test]
fn check_lifetime_stats() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let lifetime_stats = Player::lifetime_stats(&pool, 5).await.unwrap();
})
}
#[test]
fn check_notification_query() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let query = r#"
#[test]
fn check_notification_query() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let query = r#"
SELECT
teams.name AS scorer_team_name,
players.name AS scorer_name,
@ -648,32 +652,33 @@ JOIN periods ON periods.id=shots.period
JOIN period_types ON period_types.id=periods.period_type
JOIN positions ON positions.id=game_players.position;
"#;
let result = sqlx::query_as::<_, Notification>(query)
.fetch_one(&pool)
.await
.unwrap();
let minutes = result.period_time_left / 60;
let seconds = result.period_time_left % 60;
println!("{0} {1} player #{3} {2} has scored! Time of the goal: {4}:{5} in the {6}",
result.scorer_team_name,
result.position,
result.scorer_name,
result.scorer_number,
minutes,
seconds,
result.period_name
);
});
}
let result = sqlx::query_as::<_, Notification>(query)
.fetch_one(&pool)
.await
.unwrap();
let minutes = result.period_time_left / 60;
let seconds = result.period_time_left % 60;
println!(
"{0} {1} player #{3} {2} has scored! Time of the goal: {4}:{5} in the {6}",
result.scorer_team_name,
result.position,
result.scorer_name,
result.scorer_number,
minutes,
seconds,
result.period_name
);
});
}
/// A simple function to connect to the database.
async fn db_connect() -> sqlx::PgPool {
let db_url = env::var("DATABASE_URL").expect("DATABASE_URL environment variable must be set to run tests.");
sqlx::postgres::PgPoolOptions::new()
.max_connections(1)
.connect(&db_url)
.await
.expect("Active database connection must be made")
}
/// A simple function to connect to the database.
async fn db_connect() -> sqlx::PgPool {
let db_url = env::var("DATABASE_URL")
.expect("DATABASE_URL environment variable must be set to run tests.");
sqlx::postgres::PgPoolOptions::new()
.max_connections(1)
.connect(&db_url)
.await
.expect("Active database connection must be made")
}
}

@ -1,10 +1,13 @@
{% extends "master.html" %}
{% block title %}Games{% endblock %}
{% block title %}Games for {{ division.name }}{% endblock %}
{% block content %}
<h1 id="games">Games for {{ division.name }}</h1>
<h1>Division: {{ division.name }}</h1>
{% if games.len() > 0 %}
<h2 id="iihf_points">Points</h2>
{{ iihf_team_stats_table|safe }}
<h2 id="games">Games</h2>
<ol aria-labelledby="games">
{% for game in games %}
<li><a href="/en/game/{{ game.id }}/">{{ game.name }}</a></li>

@ -0,0 +1,26 @@
<table>
<thead>
<tr>
<th>Team</th>
<th>Points</th>
<th>RW</th>
<th>RL</th>
<th>OW</th>
<th>OL</th>
<th>T</th>
</tr>
</thead>
<tbody>
{% for team in iihf_stats %}
<tr>
<td>{{ team.team_name }}</td>
<td>{{ team.points }}</td>
<td>{{ team.reg_wins }}</td>
<td>{{ team.reg_losses }}</td>
<td>{{ team.ot_wins }}</td>
<td>{{ team.ot_losses }}</td>
<td>{{ team.ties }}</td>
</tr>
{% endfor %}
</tbody>
</table>
Loading…
Cancel
Save