diff --git a/lucyleague-frontend b/lucyleague-frontend index d97a90e..04865d6 160000 --- a/lucyleague-frontend +++ b/lucyleague-frontend @@ -1 +1 @@ -Subproject commit d97a90e1499fa090c0d5e4275c62b8cc2ca5d5e8 +Subproject commit 04865d630ba5b3bc4d5c02c1a5b89193b1b60324 diff --git a/sql/initdb.sql b/sql/initdb.sql index fac4477..e9f6183 100644 --- a/sql/initdb.sql +++ b/sql/initdb.sql @@ -5,7 +5,6 @@ CREATE TABLE IF NOT EXISTS users ( avatarurl VARCHAR(200) NOT NULL, permissions BIGINT DEFAULT 0 NOT NULL, created_at TIMESTAMPTZ NOT NULL, - UNIQUE (username), UNIQUE (steamid) ); @@ -17,37 +16,70 @@ CREATE TABLE IF NOT EXISTS leagues ( created_at TIMESTAMPTZ NOT NULL ); +CREATE TABLE IF NOT EXISTS divisions ( + id BIGSERIAL PRIMARY KEY, + leagueid BIGSERIAL NOT NULL, + prio INT NOT NULL, + name VARCHAR(50) NOT NULL, + created_at TIMESTAMPTZ NOT NULL, + CONSTRAINT FK_divisions_league FOREIGN KEY (leagueid) references leagues(id) +); + +CREATE TABLE IF NOT EXISTS division_admins ( + id BIGSERIAL PRIMARY KEY, + divisionid BIGSERIAL NOT NULL, + userid BIGSERIAL NOT NULL, + relation VARCHAR(50) NOT NULL DEFAULT 'Admin', + CONSTRAINT FK_division_admins_user FOREIGN KEY (userid) references users(id), + CONSTRAINT FK_division_admins_division FOREIGN KEY (divisionid) references divisions(id) +); + CREATE TABLE IF NOT EXISTS teams ( id BIGSERIAL PRIMARY KEY, - leagueid BIGSERIAL NOT NULL, team_tag VARCHAR(6) NOT NULL DEFAULT 'Tag', team_name VARCHAR(200) NOT NULL DEFAULT 'Unnamed', + created_at TIMESTAMPTZ NOT NULL +); + +-- A team can be in multiple leagues at the same time, and have different rosters, while having the same (base) team. +CREATE TABLE IF NOT EXISTS teamDivAssociations ( + id BIGSERIAL PRIMARY KEY, + roster_name VARCHAR(50), + teamid BIGSERIAL NOT NULL, + divisionid BIGSERIAL NOT NULL, + points_up BIGINT NOT NULL DEFAULT 0, + points_down BIGINT NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL, - CONSTRAINT FK_teams_league FOREIGN KEY (leagueid) references leagues(id) + CONSTRAINT FK_teamDivAssociation_team FOREIGN KEY (teamid) references teams(id), + CONSTRAINT FK_teamDivAssociation_division FOREIGN KEY (divisionid) references divisions(id) ); -CREATE TABLE IF NOT EXISTS userTeam ( - leagueid BIGSERIAL NOT NULL, +CREATE TABLE IF NOT EXISTS userTeamAssociation ( + id BIGSERIAL PRIMARY KEY, userid BIGSERIAL NOT NULL, - teamid BIGSERIAL NOT NULL, + teamdivid BIGSERIAL NOT NULL, created_at TIMESTAMPTZ NOT NULL, - CONSTRAINT FK_userTeam_league FOREIGN KEY (leagueid) references leagues(id), - CONSTRAINT FK_userTeam_user FOREIGN KEY (userid) references users(id), - CONSTRAINT FK_userTeam_team FOREIGN KEY (teamid) references teams(id) + ended_at TIMESTAMPTZ, + is_leader BOOLEAN NOT NULL, + CONSTRAINT FK_userTeamAssociation_teamdivid FOREIGN KEY (teamdivid) references teamDivAssociations(id), + CONSTRAINT FK_userTeamAssociation_user FOREIGN KEY (userid) references users(id) ); CREATE TABLE IF NOT EXISTS games ( - id BIGSERIAL NOT NULL, + id BIGSERIAL PRIMARY KEY, title VARCHAR(50), + leagueid BIGSERIAL NOT NULL, teamhomeid BIGSERIAL NOT NULL, teamawayid BIGSERIAL NOT NULL, created_at TIMESTAMPTZ NOT NULL, played_at TIMESTAMPTZ NOT NULL, + CONSTRAINT FK_game_league FOREIGN KEY (leagueid) references leagues(id), CONSTRAINT FK_game_home FOREIGN KEY (teamhomeid) references teams(id), CONSTRAINT FK_game_away FOREIGN KEY (teamawayid) references teams(id) ); CREATE TABLE IF NOT EXISTS authorizations ( + id BIGSERIAL PRIMARY KEY, userid BIGSERIAL NOT NULL, token VARCHAR(50) NOT NULL, created_at TIMESTAMPTZ NOT NULL, @@ -56,12 +88,11 @@ CREATE TABLE IF NOT EXISTS authorizations ( ); CREATE TABLE IF NOT EXISTS team_invites ( - leagueid BIGSERIAL NOT NULL, - teamid BIGSERIAL NOT NULL, + id BIGSERIAL PRIMARY KEY, + teamdivid BIGSERIAL NOT NULL, to_userid BIGSERIAL NOT NULL, from_userid BIGSERIAL NOT NULL, - CONSTRAINT FK_team_invites_leagueid FOREIGN KEY (leagueid) references leagues(id), - CONSTRAINT FK_team_invites_teamid FOREIGN KEY (teamid) references teams(id), + CONSTRAINT FK_team_invites_teamdivid FOREIGN KEY (teamdivid) references teamDivAssociations(id), CONSTRAINT FK_team_invites_to_userid FOREIGN KEY (to_userid) references users(id), CONSTRAINT FK_team_invites_from_userid FOREIGN KEY (from_userid) references users(id) ); diff --git a/src/apiv1/admin/mod.rs b/src/apiv1/admin/mod.rs index c127908..93df20f 100644 --- a/src/apiv1/admin/mod.rs +++ b/src/apiv1/admin/mod.rs @@ -96,6 +96,43 @@ pub async fn post_league( Ok(HttpResponse::Created().json(response)) } +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct MiniDivision { + pub leagueid: i64, + pub name: String, +} + +#[post("/api/v1/admin/divisions")] +pub async fn post_league_divisions( + division: web::Json, + state: web::Data, + auth: web::Header, +) -> Result { + log::debug!("POST request at /api/v1/divisions"); + log::debug!("Authorization header: {0}", auth.0 .0); + + log::trace!("Grabbing pool"); + let client = crate::grab_pool(&state).await?; + + let user = match db::get_user_from_auth_token(&client, &auth.0 .0).await { + Ok(user) => user, + Err(_) => return Ok(HttpResponse::Unauthorized().body("Error processing permissions")), + }; + + // if not admin / can't create div + if !user.admin_or_perm(UserPermission::CreateLeague) { + return Ok(HttpResponse::Forbidden().body("Insufficient permissions")); + } + + // Actually create the new div + log::info!("Authorization succeeded, creating a new division"); + let division = division.into_inner(); + log::debug!("Adding division: {0:?}", division); + let response = db::divisions::add_division(&client, division).await?; + log::trace!("OK response, {response:?}"); + + Ok(HttpResponse::Created().json(response)) +} /// Set a user or multiple users to a team. #[post("/api/v1/admin/setuserteam")] pub async fn post_users_team( @@ -107,6 +144,6 @@ pub async fn post_users_team( let user_team = user_team.into_inner(); // fetch the team to get its id let team = db::get_team_from_id(&client, user_team.team_id).await?; - let users = db::get_team_players(&client, &team).await?; - Ok(HttpResponse::Ok().json(users)) + todo!(); + Ok(HttpResponse::Ok().finish()) } diff --git a/src/apiv1/apimodels.rs b/src/apiv1/apimodels.rs index 8506b10..87c2025 100644 --- a/src/apiv1/apimodels.rs +++ b/src/apiv1/apimodels.rs @@ -14,8 +14,3 @@ pub struct UserResponse { pub user: User, pub teams: Vec, } -#[derive(Serialize, Deserialize)] -pub struct LeagueResponse { - pub info: League, - pub teams: Vec, -} diff --git a/src/apiv1/leagues.rs b/src/apiv1/leagues.rs index a4ba494..5942742 100644 --- a/src/apiv1/leagues.rs +++ b/src/apiv1/leagues.rs @@ -1,20 +1,64 @@ use crate::db; +use crate::db::divisions::DeepTeamDivAssociation; use crate::errors::MyError; +use actix_web::body::MessageBody; use actix_web::{get, web, Error, HttpResponse}; use deadpool_postgres::Client; +use serde::{Deserialize, Serialize}; -use crate::apiv1::LeagueResponse; -use crate::models::League; +use crate::apiv1::grab_pool; +use crate::models::{Division, League, Team, TeamDivAssociation, WrappedDivisionAdmin}; use crate::AppState; #[get("/api/v1/leagues")] async fn get_all_leagues(state: web::Data) -> Result { log::info!("GET request at /api/v1/leagues"); - let client = state.pool.get().await.map_err(MyError::PoolError)?; + let client = grab_pool(&state).await?; - let leagues: Vec = db::get_leagues(&client).await?; + let leagues: Vec = db::leagues::get_leagues(&client).await?; - Ok(HttpResponse::Ok().json(leagues)) + let mut league_responses: Vec = Vec::with_capacity(leagues.len()); + + for league in leagues { + let league_divs: Vec = + db::leagues::get_divs_for_league_id(&client, league.id).await?; + + let mut divisions: Vec = Vec::with_capacity(league_divs.len()); + + for div in league_divs { + let admins = db::divisions::get_admins_for_div_id_wrapped(&client, div.id).await?; + divisions.push(DivisionOptionalTeams { + info: div, + admins, + teams: None, + }) + } + league_responses.push(LeagueReturn { + divisions, + info: league, + }) + } + + Ok(HttpResponse::Ok().json(league_responses)) +} + +#[derive(Serialize, Deserialize)] +struct AdminInfo { + inner_user_id: i64, + relation: String, +} + +#[derive(Serialize, Deserialize)] +struct DivisionOptionalTeams { + info: Division, + admins: Vec, + teams: Option>, +} + +#[derive(Serialize, Deserialize)] +struct LeagueReturn { + info: League, + divisions: Vec, } #[get("/api/v1/leagues/{league_id}")] @@ -23,15 +67,28 @@ pub async fn get_league( league_id: web::Path, ) -> Result { log::debug!("GET request at /api/v1/leagues/league_id"); - let client: Client = state.pool.get().await.map_err(MyError::PoolError)?; - let league_info = db::get_league_from_id(&client, *league_id).await?; + let client: Client = grab_pool(&state).await?; + + let league_info = db::leagues::get_league_from_id(&client, *league_id).await?; + + let league_divs: Vec = + db::leagues::get_divs_for_league_id(&client, *league_id).await?; - let teams = db::get_teams_with_leagueid(&client, *league_id).await?; + let mut divisions: Vec = Vec::with_capacity(league_divs.len()); + for div in league_divs { + let admins = db::divisions::get_admins_for_div_id_wrapped(&client, div.id).await?; + let teams = db::divisions::get_teams_for_div_id(&client, div.id).await?; + divisions.push(DivisionOptionalTeams { + info: div, + admins, + teams: Some(teams), + }) + } - let results = LeagueResponse { + let resp = LeagueReturn { info: league_info, - teams, + divisions, }; - Ok(HttpResponse::Ok().json(results)) + Ok(HttpResponse::Ok().json(resp)) } diff --git a/src/apiv1/mod.rs b/src/apiv1/mod.rs index abf7018..50146b3 100644 --- a/src/apiv1/mod.rs +++ b/src/apiv1/mod.rs @@ -59,7 +59,7 @@ pub async fn logout( body: web::Json, state: web::Data, ) -> Result { - let client = state.pool.get().await.unwrap(); + let client = grab_pool(&state).await?; let user = match db::get_user_from_auth_token(&client, &body.auth_token).await { Ok(user) => user, Err(err) => return Ok(HttpResponse::BadRequest().body(format!("{err:?}"))), @@ -123,6 +123,10 @@ struct Token { pub expires: DateTime, } +pub async fn grab_pool(state: &AppState) -> Result { + state.pool.get().await.map_err(MyError::PoolError) +} + #[post("/api/v1/verifylogin")] pub async fn verify_openid_login( body: web::Json, @@ -158,7 +162,7 @@ pub async fn verify_openid_login( // let openid_sig = inner.get("openid.sig").expect("No openid.sig on request"); let steamid = openid_identity.replace("https://steamcommunity.com/openid/id/", ""); log::info!("Openid landing received from steamid: {steamid}"); - let client: Client = state.pool.get().await.map_err(MyError::PoolError)?; + let client: Client = grab_pool(&state).await?; let auth = match db::get_user_from_steamid(&client, &steamid).await { // there is a user corresponding @@ -202,7 +206,7 @@ pub async fn verify_openid_login( #[derive(Serialize, Deserialize)] struct TeamResponse { pub id: i64, - pub leagueid: i64, + pub divisionid: i64, pub team_name: String, pub players: Vec, } diff --git a/src/apiv1/teams.rs b/src/apiv1/teams.rs index 6fcd624..922087c 100644 --- a/src/apiv1/teams.rs +++ b/src/apiv1/teams.rs @@ -2,6 +2,7 @@ use crate::db; use crate::errors::MyError; use crate::models::League; use crate::models::MiniTeam; +use crate::models::TeamDivAssociation; use crate::models::User; use crate::steamapi; use crate::CurrentHost; @@ -13,23 +14,28 @@ use deadpool_postgres::{Client, Pool}; use crate::apiv1::TeamResponse; use crate::AppState; +// this will be retroactively changed to be for a teamDivAssociation and not a root team +// maybe /rootteam/{team_id}? #[get("/api/v1/teams/{team_id}")] async fn get_team(state: web::Data, path: web::Path) -> Result { log::info!("GET request at /api/v1/teams/{path}"); - let team_id = path.into_inner(); - if team_id < 0 { + let team_div_assoc_id = path.into_inner(); + if team_div_assoc_id < 0 { return Err(MyError::NotFound.into()); } let client = state.pool.get().await.map_err(MyError::PoolError)?; - let team = db::get_team_from_id(&client, team_id).await?; - let players = db::get_team_players(&client, &team).await?; + let team_div_assoc: TeamDivAssociation = + db::get_teamdivassociation_from_id(&client, team_div_assoc_id).await?; + let team = db::get_team_from_id(&client, team_div_assoc.teamid).await?; + + let players = db::get_team_players(&client, &team_div_assoc).await?; let resp = TeamResponse { id: team.id, - leagueid: team.leagueid, + divisionid: team_div_assoc.divisionid, team_name: team.team_name, players, }; - Ok(HttpResponse::Created().json(resp)) + Ok(HttpResponse::Ok().json(resp)) } #[post("/api/v1/teams")] async fn post_team( @@ -40,7 +46,7 @@ async fn post_team( let team = new_team.into_inner(); let client = state.pool.get().await.map_err(MyError::PoolError)?; let leagueid = team.leagueid; - let league = match db::get_league_from_id(&client, leagueid).await { + let league = match db::leagues::get_league_from_id(&client, leagueid).await { Ok(league) => league, Err(_) => return Ok(HttpResponse::NotFound().body("League not found with id ${leagueid}")), }; diff --git a/src/db/divisions.rs b/src/db/divisions.rs new file mode 100644 index 0000000..b724b68 --- /dev/null +++ b/src/db/divisions.rs @@ -0,0 +1,116 @@ +use deadpool_postgres::Client; +use derive_more::derive::Debug; +use serde::{Deserialize, Serialize}; +use tokio_pg_mapper::FromTokioPostgresRow; + +use crate::{ + admin::MiniDivision, + errors::MyError, + models::{Division, DivisionAdmin, Team, TeamDivAssociation, WrappedDivisionAdmin}, +}; + +pub async fn add_division(client: &Client, division: MiniDivision) -> Result { + let _stmt = "INSERT INTO divisions(leagueid, name, prio, created_at) VALUES ($1, $2, 1, $3) RETURNING $table_fields" + .replace("$table_fields", &Division::sql_table_fields()); + + let stmt = client.prepare(&_stmt).await?; + + let row = client + .query_one( + &stmt, + &[ + &division.leagueid, + &division.name, + &chrono::offset::Utc::now(), + ], + ) + .await?; + + Ok(Division::from_row(row).unwrap()) +} +pub async fn get_admins_for_div_id_wrapped( + client: &Client, + divisionid: i64, +) -> Result, MyError> { + let div_admins = get_admins_for_div_id(client, divisionid).await?; + // TODO: optimize this with a join + + let stmt = client + .prepare("SELECT users.username , users.avatarurl FROM users WHERE id=$1") + .await?; + + let mut ret: Vec = Vec::new(); + for admin in div_admins.into_iter() { + let row = client.query_one(&stmt, &[&admin.id]).await?; + + ret.push(WrappedDivisionAdmin { + inner: admin, + username: row.get("users.username"), + avatarurl: row.get("users.avatarurl"), + }) + } + Ok(ret) +} +pub async fn get_admins_for_div_id( + client: &Client, + divisionid: i64, +) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM division_admins WHERE divisionid=$1"; + let _stmt = _stmt.replace("$table_fields", &DivisionAdmin::sql_table_fields()); + let stmt = client.prepare(&_stmt).await?; + + let results = client + .query(&stmt, &[&divisionid]) + .await? + .iter() + .map(DivisionAdmin::from_row_ref) + .map(Result::unwrap) + .collect(); + + Ok(results) +} + +pub async fn get_teamassociations_for_div_id( + client: &Client, + divisionid: i64, +) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM teamDivAssociations WHERE divisionid=$1"; + let _stmt = _stmt.replace("$table_fields", &TeamDivAssociation::sql_table_fields()); + let stmt = client.prepare(&_stmt).await?; + + let results = client + .query(&stmt, &[&divisionid]) + .await? + .iter() + .map(TeamDivAssociation::from_row_ref) + .map(Result::unwrap) + .collect(); + + Ok(results) +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct DeepTeamDivAssociation { + pub team_info: Team, + pub association_info: TeamDivAssociation, +} +pub async fn get_teams_for_div_id( + client: &Client, + divisionid: i64, +) -> Result, MyError> { + let assocs = get_teamassociations_for_div_id(client, divisionid).await?; + let _stmt = "SELECT $table_fields FROM teams WHERE id=$1" + .replace("$table_fields", &Team::sql_table_fields()); + let stmt = client.prepare(&_stmt).await?; + + let mut ret: Vec = Vec::with_capacity(assocs.len()); + + for assoc in assocs.into_iter() { + let row = client.query_one(&stmt, &[&assoc.id]).await?; + ret.push(DeepTeamDivAssociation { + team_info: Team::from_row(row).unwrap(), + association_info: assoc, + }) + } + Ok(ret) +} diff --git a/src/db/leagues.rs b/src/db/leagues.rs new file mode 100644 index 0000000..7cbeab1 --- /dev/null +++ b/src/db/leagues.rs @@ -0,0 +1,78 @@ +use chrono::{DateTime, Utc}; +use deadpool_postgres::Client; +use tokio_pg_mapper::FromTokioPostgresRow; + +use crate::{ + errors::MyError, + models::{ + Authorization, Division, League, MiniLeague, MiniTeam, MiniUser, Team, TeamDivAssociation, + }, +}; + +pub async fn get_teams_with_divisionid( + client: &Client, + divisionid: i64, +) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM teamDivAssociations WHERE divisionid=$1"; + let _stmt = _stmt.replace("$table_fields", &Team::sql_table_fields()); + let stmt = client.prepare(&_stmt).await.unwrap(); + + let results = client + .query(&stmt, &[&divisionid]) + .await? + .iter() + .map(|row| TeamDivAssociation::from_row_ref(row).unwrap()) + .collect::>(); + Ok(results) +} + +pub async fn get_divs_for_league_id( + client: &Client, + leagueid: i64, +) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM divisions WHERE leagueid=$1;"; + let _stmt = _stmt.replace("$table_fields", &Division::sql_table_fields()); + let stmt = client.prepare(&_stmt).await.unwrap(); + + let results = client + .query(&stmt, &[&leagueid]) + .await? + .iter() + .map(|row| Division::from_row_ref(row).unwrap()) + .collect::>(); + + Ok(results) +} + +pub async fn get_leagues(client: &Client) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM leagues;"; + let _stmt = _stmt.replace("$table_fields", &League::sql_table_fields()); + let stmt = client.prepare(&_stmt).await.unwrap(); + + let results = client + .query(&stmt, &[]) + .await? + .iter() + .map(|row| League::from_row_ref(row).unwrap()) + .collect::>(); + Ok(results) +} + +pub async fn get_league_from_id(client: &Client, leagueid: i64) -> Result { + log::debug!("Getting league {leagueid}"); + let _stmt = "SELECT $table_fields FROM leagues WHERE id=$1;"; + let _stmt = _stmt.replace("$table_fields", &League::sql_table_fields()); + log::debug!("Running this statement: {0}", _stmt); + let stmt = client.prepare(&_stmt).await.unwrap(); + + let results = client + .query(&stmt, &[&leagueid]) + .await? + .iter() + .map(|row| League::from_row_ref(row).unwrap()) + .collect::>() + .pop() + .ok_or(MyError::NotFound)?; + + Ok(results) +} diff --git a/src/db.rs b/src/db/mod.rs similarity index 82% rename from src/db.rs rename to src/db/mod.rs index 0a09783..04376d8 100644 --- a/src/db.rs +++ b/src/db/mod.rs @@ -6,18 +6,24 @@ use tokio_pg_mapper::FromTokioPostgresRow; use crate::{ authorization::create_authorization_for_user, errors::MyError, - models::{Authorization, League, MiniLeague, MiniTeam, MiniUser, Team, User, UserTeam}, + models::{ + Authorization, League, MiniLeague, MiniTeam, MiniUser, Team, TeamDivAssociation, User, + UserTeam, + }, permission::UserPermission, }; +pub mod divisions; +pub mod leagues; + pub async fn add_test_data(client: &Client) -> Result<(), MyError> { - let _stmt = include_str!("../sql/test_data.sql"); + let _stmt = include_str!("../../sql/test_data.sql"); client.batch_execute(_stmt).await?; Ok(()) } pub async fn initdb(client: &Client) -> Result<(), MyError> { - let _stmt = include_str!("../sql/initdb.sql"); + let _stmt = include_str!("../../sql/initdb.sql"); client.batch_execute(_stmt).await?; Ok(()) @@ -32,7 +38,7 @@ pub async fn revoke_user_authorization(client: &Client, user: &User) -> Result Result { @@ -52,6 +58,26 @@ pub async fn get_team_from_id(client: &Client, team_id: i64) -> Result Result { + let _stmt = "SELECT $table_fields FROM teamDivAssociations WHERE id=$1"; + let _stmt = _stmt.replace("$table_fields", &Team::sql_table_fields()); + let stmt = client.prepare(&_stmt).await.unwrap(); + + let results = client + .query(&stmt, &[&assoc_id]) + .await? + .iter() + .map(|row| TeamDivAssociation::from_row_ref(row).unwrap()) + .collect::>() + .pop() + .ok_or(MyError::NotFound); + + results +} + pub async fn add_team(client: &Client, team: &MiniTeam) -> Result { let _stmt = "INSERT INTO teams(leagueid, team_name, created_at) VALUES($1, $2, $3) RETURNING $table_fields"; let _stmt = _stmt.replace("$table_fields", &Team::sql_table_fields()); @@ -69,13 +95,16 @@ pub async fn add_team(client: &Client, team: &MiniTeam) -> Result .ok_or(MyError::NotFound) } -pub async fn get_team_players(client: &Client, team: &Team) -> Result, MyError> { - let _stmt = "SELECT $table_fields FROM userTeam WHERE teamid=$1 AND leagueid=$2"; +pub async fn get_team_players( + client: &Client, + team: &TeamDivAssociation, +) -> Result, MyError> { + let _stmt = "SELECT $table_fields FROM userTeam WHERE teamid=$1 AND divisionid=$2"; let _stmt = _stmt.replace("$table_fields", &UserTeam::sql_table_fields()); let stmt = client.prepare(&_stmt).await.unwrap(); let userids: Vec = client - .query(&stmt, &[&team.id, &team.leagueid]) + .query(&stmt, &[&team.id, &team.divisionid]) .await? .iter() .map(|row| UserTeam::from_row_ref(row).unwrap().userid) @@ -114,53 +143,6 @@ async fn mass_get_user_from_internal_id( Ok(users) } -pub async fn get_league_from_id(client: &Client, leagueid: i64) -> Result { - log::debug!("Getting league {leagueid}"); - let _stmt = "SELECT $table_fields FROM leagues WHERE id=$1;"; - let _stmt = _stmt.replace("$table_fields", &League::sql_table_fields()); - log::debug!("Running this statement: {0}", _stmt); - let stmt = client.prepare(&_stmt).await.unwrap(); - - let results = client - .query(&stmt, &[&leagueid]) - .await? - .iter() - .map(|row| League::from_row_ref(row).unwrap()) - .collect::>() - .pop() - .ok_or(MyError::NotFound)?; - - Ok(results) -} - -pub async fn get_teams_with_leagueid(client: &Client, leagueid: i64) -> Result, MyError> { - let _stmt = "SELECT $table_fields FROM teams WHERE leagueid=$1"; - let _stmt = _stmt.replace("$table_fields", &Team::sql_table_fields()); - let stmt = client.prepare(&_stmt).await.unwrap(); - - let results = client - .query(&stmt, &[&leagueid]) - .await? - .iter() - .map(|row| Team::from_row_ref(row).unwrap()) - .collect::>(); - Ok(results) -} - -pub async fn get_leagues(client: &Client) -> Result, MyError> { - let _stmt = "SELECT $table_fields FROM leagues;"; - let _stmt = _stmt.replace("$table_fields", &League::sql_table_fields()); - let stmt = client.prepare(&_stmt).await.unwrap(); - - let results = client - .query(&stmt, &[]) - .await? - .iter() - .map(|row| League::from_row_ref(row).unwrap()) - .collect::>(); - Ok(results) -} - // pub async fn add_team(client: &Client, league: League, team: &MiniTeam) -> Result { // let _stmt = "INSERT INTO teams(leagueid, teamname)" // } @@ -191,7 +173,7 @@ pub async fn add_league(client: &Client, league: MiniLeague) -> Result Result { - let _stmt = include_str!("../sql/get_user_from_authtoken.sql"); + let _stmt = include_str!("../../sql/get_user_from_authtoken.sql"); let stmt = client.prepare(_stmt).await.unwrap(); let received_auth = client @@ -217,7 +199,7 @@ pub async fn get_user_from_auth_token(client: &Client, token: &str) -> Result Result { - let _stmt = include_str!("../sql/get_user_from_steamid.sql"); + let _stmt = include_str!("../../sql/get_user_from_steamid.sql"); let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields()); let stmt = client.prepare(&_stmt).await.unwrap(); @@ -235,7 +217,7 @@ pub async fn get_authorization_for_user( client: &Client, user: &User, ) -> Result { - let _stmt = include_str!("../sql/get_auth_token.sql"); + let _stmt = include_str!("../../sql/get_auth_token.sql"); let _stmt = _stmt.replace("$fields", &Authorization::sql_fields()); let stmt = client.prepare(&_stmt).await?; @@ -260,7 +242,7 @@ pub async fn register_authorization( user: &User, expiry: DateTime, ) -> Result { - let _stmt = include_str!("../sql/register_auth_token.sql"); + let _stmt = include_str!("../../sql/register_auth_token.sql"); // $table_fields didn't work with this for some reason when i tested it let _stmt = _stmt.replace("$fields", &Authorization::sql_fields()); log::debug!("Registering authorization {token} for {0}", &user.id); @@ -278,7 +260,7 @@ pub async fn register_authorization( } pub async fn get_users(client: &Client) -> Result, MyError> { - let sql_string = include_str!("../sql/get_users.sql"); + let sql_string = include_str!("../../sql/get_users.sql"); let sql_string = sql_string.replace("$table_fields", &User::sql_table_fields()); let sql_string = client.prepare(&sql_string).await.unwrap(); @@ -317,7 +299,7 @@ pub async fn set_super_user(client: &Client, user: &User) -> Result Result { - let _stmt = include_str!("../sql/add_user.sql"); + let _stmt = include_str!("../../sql/add_user.sql"); let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields()); let stmt = client.prepare(&_stmt).await.unwrap(); @@ -356,7 +338,7 @@ pub async fn get_user_page( amount: std::num::NonZero, ) -> Result, MyError> { log::trace!("Getting page {page} amount {amount}"); - let _stmt = include_str!("../sql/get_users_paged.sql"); + let _stmt = include_str!("../../sql/get_users_paged.sql"); let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields()); let stmt = client.prepare(&_stmt).await.unwrap(); @@ -387,7 +369,7 @@ pub async fn search_usernames( let page: i64 = page.into(); let offset: i64 = page * amount; - let _stmt = include_str!("../sql/fuzzy_search.sql"); + let _stmt = include_str!("../../sql/fuzzy_search.sql"); let _stmt = _stmt.replace("$table_fields", &User::sql_table_fields()); let stmt = client.prepare(&_stmt).await.unwrap(); diff --git a/src/main.rs b/src/main.rs index 8d29379..45e8098 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ Copyright (C) 2024 Lucy Faria and collaborators (https://lucyfaria.net) */ #![allow(dead_code)] +#![allow(unused_imports)] // Main execution and routes. use crate::config::ExampleConfig; @@ -204,10 +205,11 @@ async fn main() -> io::Result<()> { .service(leagues::get_league) .service(leagues::get_all_leagues) .service(admin::post_league) + .service(admin::post_league_divisions) .service(verify_openid_login) .service(logout) }) - .keep_alive(Duration::from_secs(70)) + .keep_alive(Duration::from_secs(0)) .bind((config.server_addr.clone(), config.server_port))? .workers(workers) .run(); diff --git a/src/models.rs b/src/models.rs index b3f5ca9..c313d6e 100644 --- a/src/models.rs +++ b/src/models.rs @@ -5,8 +5,7 @@ use tokio_pg_mapper_derive::PostgresMapper; /// A user without the id / created_at. /// /// Useful if you want Postgres to generate an id automatically. -#[derive(Deserialize, PostgresMapper, Serialize)] -#[pg_mapper(table = "users")] +#[derive(Deserialize, Serialize)] pub struct MiniUser { pub steamid: String, pub permissions: Option, @@ -41,13 +40,37 @@ impl From for MiniUser { } } -#[derive(Debug, Deserialize, PostgresMapper, Serialize)] -#[pg_mapper(table = "leagues")] +#[derive(Debug, Deserialize, Serialize)] pub struct MiniLeague { pub name: String, pub accepting_teams: bool, pub is_hidden: bool, } +#[derive(Debug, Deserialize, PostgresMapper, Serialize)] +#[pg_mapper(table = "division_admins")] +pub struct DivisionAdmin { + pub id: i64, + pub divisionid: i64, + pub userid: i64, + pub relation: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct WrappedDivisionAdmin { + pub inner: DivisionAdmin, + pub username: String, + pub avatarurl: String, +} + +#[derive(Debug, Deserialize, PostgresMapper, Serialize)] +#[pg_mapper(table = "divisions")] +pub struct Division { + pub id: i64, + pub prio: i32, + pub leagueid: i64, + pub name: String, + pub created_at: DateTime, +} #[derive(Debug, Deserialize, PostgresMapper, Serialize)] #[pg_mapper(table = "leagues")] @@ -64,14 +87,15 @@ pub struct League { pub struct MiniTeam { pub leagueid: i64, pub team_name: String, + pub team_tag: String, } #[derive(Debug, Deserialize, PostgresMapper, Serialize)] #[pg_mapper(table = "teams")] pub struct Team { pub id: i64, - pub leagueid: i64, pub team_name: String, + pub team_tag: String, pub created_at: DateTime, } @@ -84,6 +108,29 @@ pub struct UserTeam { pub created_at: DateTime, } +#[derive(Debug, Deserialize, PostgresMapper, Serialize)] +#[pg_mapper(table = "teamDivAssociations")] +pub struct TeamDivAssociation { + pub id: i64, + pub roster_name: Option, + pub teamid: i64, + pub divisionid: i64, + pub points_up: i64, + pub points_down: i64, + pub created_at: DateTime, +} + +#[derive(Debug, Deserialize, PostgresMapper, Serialize)] +#[pg_mapper(table = "team_invites")] +pub struct TeamInvite { + pub id: i64, + pub leagueid: i64, + pub divisionid: i64, + pub points_up: i64, + pub points_down: i64, + pub created_at: DateTime, +} + #[derive(Debug, Deserialize, PostgresMapper, Serialize)] #[pg_mapper(table = "authorization")] pub struct Authorization {