commit
ce45ef847e
@ -0,0 +1 @@
|
|||||||
|
export DATABASE_URL="postgresql://ibihf2:ibihf@localhost/ibihf"
|
@ -0,0 +1 @@
|
|||||||
|
/target
|
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 @@
|
|||||||
|
DROP TABLE divisions;
|
@ -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 @@
|
|||||||
|
DELETE FROM periods;
|
@ -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…
Reference in new issue