diff --git a/migrations/20230405014229_create_iihf_stats_per_game.down.sql b/migrations/20230405014229_create_iihf_stats_per_game.down.sql deleted file mode 100644 index 093fb5b..0000000 --- a/migrations/20230405014229_create_iihf_stats_per_game.down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- Add down migration script here ---DROP FUNCTION game_results(INTEGER, INTEGER); diff --git a/migrations/20230405014229_create_iihf_stats_per_game.up.sql b/migrations/20230405014229_create_iihf_stats_per_game.up.sql deleted file mode 100644 index 94795da..0000000 --- a/migrations/20230405014229_create_iihf_stats_per_game.up.sql +++ /dev/null @@ -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; diff --git a/migrations/20230406195100_create_reg_win_function.down.sql b/migrations/20230406195100_create_reg_win_function.down.sql new file mode 100644 index 0000000..dfa1ac3 --- /dev/null +++ b/migrations/20230406195100_create_reg_win_function.down.sql @@ -0,0 +1 @@ +DROP FUNCTION reg_win(INTEGER, INTEGER); diff --git a/migrations/20230406195100_create_reg_win_function.up.sql b/migrations/20230406195100_create_reg_win_function.up.sql new file mode 100644 index 0000000..fca182b --- /dev/null +++ b/migrations/20230406195100_create_reg_win_function.up.sql @@ -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; diff --git a/migrations/20230406195101_create_reg_loss_function.down.sql b/migrations/20230406195101_create_reg_loss_function.down.sql new file mode 100644 index 0000000..8a85daa --- /dev/null +++ b/migrations/20230406195101_create_reg_loss_function.down.sql @@ -0,0 +1 @@ +DROP FUNCTION reg_loss(INTEGER, INTEGER); diff --git a/migrations/20230406195101_create_reg_loss_function.up.sql b/migrations/20230406195101_create_reg_loss_function.up.sql new file mode 100644 index 0000000..6b8f3d9 --- /dev/null +++ b/migrations/20230406195101_create_reg_loss_function.up.sql @@ -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; diff --git a/migrations/20230406195111_create_ot_win_function.down.sql b/migrations/20230406195111_create_ot_win_function.down.sql new file mode 100644 index 0000000..2193bdc --- /dev/null +++ b/migrations/20230406195111_create_ot_win_function.down.sql @@ -0,0 +1 @@ +DROP FUNCTION ot_win(INTEGER, INTEGER); diff --git a/migrations/20230406195111_create_ot_win_function.up.sql b/migrations/20230406195111_create_ot_win_function.up.sql new file mode 100644 index 0000000..24d1173 --- /dev/null +++ b/migrations/20230406195111_create_ot_win_function.up.sql @@ -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; diff --git a/migrations/20230406195121_create_ot_loss_function.down.sql b/migrations/20230406195121_create_ot_loss_function.down.sql new file mode 100644 index 0000000..96cc862 --- /dev/null +++ b/migrations/20230406195121_create_ot_loss_function.down.sql @@ -0,0 +1 @@ +DROP FUNCTION ot_loss(INTEGER, INTEGER); diff --git a/migrations/20230406195121_create_ot_loss_function.up.sql b/migrations/20230406195121_create_ot_loss_function.up.sql new file mode 100644 index 0000000..7eb75c4 --- /dev/null +++ b/migrations/20230406195121_create_ot_loss_function.up.sql @@ -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; diff --git a/migrations/20230406195131_create_tie_function.down.sql b/migrations/20230406195131_create_tie_function.down.sql new file mode 100644 index 0000000..bfe5c77 --- /dev/null +++ b/migrations/20230406195131_create_tie_function.down.sql @@ -0,0 +1 @@ +DROP FUNCTION tie(INTEGER, INTEGER); diff --git a/migrations/20230406195131_create_tie_function.up.sql b/migrations/20230406195131_create_tie_function.up.sql new file mode 100644 index 0000000..7e8457a --- /dev/null +++ b/migrations/20230406195131_create_tie_function.up.sql @@ -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; diff --git a/migrations/20230406195200_create_iihf_stats_function.up.sql b/migrations/20230406195200_create_iihf_stats_function.up.sql index 49cb98d..30e8c29 100644 --- a/migrations/20230406195200_create_iihf_stats_function.up.sql +++ b/migrations/20230406195200_create_iihf_stats_function.up.sql @@ -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; diff --git a/migrations/20230406195840_create_iihf_points_function.up.sql b/migrations/20230406195840_create_iihf_points_function.up.sql index 892880b..4169f89 100644 --- a/migrations/20230406195840_create_iihf_points_function.up.sql +++ b/migrations/20230406195840_create_iihf_points_function.up.sql @@ -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; diff --git a/src/main.rs b/src/main.rs index 6371e56..5f01d54 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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, - lang: SupportedLanguage, + goals: Vec, + lang: SupportedLanguage, } #[derive(Template)] -#[template(path="partials/individual_game_points_table.html")] +#[template(path = "partials/individual_game_points_table.html")] struct IndividualGamePointsTableTemplate { - players: Vec, - lang: SupportedLanguage, + players: Vec, + lang: SupportedLanguage, } #[derive(Template)] -#[template(path="partials/team_stats_table.html")] +#[template(path = "partials/team_stats_table.html")] struct TeamGameStatsTemplate { - teams: Vec, - lang: SupportedLanguage, + teams: Vec, + lang: SupportedLanguage, } #[derive(Template)] -#[template(path="division_list.html")] +#[template(path = "division_list.html")] struct DivisionListTemplate { - league: League, - divisions: Vec, - lang: SupportedLanguage, + league: League, + divisions: Vec, + lang: SupportedLanguage, } #[derive(Template)] -#[template(path="league_list.html")] +#[template(path = "league_list.html")] struct LeagueListTemplate { - leagues: Vec, - heading: String, - lang: SupportedLanguage, + leagues: Vec, + heading: String, + lang: SupportedLanguage, +} + +#[derive(Template)] +#[template(path="partials/iihf_team_stats_table.html")] +struct IihfTeamStatsTableTemplate { + iihf_stats: Vec, } #[derive(Template)] -#[template(path="game_list.html")] +#[template(path = "game_list.html")] struct GameListTemplate { - division: Division, - games: Vec, - lang: SupportedLanguage, + division: Division, + iihf_team_stats_table: IihfTeamStatsTableTemplate, + games: Vec, + lang: SupportedLanguage, } #[derive(Template)] -#[template(path="partials/play_by_play_table.html")] +#[template(path = "partials/play_by_play_table.html")] struct ShotsTableTemplate { - shots: Vec, - lang: SupportedLanguage, + shots: Vec, + 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>, + db_pool: Arc>, } #[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, 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, + 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) -> 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) -> 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, Path(id): Path) -> 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, + Path(id): Path, + ) -> 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, Path(lang): Path) -> 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, + Path(lang): Path, +) -> 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, 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, + 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, 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, + 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, 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, + 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); diff --git a/src/views.rs b/src/views.rs index 00d2874..9473c7f 100644 --- a/src/views.rs +++ b/src/views.rs @@ -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, sqlx::Error> { +pub async fn game_box_score(pool: &PgPool, game_id: i32) -> Result, 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, sqlx::Error> { + .bind(game_id) + .fetch_all(pool) + .await +} +pub async fn game_goals(pool: &PgPool, game_id: i32) -> Result, 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, sqlx::Error> { + "#, + ) + .bind(game_id) + .fetch_all(pool) + .await +} +pub async fn game_iihf_stats(pool: &PgPool, game_id: i32) -> Result, 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, 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, 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, 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, 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, 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, sqlx::Error> { + game_score(pool, self.id).await + } + pub async fn box_score(&self, pool: &PgPool) -> Result, sqlx::Error> { + game_box_score(pool, self.id).await + } + pub async fn iihf_points(&self, pool: &PgPool) -> Result, sqlx::Error> { + game_iihf_points(pool, self.id).await + } + pub async fn iihf_stats(&self, pool: &PgPool) -> Result, sqlx::Error> { + game_iihf_stats(pool, self.id).await + } + pub async fn goals(&self, pool: &PgPool) -> Result, sqlx::Error> { + game_goals(pool, self.id).await + } + pub async fn play_by_play(&self, pool: &PgPool) -> Result, sqlx::Error> { + game_play_by_play(pool, self.id).await + } +} + +pub async fn division_iihf_stats(pool: &PgPool, division_id: i32) -> Result, 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, sqlx::Error> { + division_iihf_stats(pool, self.id).await + } } impl Player { - pub async fn latest_league(pool: &PgPool, id: i32) -> Result, sqlx::Error> { - let query = - r#" + pub async fn latest_league(pool: &PgPool, id: i32) -> Result, 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, 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, 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 { - let query =r#" + sqlx::query_as::<_, GoalDetails>(&query) + .bind(id) + .fetch_all(pool) + .await + } + pub async fn lifetime_stats(pool: &PgPool, id: i32) -> Result { + 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, 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 { - let query = r#" + pub async fn player_stats( + pool: &PgPool, + player_id: i32, + league_id: i32, + ) -> Result { + 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, - pub first_assist_number: Option, - pub first_assist_id: Option, - pub second_assist_name: Option, - pub second_assist_id: Option, - pub second_assist_number: Option, + 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, + pub first_assist_number: Option, + pub first_assist_id: Option, + pub second_assist_name: Option, + pub second_assist_id: Option, + pub second_assist_number: Option, } #[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, - pub first_assist_number: Option, - pub first_assist_id: Option, - pub second_assist_name: Option, - pub second_assist_id: Option, - pub second_assist_number: Option, + 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, + pub first_assist_number: Option, + pub first_assist_id: Option, + pub second_assist_name: Option, + pub second_assist_id: Option, + pub second_assist_number: Option, } +#[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, 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") + } } diff --git a/templates/game_list.html b/templates/game_list.html index eb61d3c..969fea2 100644 --- a/templates/game_list.html +++ b/templates/game_list.html @@ -1,10 +1,13 @@ {% extends "master.html" %} -{% block title %}Games{% endblock %} +{% block title %}Games for {{ division.name }}{% endblock %} {% block content %} -

Games for {{ division.name }}

+

Division: {{ division.name }}

{% if games.len() > 0 %} +

Points

+ {{ iihf_team_stats_table|safe }} +

Games

    {% for game in games %}
  1. {{ game.name }}
  2. diff --git a/templates/partials/iihf_team_stats_table.html b/templates/partials/iihf_team_stats_table.html new file mode 100644 index 0000000..b6e3414 --- /dev/null +++ b/templates/partials/iihf_team_stats_table.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + {% for team in iihf_stats %} + + + + + + + + + + {% endfor %} + +
    TeamPointsRWRLOWOLT
    {{ team.team_name }}{{ team.points }}{{ team.reg_wins }}{{ team.reg_losses }}{{ team.ot_wins }}{{ team.ot_losses }}{{ team.ties }}