Cargo fmt; fix a bug that is only happening because routes arent typesafe

master
Tait Hoyem 1 year ago
parent d260895828
commit d84931f2b5

@ -1,9 +1,10 @@
use sqlx::{Postgres, Pool};
use sqlx::postgres::PgPoolOptions;
use sqlx::{Pool, Postgres};
pub async fn connect() -> Pool<Postgres> {
PgPoolOptions::new()
.max_connections(8)
.connect("postgres://ibihf:ibihf@localhost/ibihf").await
.unwrap()
PgPoolOptions::new()
.max_connections(8)
.connect("postgres://ibihf:ibihf@localhost/ibihf")
.await
.unwrap()
}

@ -1,5 +1,5 @@
pub fn seconds_as_time(secs: &i32) -> ::askama::Result<String> {
let minutes = secs / 60;
let seconds = secs % 60;
Ok(format!("{}:{}", minutes, seconds))
let minutes = secs / 60;
let seconds = secs % 60;
Ok(format!("{}:{}", minutes, seconds))
}

@ -1,68 +1,73 @@
use askama::i18n::FluentValue;
use crate::LOCALES;
use askama::i18n::{langid, LanguageIdentifier, Locale};
use askama::i18n::fluent_templates::Loader;
use serde::{
Serialize,
Deserialize,
};
use strum::{
IntoEnumIterator,
};
use strum_macros::{
EnumIter,
AsRefStr,
EnumVariantNames,
EnumCount,
};
use askama::i18n::FluentValue;
use askama::i18n::{langid, LanguageIdentifier, Locale};
use serde::{Deserialize, Serialize};
use strum::IntoEnumIterator;
use strum_macros::{AsRefStr, EnumCount, EnumIter, EnumVariantNames};
use derive_more::Display;
#[derive(Serialize, Deserialize, Clone, Copy, Debug, Display, EnumIter, EnumCount, EnumVariantNames, AsRefStr, PartialEq, Eq)]
#[derive(
Serialize,
Deserialize,
Clone,
Copy,
Debug,
Display,
EnumIter,
EnumCount,
EnumVariantNames,
AsRefStr,
PartialEq,
Eq,
)]
pub enum SupportedLanguage {
#[serde(rename="en-ca")]
#[display(fmt="en-ca")]
English,
#[serde(rename="fr-ca")]
#[display(fmt="fr-ca")]
French,
#[serde(rename = "en-ca")]
#[display(fmt = "en-ca")]
English,
#[serde(rename = "fr-ca")]
#[display(fmt = "fr-ca")]
French,
}
impl From<SupportedLanguage> for FluentValue<'_> {
fn from(n: SupportedLanguage) -> Self {
n.to_string().into()
}
fn from(n: SupportedLanguage) -> Self {
n.to_string().into()
}
}
impl Into<LanguageIdentifier> for SupportedLanguage {
fn into(self) -> LanguageIdentifier {
match self {
Self::English => langid!("en-ca"),
Self::French => langid!("fr-ca"),
fn into(self) -> LanguageIdentifier {
match self {
Self::English => langid!("en-ca"),
Self::French => langid!("fr-ca"),
}
}
}
}
impl<'a> Into<Locale<'a>> for SupportedLanguage {
fn into(self) -> Locale<'a> {
Locale::new(self.into(), &LOCALES)
}
fn into(self) -> Locale<'a> {
Locale::new(self.into(), &LOCALES)
}
}
impl SupportedLanguage {
pub fn lookup(&self, key: &str) -> String {
LOCALES.lookup(&(*self).into(), key).expect("Unable to find key {key} in locale {self}.")
}
pub fn other_langs(&self) -> impl Iterator<Item=Self> + '_ {
Self::iter()
.filter(move |lang| lang != self)
}
pub fn native_name(&self) -> String {
match self {
Self::English => "English",
Self::French => "Français"
}.to_string()
}
pub fn lookup(&self, key: &str) -> String {
LOCALES
.lookup(&(*self).into(), key)
.expect("Unable to find key {key} in locale {self}.")
}
pub fn other_langs(&self) -> impl Iterator<Item = Self> + '_ {
Self::iter().filter(move |lang| lang != self)
}
pub fn native_name(&self) -> String {
match self {
Self::English => "English",
Self::French => "Français",
}
.to_string()
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct LangLink {
pub href: String,
pub name: String,
pub href: String,
pub name: String,
}

@ -1,9 +1,9 @@
mod db;
mod filters;
mod model;
mod views;
mod languages;
mod model;
mod traits;
mod views;
macro_rules! other_lang_urls {
($lang:expr, $template:ident) => {
@ -12,9 +12,9 @@ macro_rules! other_lang_urls {
name: olang.native_name(),
href: Into::<Locale>::into($lang)
.translate(
$template::URL_KEY,
$template::URL_KEY_TEMPLATE,
vec![
("lang", olang.to_string().into())
("lang", olang.into())
],
)
.expect("Unable to find key {key} in locale {self}.")
@ -27,7 +27,7 @@ macro_rules! other_lang_urls {
name: olang.native_name(),
href: Into::<Locale>::into($lang)
.translate(
$template::URL_KEY,
$template::URL_KEY_TEMPLATE,
hashmap_macro::hashmap![
"lang" => olang.to_string().into(),
$($k => $v.into()),+
@ -39,19 +39,16 @@ macro_rules! other_lang_urls {
};
}
use traits::TemplateUrl;
use static_assertions::assert_impl_all;
use traits::TemplateUrl;
#[macro_use]
extern crate ibihf_macros;
use askama::i18n::{langid, Locale};
askama::i18n::load!(LOCALES);
use crate::model::{Division, Game, League, Player, Language};
use views::{GoalDetails, PlayerStats, ShotDetails, TeamStats, IihfStatsI64};
use languages::{
SupportedLanguage,
LangLink,
};
use crate::model::{Division, Game, Language, League, Player};
use languages::{LangLink, SupportedLanguage};
use views::{GoalDetails, IihfStatsI64, PlayerStats, ShotDetails, TeamStats};
use askama::Template;
use axum::{
@ -71,11 +68,11 @@ const VERSION: &str = "0.3.5";
#[derive(Template, TemplateUrl)]
#[template(path = "language_list.html")]
struct LanguageListTemplate<'a> {
#[locale]
pub loc: Locale<'a>,
pub lang_links: Vec<LangLink>,
pub lang: SupportedLanguage,
pub languages: Vec<Language>,
#[locale]
pub loc: Locale<'a>,
pub lang_links: Vec<LangLink>,
pub lang: SupportedLanguage,
pub languages: Vec<Language>,
}
#[derive(Template)]
@ -128,11 +125,11 @@ struct LeagueListTemplate<'a> {
assert_impl_all!(LeagueListTemplate: TemplateUrl);
#[derive(Template)]
#[template(path="partials/iihf_team_stats_table.html")]
#[template(path = "partials/iihf_team_stats_table.html")]
struct IihfTeamStatsTableTemplate<'a> {
#[locale]
locale: Locale<'a>,
iihf_stats: Vec<IihfStatsI64>,
#[locale]
locale: Locale<'a>,
iihf_stats: Vec<IihfStatsI64>,
}
#[derive(Template, TemplateUrl)]
@ -143,7 +140,7 @@ struct GameListTemplate<'a> {
locale: Locale<'a>,
lang_links: Vec<LangLink>,
division: Division,
iihf_team_stats_table: IihfTeamStatsTableTemplate<'a>,
iihf_team_stats_table: IihfTeamStatsTableTemplate<'a>,
games: Vec<Game>,
lang: SupportedLanguage,
}
@ -203,10 +200,22 @@ async fn main() {
let router = Router::new()
.route("/", get(language_list))
.route("/:lang/", get(league_html))
.route(&SupportedLanguage::English.lookup(DivisionListTemplate::URL_KEY), get(divisions_for_league_html))
.route(&SupportedLanguage::English.lookup(GameListTemplate::URL_KEY), get(games_for_division_html))
.route(&SupportedLanguage::English.lookup(GameScorePageTemplate::URL_KEY), get(score_for_game_html))
.route(&SupportedLanguage::French.lookup(GameScorePageTemplate::URL_KEY), get(score_for_game_html))
.route(
&SupportedLanguage::English.lookup(DivisionListTemplate::URL_KEY),
get(divisions_for_league_html),
)
.route(
&SupportedLanguage::English.lookup(GameListTemplate::URL_KEY),
get(games_for_division_html),
)
.route(
&SupportedLanguage::English.lookup(GameScorePageTemplate::URL_KEY),
get(score_for_game_html),
)
.route(
&SupportedLanguage::French.lookup(GameScorePageTemplate::URL_KEY),
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));
@ -217,19 +226,15 @@ async fn main() {
.unwrap();
}
async fn language_list(
State(server_config): State<ServerState>,
) -> impl IntoResponse {
let languages = Language::all(&*server_config.db_pool)
.await
.unwrap();
let lang_list_tmpl = LanguageListTemplate {
loc: Locale::new(langid!("en-ca"), &LOCALES),
lang_links: Vec::new(),
languages,
lang: SupportedLanguage::English,
};
(StatusCode::OK, lang_list_tmpl)
async fn language_list(State(server_config): State<ServerState>) -> impl IntoResponse {
let languages = Language::all(&*server_config.db_pool).await.unwrap();
let lang_list_tmpl = LanguageListTemplate {
loc: Locale::new(langid!("en-ca"), &LOCALES),
lang_links: Vec::new(),
languages,
lang: SupportedLanguage::English,
};
(StatusCode::OK, lang_list_tmpl)
}
async fn player_from_name(
@ -294,7 +299,7 @@ async fn league_html(
) -> impl IntoResponse {
let leagues = League::all(&*server_config.db_pool).await.unwrap();
let leagues_template = LeagueListTemplate {
lang_links: other_lang_urls!(lang, PlayerPageTemplate),
lang_links: other_lang_urls!(lang, LeagueListTemplate),
locale: lang.into(),
leagues,
lang,
@ -315,7 +320,7 @@ async fn divisions_for_league_html(
let html = DivisionListTemplate {
locale: lang.into(),
// TODO: add league_id here
lang_links: other_lang_urls!(lang, PlayerPageTemplate, "id" => league.id),
lang_links: other_lang_urls!(lang, DivisionListTemplate, "id" => league.id),
league,
divisions,
lang,
@ -333,17 +338,15 @@ async fn games_for_division_html(
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 iihf_stats = division.iihf_stats(&*server_config.db_pool).await.unwrap();
let games_template = GameListTemplate {
locale: lang.into(),
lang_links: other_lang_urls!(lang, GameListTemplate, "id" => division_id),
division,
iihf_team_stats_table: IihfTeamStatsTableTemplate {
locale: lang.into(),
iihf_stats,
},
iihf_team_stats_table: IihfTeamStatsTableTemplate {
locale: lang.into(),
iihf_stats,
},
games,
lang,
};
@ -361,14 +364,13 @@ async fn score_for_game_html(
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 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 { locale: lang.into(), teams: score };
let goal_details = game.box_score(&server_config.db_pool)
.await
.unwrap();
let score_html = TeamGameStatsTemplate {
locale: lang.into(),
teams: score,
};
let goal_details = game.box_score(&server_config.db_pool).await.unwrap();
let goal_details_html = IndividualGamePointsTableTemplate {
locale: lang.into(),
players: goal_details,
@ -378,7 +380,10 @@ async fn score_for_game_html(
locale: lang.into(),
goals: box_score,
};
let pbp_html = ShotsTableTemplate { locale: lang.into(), shots: pbp };
let pbp_html = ShotsTableTemplate {
locale: lang.into(),
shots: pbp,
};
let game_template = GameScorePageTemplate {
locale: lang.into(),
lang_links: other_lang_urls!(lang, GameScorePageTemplate, "id" => game_id),

@ -1,152 +1,154 @@
use sqlx::FromRow;
use sqlx::types::chrono::{DateTime, Utc};
use chrono::serde::ts_seconds;
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use sqlx::types::chrono::{DateTime, Utc};
use sqlx::FromRow;
pub trait TableName {
const TABLE_NAME: &'static str;
const TABLE_NAME: &'static str;
}
macro_rules! impl_table_name {
($ty:ident, $tname:literal) => {
impl TableName for $ty {
const TABLE_NAME: &'static str = $tname;
}
}
($ty:ident, $tname:literal) => {
impl TableName for $ty {
const TABLE_NAME: &'static str = $tname;
}
};
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Table)]
#[ormx(table = "supported_languages", id = id, insertable, deletable)]
pub struct Language {
#[ormx(default)]
pub id: i32,
pub native_name: String,
pub short_name: String,
#[ormx(default)]
pub id: i32,
pub native_name: String,
pub short_name: String,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Table)]
#[ormx(table = "leagues", id = id, insertable, deletable)]
pub struct League {
#[ormx(default)]
pub id: i32,
pub name: String,
#[ormx(default)]
pub id: i32,
pub name: String,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Patch)]
#[ormx(table_name = "leagues", table = League, id = "id")]
pub struct NewLeague {
pub name: String,
pub name: String,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Table)]
#[ormx(table = "divisions", id = id, insertable, deletable)]
pub struct Division {
#[ormx(default)]
pub id: i32,
pub name: String,
#[ormx(get_many(i32))]
pub league: i32,
#[ormx(default)]
pub id: i32,
pub name: String,
#[ormx(get_many(i32))]
pub league: i32,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Patch)]
#[ormx(table_name = "divisions", table = Division, id = "id")]
pub struct NewDivision {
pub name: String,
pub league: i32,
pub name: String,
pub league: i32,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Table)]
#[ormx(table = "teams", id = id, insertable, deletable)]
pub struct Team {
#[ormx(default)]
pub id: i32,
pub name: String,
pub division: i32,
pub image: Option<String>,
#[ormx(default)]
pub id: i32,
pub name: String,
pub division: i32,
pub image: Option<String>,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Patch)]
#[ormx(table_name = "teams", table = Team, id = "id")]
pub struct NewTeam {
pub name: String,
pub division: i32,
pub name: String,
pub division: i32,
}
#[derive(FromRow, Serialize, Deserialize, Debug, ormx::Table)]
#[ormx(table = "players", id = id, insertable, deletable)]
pub struct Player {
#[ormx(default)]
pub id: i32,
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
#[ormx(default)]
pub id: i32,
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
}
impl Player {
pub async fn from_name_case_insensitive(pool: &sqlx::PgPool, name: String) -> Option<Player> {
sqlx::query_as::<_, Player>("SELECT * FROM players WHERE REPLACE(UPPER(name), ' ', '-') LIKE UPPER($1);")
.bind(name)
.fetch_optional(pool)
.await
.unwrap()
}
pub async fn from_name_case_insensitive(pool: &sqlx::PgPool, name: String) -> Option<Player> {
sqlx::query_as::<_, Player>(
"SELECT * FROM players WHERE REPLACE(UPPER(name), ' ', '-') LIKE UPPER($1);",
)
.bind(name)
.fetch_optional(pool)
.await
.unwrap()
}
}
#[derive(FromRow, Deserialize, Serialize, Debug, ormx::Patch)]
#[ormx(table_name = "players", table = Player, id = "id")]
pub struct NewPlayer {
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
}
#[derive(FromRow, Deserialize, Serialize, Debug, ormx::Table)]
#[ormx(table = "shots", id = id, insertable, deletable)]
pub struct Shot {
#[ormx(default)]
pub id: i32,
pub shooter: i32,
pub goalie: i32,
pub assistant: Option<i32>,
pub period: i32,
pub period_time: i32,
pub video_timestamp: Option<i32>,
pub blocker: Option<i32>,
pub on_net: bool,
pub assistant_second: Option<i32>,
pub goal: bool,
#[serde(with = "ts_seconds")]
pub created_at: DateTime<Utc>,
#[ormx(default)]
pub id: i32,
pub shooter: i32,
pub goalie: i32,
pub assistant: Option<i32>,
pub period: i32,
pub period_time: i32,
pub video_timestamp: Option<i32>,
pub blocker: Option<i32>,
pub on_net: bool,
pub assistant_second: Option<i32>,
pub goal: bool,
#[serde(with = "ts_seconds")]
pub created_at: DateTime<Utc>,
}
#[derive(FromRow, Deserialize, Serialize, Debug, ormx::Table)]
#[ormx(table = "game_players", id = id, insertable, deletable)]
pub struct GamePlayer {
#[ormx(default)]
pub id: i32,
pub team: i32,
pub player: i32,
pub position: i32,
pub game: i32,
#[ormx(default)]
pub id: i32,
pub team: i32,
pub player: i32,
pub position: i32,
pub game: i32,
}
#[derive(FromRow, Deserialize, Serialize, Debug, ormx::Table)]
#[ormx(table = "games", id = id, insertable, deletable)]
pub struct Game {
#[ormx(default)]
pub id: i32,
#[ormx(get_many(i32))]
pub division: i32,
pub name: String,
pub team_home: i32,
pub team_away: i32,
#[ormx(default)]
pub id: i32,
#[ormx(get_many(i32))]
pub division: i32,
pub name: String,
pub team_home: i32,
pub team_away: i32,
}
#[derive(FromRow, Deserialize, Serialize, Debug, ormx::Table)]
#[ormx(table = "periods", id = id, insertable, deletable)]
pub struct Period {
pub id: i32,
pub period_type: i32,
#[ormx(get_many(i32))]
pub game: i32,
pub id: i32,
pub period_type: i32,
#[ormx(get_many(i32))]
pub game: i32,
}
impl_table_name!(GamePlayer, "game_players");
@ -161,91 +163,87 @@ impl_table_name!(Language, "supported_languages");
#[cfg(test)]
mod tests {
use ormx::Table;
use std::env;
use crate::languages::SupportedLanguage;
use crate::model::{
Language,
GamePlayer,
Player,
League,
Division,
Team,
Shot,
TableName,
Game,
};
use strum::{
EnumCount,
IntoEnumIterator,
};
#[test]
fn db_languages_match_supported_langauges_enum() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let db_langs = Language::all(&pool).await.unwrap();
assert_eq!(db_langs.len(), SupportedLanguage::COUNT);
for lang_name in SupportedLanguage::iter() {
let found = db_langs.iter().find(|db_lang| db_lang.short_name == format!("{}", lang_name));
assert!(found.is_some(), "No database language found for variant {lang_name}");
assert_eq!(found.unwrap().short_name, lang_name.to_string());
}
});
}
#[test]
fn test_get_player_from_name() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::from_name_case_insensitive(&pool, "tait-hoyem".to_string()).await;
assert!(player.is_some());
let player = player.unwrap();
assert_eq!(player.name, "Tait Hoyem");
})
}
/// 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")
}
/// This macro generates a test that will `SELECT` all records for a table.
/// Then, it checks that
/// 1. The table rows gets deserialized correctly.
/// 2. There is at least one row.
macro_rules! generate_select_test {
($ret_type:ident, $func_name:ident) => {
#[test]
fn $func_name() {
use crate::languages::SupportedLanguage;
use crate::model::{
Division, Game, GamePlayer, Language, League, Player, Shot, TableName, Team,
};
use ormx::Table;
use std::env;
use strum::{EnumCount, IntoEnumIterator};
#[test]
fn db_languages_match_supported_langauges_enum() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let results = sqlx::query_as::<_, $ret_type>(
&format!(
"SELECT * FROM {};",
<$ret_type as TableName>::TABLE_NAME
)
)
.fetch_all(&pool)
.await
.unwrap();
// check that there is at least one result item
assert!(results.len() > 0, "There must be at least one result in the table.");
let pool = db_connect().await;
let db_langs = Language::all(&pool).await.unwrap();
assert_eq!(db_langs.len(), SupportedLanguage::COUNT);
for lang_name in SupportedLanguage::iter() {
let found = db_langs
.iter()
.find(|db_lang| db_lang.short_name == format!("{}", lang_name));
assert!(
found.is_some(),
"No database language found for variant {lang_name}"
);
assert_eq!(found.unwrap().short_name, lang_name.to_string());
}
});
}
}
}
generate_select_test!(GamePlayer, selec_game_player);
generate_select_test!(Player, select_player);
generate_select_test!(League, select_league);
generate_select_test!(Division, select_division);
generate_select_test!(Team, select_team);
generate_select_test!(Shot, select_shot);
generate_select_test!(Game, select_game);
generate_select_test!(Language, select_lang);
#[test]
fn test_get_player_from_name() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let player = Player::from_name_case_insensitive(&pool, "tait-hoyem".to_string()).await;
assert!(player.is_some());
let player = player.unwrap();
assert_eq!(player.name, "Tait Hoyem");
})
}
/// 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")
}
/// This macro generates a test that will `SELECT` all records for a table.
/// Then, it checks that
/// 1. The table rows gets deserialized correctly.
/// 2. There is at least one row.
macro_rules! generate_select_test {
($ret_type:ident, $func_name:ident) => {
#[test]
fn $func_name() {
tokio_test::block_on(async move {
let pool = db_connect().await;
let results = sqlx::query_as::<_, $ret_type>(&format!(
"SELECT * FROM {};",
<$ret_type as TableName>::TABLE_NAME
))
.fetch_all(&pool)
.await
.unwrap();
// check that there is at least one result item
assert!(
results.len() > 0,
"There must be at least one result in the table."
);
});
}
};
}
generate_select_test!(GamePlayer, selec_game_player);
generate_select_test!(Player, select_player);
generate_select_test!(League, select_league);
generate_select_test!(Division, select_division);
generate_select_test!(Team, select_team);
generate_select_test!(Shot, select_shot);
generate_select_test!(Game, select_game);
generate_select_test!(Language, select_lang);
}

@ -1,4 +1,4 @@
pub trait TemplateUrl {
const URL_KEY: &'static str;
const URL_KEY_TEMPLATE: &'static str;
const URL_KEY: &'static str;
const URL_KEY_TEMPLATE: &'static str;
}

@ -1,7 +1,7 @@
#![allow(dead_code)]
use crate::model::{Division, Game, League, Player};
use serde::{Serialize, Deserialize};
use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use sqlx::PgPool;
@ -35,18 +35,18 @@ pub struct IihfStatsI64 {
pub points: i64,
}
impl Into<IihfStatsI64> for IihfStats {
fn into(self) -> IihfStatsI64 {
IihfStatsI64 {
team_name: self.team_name.clone(),
team_id: self.team_id,
reg_wins: self.reg_wins.into(),
reg_losses: self.reg_losses.into(),
ot_wins: self.ot_wins.into(),
ot_losses: self.ot_losses.into(),
ties: self.ties.into(),
points: self.points.into(),
fn into(self) -> IihfStatsI64 {
IihfStatsI64 {
team_name: self.team_name.clone(),
team_id: self.team_id,
reg_wins: self.reg_wins.into(),
reg_losses: self.reg_losses.into(),
ot_wins: self.ot_wins.into(),
ot_losses: self.ot_losses.into(),
ties: self.ties.into(),
points: self.points.into(),
}
}
}
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
@ -257,29 +257,32 @@ ORDER BY
}
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 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#"
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,
@ -297,17 +300,17 @@ GROUP BY
teams.id
ORDER BY
points DESC;
"#
)
.bind(division_id)
.fetch_all(pool)
.await
"#,
)
.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
}
pub async fn iihf_stats(&self, pool: &PgPool) -> Result<Vec<IihfStatsI64>, sqlx::Error> {
division_iihf_stats(pool, self.id).await
}
}
impl Player {
@ -504,7 +507,10 @@ pub struct ShotDetails {
#[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 crate::views::{
division_iihf_stats, game_box_score, game_goals, game_iihf_points, game_iihf_stats,
game_play_by_play, game_score, get_player_stats_overview, Notification,
};
use ormx::Table;
use std::env;

Loading…
Cancel
Save