Initial commit; this should have been done way earlier

master
Tait Hoyem 1 year ago
commit ce45ef847e

@ -0,0 +1 @@
export DATABASE_URL="postgresql://ibihf2:ibihf@localhost/ibihf"

1
.gitignore vendored

@ -0,0 +1 @@
/target

1782
Cargo.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
[package]
name = "ibihf"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = { version = "0.6.12" }
axum-macros = "0.3.7"
chrono = { version = "0.4.24", features = ["serde"] }
serde = "1.0.158"
serde_plain = "1.0.1"
serde_tuple = "0.5.0"
sql-builder = "3.1.1"
sqlx = { version = "0.6.3", features = ["postgres", "runtime-tokio-rustls", "time", "chrono"] }
static_assertions = "1.1.0"
tokio = { version = "1.26.0", features = ["rt-multi-thread", "macros" ] }
[dev-dependencies]
tokio-test = "0.4.2"

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE leagues;

@ -0,0 +1,7 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS leagues (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL,
start_date TIMESTAMPTZ NOT NULL,
end_date TIMESTAMPTZ
);

@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS divisions (
id SERIAL PRIMARY KEY NOT NULL,
league INTEGER NOT NULL,
name VARCHAR(255) NOT NULL,
CONSTRAINT league_fk
FOREIGN KEY(league)
REFERENCES leagues(id)
ON DELETE RESTRICT
);

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS teams;

@ -0,0 +1,10 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS teams (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL,
division INTEGER NOT NULL,
CONSTRAINT division_fk
FOREIGN KEY(division)
REFERENCES divisions(id)
ON DELETE RESTRICT
);

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE players;

@ -0,0 +1,7 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS players (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(255) NOT NULL,
height_cm INTEGER,
weight_kg INTEGER
);

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE games;

@ -0,0 +1,17 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS games (
id SERIAL PRIMARY KEY NOT NULL,
-- a possibly null name for the game; this allows there to be special names like "Gold Medal Game"
name VARCHAR(255),
team_home INTEGER NOT NULL,
team_away INTEGER NOT NULL,
-- home and away teams need to actually be teams
CONSTRAINT team_home_fk
FOREIGN KEY(team_home)
REFERENCES teams(id)
ON DELETE RESTRICT,
CONSTRAINT team_away_fk
FOREIGN KEY(team_away)
REFERENCES teams(id)
ON DELETE RESTRICT
);

@ -0,0 +1,2 @@
-- Add down migration script here
DELETE FROM leagues WHERE id=1;

@ -0,0 +1,5 @@
-- Add up migration script here
INSERT INTO leagues
(id, name, start_date, end_date)
VALUES
(1, '2022 Canadian National Blind Hockey Tournament', '2022-03-24', '2022-03-26');

@ -0,0 +1,2 @@
DELETE FROM divisions
WHERE id BETWEEN 1 AND 3;

@ -0,0 +1,6 @@
INSERT INTO divisions
(id, name, league)
VALUES
(1, 'Low Vision & Development Division', 1),
(2, 'Open Division', 1),
(3, 'Children Division', 1);

@ -0,0 +1,2 @@
-- Add down migration script here
DELETE FROM teams WHERE id=1 OR id=2;

@ -0,0 +1,6 @@
-- Add up migration script here
INSERT INTO teams
(id, name, division)
VALUES
(1, 'Bullseye', 1),
(2, 'See Cats', 1);

@ -0,0 +1,3 @@
-- Add down migration script here
DELETE FROM players
WHERE id BETWEEN 1 AND 31;

@ -0,0 +1,35 @@
-- Add up migration script here
INSERT INTO players
(id, name)
VALUES
(1, 'Tait Hoyem'),
(2, 'Hillary Scanlon'),
(3, 'Nelson Rego'),
(4, 'Carrie Anton'),
(5, 'Salamaan Chaudhri'),
(6, 'Ben Ho Lung'),
(7, 'Brian MacLean'),
(8, 'Shannon Murphy'),
(9, 'Joseph Robinson'),
(10, 'Drexcyl Sison'),
(11, 'Ginny Sweet'),
(12, 'Catharine Lemay'),
(13, 'Thomas Stewart'),
(14, 'Maurice Clement-Lafrance'),
(15, 'Jennifer Fancy'),
(16, 'Allyssa Foulds'),
(17, 'Ryan Kucy'),
(18, 'Denis LeBlanc'),
(19, 'Bob Lowe'),
(20, 'Ted Moritsugu'),
(21, 'Dave Poidevin'),
(22, 'Jillian Stewart'),
(23, 'Laura Mark'),
(24, 'Matt Arnold'),
(25, 'Rory Kucy'),
(26, 'Jeff Stewart'),
(27, 'Dylan Brown'),
(28, 'Codi Isaac'),
(29, 'Richard Isaac'),
(30, 'Tyler McGuffin'),
(31, 'Scarlette Dorn');

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS positions;

@ -0,0 +1,9 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS positions (
id SERIAL PRIMARY KEY NOT NULL,
name VARCHAR(32) NOT NULL,
-- the short version, which should usually one character can be 2 charaters in some rare cases.
-- for example, in Goalball you'd have L, R, and C.
-- In hockey, you'd have C, D, LW and RW.
short_name VARCHAR(2) NOT NULL
);

@ -0,0 +1,3 @@
-- Add down migration script here
DELETE FROM positions
WHERE id BETWEEN 1 AND 7;

@ -0,0 +1,11 @@
-- Add up migration script here
INSERT INTO positions
(id, name, short_name)
VALUES
(1, 'Center', 'C'),
(2, 'Right-Wing', 'R'),
(3, 'Left-Wing', 'L'),
(4, 'Defence', 'D'),
(5, 'Goalie', 'G'),
(6, 'Head Coach', 'HC'),
(7, 'Assistant Coach', 'AC');

@ -0,0 +1 @@
DROP TABLE IF EXISTS periods;

@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS periods (
id SERIAL PRIMARY KEY NOT NULL,
-- "first", "second", "third", "second overtime", "shootout"
name VARCHAR(32) NOT NULL,
-- "1", "2", "3", "OT", "[2-9]OT", "SO"
-- technically 10+OT would not work, but this should be rare enough to not worry about.
short_name VARCHAR(3) NOT NULL
);

@ -0,0 +1,16 @@
INSERT INTO periods
(id, name, short_name)
VALUES
(1, 'first', '1'),
(2, 'second', '2'),
(3, 'third', '3'),
(4, 'overtime', 'OT'),
(5, 'shootout', 'SO'),
(6, 'second overtime', '2OT'),
(7, 'third overtime', '3OT'),
(8, 'fourth overtime', '4OT'),
(9, 'fifth overtime', '5OT'),
(10, 'sixth overtime', '6OT'),
(11, 'seventh overtime', '7OT'),
(12, 'eighth overtime', '8OT'),
(13, 'ninth overtime', '9OT');

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS team_players;

@ -0,0 +1,21 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS team_players (
id SERIAL PRIMARY KEY NOT NULL,
team INTEGER NOT NULL,
player INTEGER NOT NULL,
position INTEGER NOT NULL,
-- not a foreign key
player_number INTEGER NOT NULL,
CONSTRAINT team_fk
FOREIGN KEY(team)
REFERENCES teams(id)
ON DELETE RESTRICT,
CONSTRAINT player_fk
FOREIGN KEY(player)
REFERENCES players(id)
ON DELETE RESTRICT,
CONSTRAINT position_fk
FOREIGN KEY(position)
REFERENCES positions(id)
ON DELETE RESTRICT
);

@ -0,0 +1,2 @@
-- Add down migration script here
DELETE FROM team_players;

@ -0,0 +1,178 @@
-- Add up migration script here
INSERT INTO team_players
(team, player, position, player_number)
VALUES
(
1,
31,
1,
11
),
(
1,
1,
3,
3
),
(
1,
2,
4,
8
),
(
1,
3,
5,
1
),
(
1,
4,
2,
14
),
(
1,
5,
4,
91
),
(
1,
7,
1,
15
),
(
1,
8,
4,
10
),
(
1,
9,
3,
13
),
(
1,
10,
1,
10
),
(
1,
11,
2,
84
),
(
2,
12,
5,
35
),
(
2,
13,
5,
30
),
(
2,
14,
1,
15
),
(
2,
15,
2,
17
),
(
2,
16,
1,
3
),
(
2,
17,
2,
9
),
(
2,
18,
4,
16
),
(
2,
19,
4,
4
),
(
2,
21,
4,
14
),
(
2,
22,
4,
12
),
(
2,
23,
2,
10
),
(
2,
24,
7,
0
),
(
2,
25,
7,
0
),
(
2,
26,
7,
0
),
(
1,
27,
7,
0
),
(
1,
28,
7,
0
),
(
1,
29,
7,
0
),
(
1,
30,
7,
0
);

@ -0,0 +1,2 @@
DELETE FROM games
WHERE id BETWEEN 1 AND 4;

@ -0,0 +1,27 @@
INSERT INTO games
(id, name, team_home, team_away)
VALUES
(
1,
'Game 1',
1, -- Bullseye
2 -- Seecats
),
(
2,
'Game 2',
1, -- Bullseye
2 -- Seecats
),
(
3,
'Game 3',
1, -- Bullseye
2 -- Seecats
),
(
4,
'Game 4',
1, -- Bullseye
2 -- Seecats
);

@ -0,0 +1,2 @@
-- Add down migration script here
DROP TABLE IF EXISTS shots;

@ -0,0 +1,68 @@
-- Add up migration script here
CREATE TABLE IF NOT EXISTS shots (
id SERIAL PRIMARY KEY NOT NULL,
-- video timestampt if known; seconds offset from beginning of video
video_timestamp INTEGER,
-- player that blocked the shot, if applicable
blocker INTEGER,
-- on net; did it go towards the goalie (this does not say whether it went in or not)
on_net BOOLEAN NOT NULL,
-- did the puck go in?
goal BOOLEAN NOT NULL,
-- what team was the shooter on
shooter_team INTEGER NOT NULL,
-- which player is the shooter
shooter INTEGER NOT NULL,
-- which player was the goalie
goalie INTEGER NOT NULL,
-- which game was this a part of
game INTEGER NOT NULL,
-- which period did the shot happen in
period INTEGER NOT NULL,
-- when did the shot happen relative to the beginning of the period
period_time INTEGER NOT NULL,
-- if applicable, set assistant(s)
assistant INTEGER,
assistant_second INTEGER,
-- was the shooter a real player
CONSTRAINT shooter_fk
FOREIGN KEY(shooter)
REFERENCES players(id)
ON DELETE RESTRICT,
-- was the assistant is a real player
CONSTRAINT assistant_fk
FOREIGN KEY(assistant)
REFERENCES players(id)
ON DELETE RESTRICT,
-- was the second assistant a real player
CONSTRAINT assistant_second_fk
FOREIGN KEY(assistant_second)
REFERENCES players(id)
ON DELETE RESTRICT,
-- was the goalie a real player
CONSTRAINT goalie_fk
FOREIGN KEY(goalie)
REFERENCES players(id)
ON DELETE RESTRICT,
-- was the (optional) blocker a real player
CONSTRAINT blocker_fk
FOREIGN KEY(blocker)
REFERENCES players(id)
ON DELETE RESTRICT,
-- was the shooter's team a real team
CONSTRAINT shooter_team_fk
FOREIGN KEY(shooter_team)
REFERENCES teams(id)
ON DELETE RESTRICT,
-- is the game a real game
CONSTRAINT game_fk
FOREIGN KEY(game)
REFERENCES games(id)
ON DELETE RESTRICT,
-- is the period refgerences a real period type
CONSTRAINT period_fk
FOREIGN KEY(period)
REFERENCES periods(id)
ON DELETE RESTRICT
);

@ -0,0 +1,2 @@
-- Add down migration script here
DELETE FROM shots;

@ -0,0 +1,186 @@
-- Add up migration script here
INSERT INTO shots
(shooter_team, goalie, shooter, game, period, period_time, video_timestamp, assistant, assistant_second, on_net, goal)
VALUES
(
2,
3,
14,
1,
3,
503,
null,
16,
null,
true,
true
),
(
1,
12,
7,
1,
3,
26,
null,
1,
null,
true,
true
),
(
1,
13,
31,
2,
2,
1186,
1649,
2,
null,
true,
true
),
(
2,
3,
22,
3,
1,
657,
1268,
21,
null,
true,
true
),
(
1,
13,
2,
3,
1,
565,
1360,
7,
null,
true,
true
),
(
1,
12,
1,
3,
3,
282,
3918,
1,
null,
true,
true
),
(
2,
3,
18,
4,
1,
691,
663,
1,
null,
true,
true
),
(
1,
12,
2,
4,
1,
106,
1247,
31,
null,
true,
true
),
(
1,
13,
2,
4,
2,
883,
1862,
7,
null,
true,
true
),
(
1,
13,
2,
4,
2,
613,
2132,
1,
null,
true,
true
),
(
1,
13,
2,
4,
2,
514,
2231,
31,
null,
true,
true
),
(
2,
3,
14,
4,
3,
1193,
2934,
16,
null,
true,
true
),
(
2,
3,
16,
4,
3,
972,
3154,
14,
null,
true,
true
),
(
2,
3,
22,
4,
3,
16,
4232,
16,
null,
true,
true
);

@ -0,0 +1,2 @@
-- Add down migration script here
DROP FUNCTION IF EXISTS player_stats_overview_all;

@ -0,0 +1,29 @@
-- Add up migration script here
CREATE OR REPLACE FUNCTION player_stats_overview_all() RETURNS VOID
LANGUAGE SQL
AS $$
SELECT
(
SELECT COUNT(id)
FROM shots
WHERE shooter=players.id
AND goal=true
) AS goals,
(
SELECT COUNT(id)
FROM shots
WHERE assistant=players.id
AND goal=true
) AS assists,
(
SELECT COUNT(id)
FROM shots
WHERE assistant=players.id
OR shooter=players.id
) AS points,
players.name AS player_name
FROM players
ORDER BY
points DESC,
players.name;
$$;

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

@ -0,0 +1,171 @@
mod db;
mod model;
use crate::model::{
TableName,
League,
Team,
Division,
TeamPlayer,
Player,
Shot,
};
use sqlx::{
Postgres,
Pool,
};
use axum::{
Router,
http::StatusCode,
extract::{
Path,
State,
},
response::{
Json,
IntoResponse,
},
routing::get,
};
use axum_macros::debug_handler;
use std::net::SocketAddr;
use std::sync::Arc;
#[derive(Clone)]
pub struct ServerState {
db_pool: Arc<Pool<Postgres>>,
}
#[tokio::main]
async fn main() {
let pool = db::connect().await;
let state = ServerState {
db_pool: Arc::new(pool),
};
let router = Router::new()
.route("/league/", get(league_all))
.route("/league/:id", get(league_id))
.route("/division/", get(division_all))
.route("/division/:id", get(division_id))
.route("/team/", get(team_all))
.route("/team/:id", get(team_id))
.route("/player/", get(player_all))
.route("/player/:id", get(player_id))
.route("/team-player/", get(team_player_all))
.route("/team-playerplayer/:id", get(team_player_id))
.route("/shot/", get(shots_all))
.route("/shot/:id", get(shots_id))
.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 get_all<T: Send + Unpin + TableName + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>>(pool: &sqlx::PgPool) -> Result<Vec<T>, sqlx::Error> {
sqlx::query_as::<_, T>(
&format!("SELECT * FROM {};", <T as TableName>::TABLE_NAME)
)
.fetch_all(pool)
.await
}
async fn get_by_id<T: Send + Unpin + TableName + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>>(pool: &sqlx::PgPool, id: i32) -> Result<Option<T>, sqlx::Error> {
sqlx::query_as::<_, T>(
&format!("SELECT * FROM {} WHERE id = $1;", <T as TableName>::TABLE_NAME)
)
.bind(id)
.fetch_optional(pool)
.await
}
/*
async fn insert_into<T: Sync + Send + Unpin + TableName + for<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow>>(pool: &sqlx::PgPool, new: &T) -> Result<sqlx::postgres::PgQueryResult, sqlx::Error> {
let query = sql_builder::SqlBuilder::insert_into(<T as TableName>::TABLE_NAME)
.values(())
.sql().unwrap();
sqlx::query(
&query
)
.execute(pool)
.await
}
*/
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 = get_all::<$crud_struct>(&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 = get_by_id::<$crud_struct>(&server_config.db_pool, id)
.await
.unwrap();
(StatusCode::OK, Json(cruder))
}
}
}
/*
macro_rules! insert {
($crud_struct:ident, $func_name:ident) => {
#[debug_handler]
async fn $func_name(State(server_config): State<ServerState>, Json(NewPlayer): Json<NewPlayer>) -> impl IntoResponse {
let cruder = get_by_id::<$crud_struct>(&server_config.db_pool, id)
.await
.unwrap();
(StatusCode::OK, Json(cruder))
}
}
}
*/
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);
}
}
impl_all_query_types!(
TeamPlayer,
team_player_all,
team_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
);

@ -0,0 +1,261 @@
use sqlx::FromRow;
use sqlx::PgPool;
use sqlx::types::chrono::{DateTime, Utc};
use chrono::serde::{ts_seconds, ts_seconds_option};
use serde::{Serialize, Deserialize};
pub trait TableName {
const TABLE_NAME: &'static str;
}
macro_rules! impl_table_name {
($ty:ident, $tname:expr) => {
impl TableName for $ty {
const TABLE_NAME: &'static str = $tname;
}
}
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct League {
pub id: i32,
pub name: String,
#[serde(with = "ts_seconds")]
pub start_date: DateTime<Utc>,
#[serde(with = "ts_seconds_option")]
pub end_date: Option<DateTime<Utc>>,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct NewLeague {
pub name: String,
#[serde(with = "ts_seconds")]
pub start_date: DateTime<Utc>,
#[serde(with = "ts_seconds_option")]
pub end_date: Option<DateTime<Utc>>,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct Division {
pub id: i32,
pub name: String,
pub league: i32,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct NewDivision {
pub id: i32,
pub name: String,
pub league: i32,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct Team {
pub id: i32,
pub name: String,
pub division: i32,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct NewTeam {
pub name: String,
pub division: i32,
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct Player {
pub id: i32,
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
}
#[derive(FromRow, Deserialize, Serialize)]
pub struct NewPlayer {
pub name: String,
pub weight_kg: Option<i32>,
pub height_cm: Option<i32>,
}
#[derive(FromRow, Deserialize, Serialize)]
pub struct Shot {
pub id: i32,
pub shooter_team: i32,
pub goalie: i32,
pub assistant: Option<i32>,
pub game: i32,
pub period: i32,
pub period_time: i32,
pub video_timestamp: Option<i32>,
}
#[derive(FromRow, Deserialize, Serialize)]
pub struct TeamPlayer {
pub id: i32,
pub team: i32,
pub player: i32,
pub position: 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,
}
#[derive(FromRow, Deserialize, Serialize, Debug)]
pub struct PlayerStats {
pub player_name: String,
pub goals: i64,
pub assists: i64,
pub points: i64,
}
async fn get_player_stats_overview(pool: PgPool) -> Result<Vec<PlayerStats>, sqlx::Error> {
let query = r#"
SELECT
(
SELECT COUNT(id)
FROM shots
WHERE shooter=players.id
AND goal=true
) AS goals,
(
SELECT COUNT(id)
FROM shots
WHERE assistant=players.id
AND goal=true
) AS assists,
(
SELECT COUNT(id)
FROM shots
WHERE assistant=players.id
OR shooter=players.id
) AS points,
players.name AS player_name
FROM players
ORDER BY
points DESC,
players.name;
"#;
let result = sqlx::query_as::<_, PlayerStats>(query)
.fetch_all(&pool)
.await;
result
}
impl_table_name!(TeamPlayer, "team_players");
impl_table_name!(Player, "players");
impl_table_name!(League, "leagues");
impl_table_name!(Division, "divisions");
impl_table_name!(Team, "teams");
impl_table_name!(Shot, "shots");
#[cfg(test)]
mod tests {
use std::env;
use crate::model::{
TeamPlayer,
Player,
League,
Division,
Team,
Shot,
TableName,
Notification,
get_player_stats_overview,
};
#[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_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,
positions.name AS position,
team_players.player_number AS scorer_number,
shots.period_time AS period_time_left,
periods.name AS period_name
FROM
shots
JOIN teams ON teams.id=shots.shooter_team
JOIN players ON players.id=shots.shooter
JOIN team_players ON team_players.player=players.id AND team_players.team=teams.id
JOIN periods ON periods.id=shots.period
JOIN positions ON positions.id=team_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
);
});
}
/// 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!(TeamPlayer, select_team_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);
}
Loading…
Cancel
Save