From a834850c0f0efbfa46dfe92cd98357a537ddcc0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=9F=83=E6=8B=89?= Date: Mon, 27 Jan 2025 22:22:41 +0800 Subject: [PATCH] wip: tags in challenge --- crates/db/src/transfer/challenge.rs | 57 ------- crates/db/src/transfer/game.rs | 45 ------ crates/db/src/transfer/game_challenge.rs | 15 +- crates/db/src/transfer/mod.rs | 2 + crates/db/src/transfer/submission.rs | 50 +----- crates/web/src/router/api/challenge/mod.rs | 107 ++++++++++--- crates/web/src/router/api/game/mod.rs | 168 ++++++++++++++++---- crates/web/src/router/api/pod/mod.rs | 5 +- crates/web/src/router/api/submission/mod.rs | 133 ++++++++++------ 9 files changed, 326 insertions(+), 256 deletions(-) diff --git a/crates/db/src/transfer/challenge.rs b/crates/db/src/transfer/challenge.rs index d16b6d5..9977aa5 100644 --- a/crates/db/src/transfer/challenge.rs +++ b/crates/db/src/transfer/challenge.rs @@ -75,63 +75,6 @@ impl Challenge { } } -pub async fn find( - id: Option, title: Option, category: Option, is_public: Option, - is_dynamic: Option, sorts: Option, page: Option, size: Option, -) -> Result<(Vec, u64), DbErr> { - let mut sql = entity::challenge::Entity::find(); - - if let Some(id) = id { - sql = sql.filter(entity::challenge::Column::Id.eq(id)); - } - - if let Some(title) = title { - sql = sql.filter(entity::challenge::Column::Title.contains(title)); - } - - if let Some(category) = category { - sql = sql.filter(entity::challenge::Column::Category.eq(category)); - } - - if let Some(is_public) = is_public { - sql = sql.filter(entity::challenge::Column::IsPublic.eq(is_public)); - } - - if let Some(is_dynamic) = is_dynamic { - sql = sql.filter(entity::challenge::Column::IsDynamic.eq(is_dynamic)); - } - - sql = sql.filter(entity::challenge::Column::DeletedAt.is_null()); - - let total = sql.clone().count(get_db()).await?; - - if let Some(sorts) = sorts { - let sorts = sorts.split(",").collect::>(); - for sort in sorts { - let col = - match crate::entity::challenge::Column::from_str(sort.replace("-", "").as_str()) { - Ok(col) => col, - Err(_) => continue, - }; - if sort.starts_with("-") { - sql = sql.order_by(col, Order::Desc); - } else { - sql = sql.order_by(col, Order::Asc); - } - } - } - - if let (Some(page), Some(size)) = (page, size) { - let offset = (page - 1) * size; - sql = sql.offset(offset).limit(size); - } - - let models = sql.all(get_db()).await?; - let challenges = models.into_iter().map(Challenge::from).collect(); - - Ok((challenges, total)) -} - pub async fn find_by_ids(ids: Vec) -> Result, DbErr> { let models = entity::challenge::Entity::find() .filter(entity::challenge::Column::Id.is_in(ids)) diff --git a/crates/db/src/transfer/game.rs b/crates/db/src/transfer/game.rs index 45140d4..0ed5917 100644 --- a/crates/db/src/transfer/game.rs +++ b/crates/db/src/transfer/game.rs @@ -64,48 +64,3 @@ impl From for entity::game::Model { } } } - -pub async fn find( - id: Option, title: Option, is_enabled: Option, sorts: Option, - page: Option, size: Option, -) -> Result<(Vec, u64), DbErr> { - let mut sql = entity::game::Entity::find(); - - if let Some(id) = id { - sql = sql.filter(entity::game::Column::Id.eq(id)); - } - - if let Some(title) = title { - sql = sql.filter(entity::game::Column::Title.contains(title)); - } - - if let Some(is_enabled) = is_enabled { - sql = sql.filter(entity::game::Column::IsEnabled.eq(is_enabled)); - } - - if let Some(sorts) = sorts { - let sorts = sorts.split(",").collect::>(); - for sort in sorts { - let col = match crate::entity::game::Column::from_str(sort.replace("-", "").as_str()) { - Ok(col) => col, - Err(_) => continue, - }; - if sort.starts_with("-") { - sql = sql.order_by(col, Order::Desc); - } else { - sql = sql.order_by(col, Order::Asc); - } - } - } - - let total = sql.clone().count(get_db()).await?; - - if let (Some(page), Some(size)) = (page, size) { - let offset = (page - 1) * size; - sql = sql.offset(offset).limit(size); - } - - let games = sql.all(get_db()).await?; - - Ok((games, total)) -} diff --git a/crates/db/src/transfer/game_challenge.rs b/crates/db/src/transfer/game_challenge.rs index df19400..697c031 100644 --- a/crates/db/src/transfer/game_challenge.rs +++ b/crates/db/src/transfer/game_challenge.rs @@ -39,7 +39,9 @@ impl From for GameChallenge { } } -async fn preload(models: Vec) -> Result, DbErr> { +pub async fn preload( + models: Vec, +) -> Result, DbErr> { let challenges = models .load_one(entity::challenge::Entity, get_db()) .await? @@ -60,9 +62,12 @@ async fn preload(models: Vec) -> Result, challenge_id: Option, is_enabled: Option, + game_id: Option, challenge_id: Option, category: Option, + is_enabled: Option, ) -> Result<(Vec, u64), DbErr> { - let mut sql = entity::game_challenge::Entity::find(); + let mut sql = entity::game_challenge::Entity::find() + .inner_join(entity::challenge::Entity) + .inner_join(entity::game::Entity); if let Some(game_id) = game_id { sql = sql.filter(entity::game_challenge::Column::GameId.eq(game_id)); @@ -76,6 +81,10 @@ pub async fn find( sql = sql.filter(entity::game_challenge::Column::IsEnabled.eq(is_enabled)); } + if let Some(category) = category { + sql = sql.filter(entity::challenge::Column::Category.eq(category)); + } + let total = sql.clone().count(get_db()).await?; let models = sql.all(get_db()).await?; diff --git a/crates/db/src/transfer/mod.rs b/crates/db/src/transfer/mod.rs index f2a1f3d..82bfb13 100644 --- a/crates/db/src/transfer/mod.rs +++ b/crates/db/src/transfer/mod.rs @@ -1,3 +1,5 @@ +//! Transfer module is used to store structures with additional fields and +//! preload functions. pub mod challenge; pub mod game; pub mod game_challenge; diff --git a/crates/db/src/transfer/submission.rs b/crates/db/src/transfer/submission.rs index b1f24f9..93ece6a 100644 --- a/crates/db/src/transfer/submission.rs +++ b/crates/db/src/transfer/submission.rs @@ -71,7 +71,7 @@ impl From for entity::submission::Model { } } -async fn preload(mut submissions: Vec) -> Result, DbErr> { +pub async fn preload(mut submissions: Vec) -> Result, DbErr> { let models = submissions .clone() .into_iter() @@ -121,54 +121,6 @@ async fn preload(mut submissions: Vec) -> Result, Db Ok(submissions) } -pub async fn find( - id: Option, user_id: Option, team_id: Option, game_id: Option, - challenge_id: Option, status: Option, page: Option, size: Option, -) -> Result<(Vec, u64), DbErr> { - let mut sql = entity::submission::Entity::find(); - - if let Some(id) = id { - sql = sql.filter(entity::submission::Column::Id.eq(id)); - } - - if let Some(user_id) = user_id { - sql = sql.filter(entity::submission::Column::UserId.eq(user_id)); - } - - if let Some(team_id) = team_id { - sql = sql.filter(entity::submission::Column::TeamId.eq(team_id)); - } - - if let Some(game_id) = game_id { - sql = sql.filter(entity::submission::Column::GameId.eq(game_id)); - } - - if let Some(challenge_id) = challenge_id { - sql = sql.filter(entity::submission::Column::ChallengeId.eq(challenge_id)); - } - - if let Some(status) = status { - sql = sql.filter(entity::submission::Column::Status.eq(status)); - } - - let total = sql.clone().count(get_db()).await?; - - if let (Some(page), Some(size)) = (page, size) { - let offset = (page - 1) * size; - sql = sql.offset(offset).limit(size); - } - - let submissions = sql.all(get_db()).await?; - let mut submissions = submissions - .into_iter() - .map(Submission::from) - .collect::>(); - - submissions = preload(submissions).await?; - - Ok((submissions, total)) -} - pub async fn get_by_challenge_ids(challenge_ids: Vec) -> Result, DbErr> { let submissions = entity::submission::Entity::find() .filter(entity::submission::Column::ChallengeId.is_in(challenge_ids)) diff --git a/crates/web/src/router/api/challenge/mod.rs b/crates/web/src/router/api/challenge/mod.rs index 110b835..c64e7ab 100644 --- a/crates/web/src/router/api/challenge/mod.rs +++ b/crates/web/src/router/api/challenge/mod.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, str::FromStr}; use axum::{ Router, @@ -10,11 +10,14 @@ use axum::{ use cds_db::{ entity::{submission::Status, user::Group}, get_db, + transfer::Challenge, }; use sea_orm::{ ActiveModelTrait, ActiveValue::{NotSet, Set, Unchanged}, - ColumnTrait, EntityTrait, QueryFilter, + ColumnTrait, EntityName, EntityTrait, Iden, IdenStatic, Order, PaginatorTrait, QueryFilter, + QueryOrder, QuerySelect, + sea_query::Expr, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -51,7 +54,7 @@ pub struct GetRequest { pub id: Option, pub title: Option, pub category: Option, - pub tags: Option>, + pub tags: Option, pub is_public: Option, pub is_dynamic: Option, pub is_detailed: Option, @@ -62,23 +65,82 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, -) -> Result>, WebError> { +) -> Result>, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && params.is_detailed.unwrap_or(false) { return Err(WebError::Forbidden(json!(""))); } - let (mut challenges, total) = cds_db::transfer::challenge::find( - params.id, - params.title, - params.category, - params.is_public, - params.is_dynamic, - params.sorts, - params.page, - params.size, - ) - .await?; + let mut sql = cds_db::entity::challenge::Entity::find(); + + if let Some(id) = params.id { + sql = sql.filter(cds_db::entity::challenge::Column::Id.eq(id)); + } + + if let Some(title) = params.title { + sql = sql.filter(cds_db::entity::challenge::Column::Title.contains(title)); + } + + if let Some(category) = params.category { + sql = sql.filter(cds_db::entity::challenge::Column::Category.eq(category)); + } + + if let Some(tags) = params.tags { + let tags = tags + .split(",") + .map(|s| s.to_owned()) + .collect::>(); + + sql = sql.filter(Expr::cust_with_expr( + format!( + "\"{}\".\"{}\" @> $1::varchar[]", + cds_db::entity::challenge::Entity.table_name(), + cds_db::entity::challenge::Column::Tags.to_string() + ) + .as_str(), + tags, + )) + } + + if let Some(is_public) = params.is_public { + sql = sql.filter(cds_db::entity::challenge::Column::IsPublic.eq(is_public)); + } + + if let Some(is_dynamic) = params.is_dynamic { + sql = sql.filter(cds_db::entity::challenge::Column::IsDynamic.eq(is_dynamic)); + } + + sql = sql.filter(cds_db::entity::challenge::Column::DeletedAt.is_null()); + + let total = sql.clone().count(get_db()).await?; + + if let Some(sorts) = params.sorts { + let sorts = sorts.split(",").collect::>(); + for sort in sorts { + let col = + match cds_db::entity::challenge::Column::from_str(sort.replace("-", "").as_str()) { + Ok(col) => col, + Err(_) => continue, + }; + if sort.starts_with("-") { + sql = sql.order_by(col, Order::Desc); + } else { + sql = sql.order_by(col, Order::Asc); + } + } + } + + if let (Some(page), Some(size)) = (params.page, params.size) { + let offset = (page - 1) * size; + sql = sql.offset(offset).limit(size); + } + + let mut challenges = sql + .all(get_db()) + .await? + .into_iter() + .map(Challenge::from) + .collect::>(); for challenge in challenges.iter_mut() { let is_detailed = params.is_detailed.unwrap_or(false); @@ -171,12 +233,15 @@ pub async fn get_status( } if let Some(game_id) = body.game_id { - let (game_challenges, _) = - cds_db::transfer::game_challenge::find(Some(game_id), None, None).await?; + let game_challenges = cds_db::entity::game_challenge::Entity::find() + .filter(cds_db::entity::game_challenge::Column::GameId.eq(game_id)) + .all(get_db()) + .await?; for game_challenge in game_challenges { - let status_response = result.get_mut(&game_challenge.challenge_id).unwrap(); - status_response.pts = game_challenge.pts; + if let Some(status_response) = result.get_mut(&game_challenge.challenge_id) { + status_response.pts = game_challenge.pts; + } } } @@ -203,7 +268,7 @@ pub struct CreateRequest { pub async fn create( Extension(ext): Extension, Json(body): Json, -) -> Result, WebError> { +) -> Result, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); @@ -249,7 +314,7 @@ pub struct UpdateRequest { pub async fn update( Extension(ext): Extension, Path(id): Path, VJson(mut body): VJson, -) -> Result, WebError> { +) -> Result, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); diff --git a/crates/web/src/router/api/game/mod.rs b/crates/web/src/router/api/game/mod.rs index e800eb8..7fd6b4d 100644 --- a/crates/web/src/router/api/game/mod.rs +++ b/crates/web/src/router/api/game/mod.rs @@ -1,5 +1,7 @@ pub mod calculator; +use std::str::FromStr; + use axum::{ Router, extract::{DefaultBodyLimit, Multipart}, @@ -14,11 +16,12 @@ use cds_db::{ use sea_orm::{ ActiveModelTrait, ActiveValue::{NotSet, Unchanged}, - ColumnTrait, Condition, EntityTrait, QueryFilter, Set, - sea_query::Cond, + ColumnTrait, Condition, EntityTrait, JoinType, Order, PaginatorTrait, QueryFilter, QueryOrder, + QuerySelect, RelationTrait, Set, }; use serde::{Deserialize, Serialize}; use serde_json::json; +use uuid::Uuid; use validator::Validate; use crate::{ @@ -93,6 +96,7 @@ pub struct GetRequest { pub sorts: Option, } +/// Get games with given params. pub async fn get( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { @@ -101,16 +105,45 @@ pub async fn get( return Err(WebError::Forbidden(json!(""))); } - let (games, total) = cds_db::transfer::game::find( - params.id, - params.title, - params.is_enabled, - params.sorts, - params.page, - params.size, - ) - .await?; - let games = games + let mut sql = cds_db::entity::game::Entity::find(); + + if let Some(id) = params.id { + sql = sql.filter(cds_db::entity::game::Column::Id.eq(id)); + } + + if let Some(title) = params.title { + sql = sql.filter(cds_db::entity::game::Column::Title.contains(title)); + } + + if let Some(is_enabled) = params.is_enabled { + sql = sql.filter(cds_db::entity::game::Column::IsEnabled.eq(is_enabled)); + } + + if let Some(sorts) = params.sorts { + let sorts = sorts.split(",").collect::>(); + for sort in sorts { + let col = match cds_db::entity::game::Column::from_str(sort.replace("-", "").as_str()) { + Ok(col) => col, + Err(_) => continue, + }; + if sort.starts_with("-") { + sql = sql.order_by(col, Order::Desc); + } else { + sql = sql.order_by(col, Order::Asc); + } + } + } + + let total = sql.clone().count(get_db()).await?; + + if let (Some(page), Some(size)) = (params.page, params.size) { + let offset = (page - 1) * size; + sql = sql.offset(offset).limit(size); + } + + let games = sql + .all(get_db()) + .await? .into_iter() .map(cds_db::transfer::Game::from) .collect::>(); @@ -257,25 +290,96 @@ pub async fn delete( pub struct GetChallengeRequest { pub game_id: Option, pub challenge_id: Option, - pub team_id: Option, + pub category: Option, pub is_enabled: Option, + + pub page: Option, + pub size: Option, } pub async fn get_challenge( - Extension(ext): Extension, Query(params): Query, + Extension(ext): Extension, Path(id): Path, Query(params): Query, ) -> Result>, WebError> { - let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; - let (game_challenges, _) = cds_db::transfer::game_challenge::find( - params.game_id, - params.challenge_id, - params.is_enabled, - ) - .await?; + let game = cds_db::entity::game::Entity::find_by_id(id) + .one(get_db()) + .await? + .ok_or(WebError::BadRequest(json!("game_not_found")))?; + + if operator.group != Group::Admin { + // Check if the operator can view the game challenges. + // + // There are two prerequisites: + // the operator must belong to one of the `is_allowed` = `true` game teams, + // and time is between game's `started_at` and `ended_at`. + // + // SELECT u.id AS user_id, gt.game_id, gt.is_allowed + // FROM users u + // JOIN user_teams ut ON u.id = ut.user_id + // JOIN game_teams gt ON ut.team_id = gt.team_id + // WHERE u.id = ? AND gt.game_id = ? AND gt.is_allowed = true; + let exists = cds_db::entity::user::Entity::find() + .join( + JoinType::InnerJoin, + cds_db::entity::user_team::Relation::User.def().rev(), + ) + .join( + JoinType::InnerJoin, + cds_db::entity::user_team::Relation::Team.def(), + ) + .join( + JoinType::InnerJoin, + cds_db::entity::game_team::Relation::Team.def().rev(), + ) + .filter(cds_db::entity::user::Column::Id.eq(operator.id)) + .filter(cds_db::entity::game_team::Column::GameId.eq(game.id)) + .filter(cds_db::entity::game_team::Column::IsAllowed.eq(true)) + .count(get_db()) + .await? + > 0; + + if !exists + || chrono::Utc::now().timestamp() < game.started_at + || chrono::Utc::now().timestamp() > game.ended_at + { + return Err(WebError::Forbidden(json!(""))); + } + } + + // Using inner join to access fields in related tables. + let mut sql = cds_db::entity::game_challenge::Entity::find() + .inner_join(cds_db::entity::challenge::Entity) + .inner_join(cds_db::entity::game::Entity); + + sql = sql.filter(cds_db::entity::game_challenge::Column::GameId.eq(id)); + + if let Some(challenge_id) = params.challenge_id { + sql = sql.filter(cds_db::entity::game_challenge::Column::ChallengeId.eq(challenge_id)); + } + + if let Some(is_enabled) = params.is_enabled { + sql = sql.filter(cds_db::entity::game_challenge::Column::IsEnabled.eq(is_enabled)); + } + + if let Some(category) = params.category { + sql = sql.filter(cds_db::entity::challenge::Column::Category.eq(category)); + } + + let total = sql.clone().count(get_db()).await?; + + if let (Some(page), Some(size)) = (params.page, params.size) { + let offset = (page - 1) * size; + sql = sql.offset(offset).limit(size); + } + + let game_challenges = + cds_db::transfer::game_challenge::preload(sql.all(get_db()).await?).await?; Ok(WebResponse { code: StatusCode::OK.as_u16(), data: Some(game_challenges), + total: Some(total), ..WebResponse::default() }) } @@ -283,7 +387,7 @@ pub async fn get_challenge( #[derive(Clone, Debug, Serialize, Deserialize)] pub struct CreateChallengeRequest { pub game_id: i64, - pub challenge_id: uuid::Uuid, + pub challenge_id: Uuid, pub is_enabled: Option, pub difficulty: Option, pub max_pts: Option, @@ -301,9 +405,19 @@ pub async fn create_challenge( return Err(WebError::Forbidden(json!(""))); } + let game = cds_db::entity::game::Entity::find_by_id(body.game_id) + .one(get_db()) + .await? + .ok_or(WebError::BadRequest(json!("game_not_found")))?; + + let challenge = cds_db::entity::challenge::Entity::find_by_id(body.challenge_id) + .one(get_db()) + .await? + .ok_or(WebError::BadRequest(json!("challenge_not_found")))?; + let game_challenge = cds_db::entity::game_challenge::ActiveModel { - game_id: Set(body.game_id), - challenge_id: Set(body.challenge_id), + game_id: Set(game.id), + challenge_id: Set(challenge.id), difficulty: body.difficulty.map_or(NotSet, Set), is_enabled: body.is_enabled.map_or(NotSet, Set), max_pts: body.max_pts.map_or(NotSet, Set), @@ -327,7 +441,7 @@ pub async fn create_challenge( #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UpdateChallengeRequest { pub game_id: Option, - pub challenge_id: Option, + pub challenge_id: Option, pub is_enabled: Option, pub difficulty: Option, pub max_pts: Option, @@ -338,7 +452,7 @@ pub struct UpdateChallengeRequest { } pub async fn update_challenge( - Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, uuid::Uuid)>, + Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, Uuid)>, Json(mut body): Json, ) -> Result, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; @@ -380,7 +494,7 @@ pub async fn update_challenge( } pub async fn delete_challenge( - Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, i64)>, + Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, Uuid)>, ) -> Result, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { diff --git a/crates/web/src/router/api/pod/mod.rs b/crates/web/src/router/api/pod/mod.rs index 2303555..4961539 100644 --- a/crates/web/src/router/api/pod/mod.rs +++ b/crates/web/src/router/api/pod/mod.rs @@ -160,10 +160,7 @@ pub async fn create( } } else { let existing_pod_count = cds_db::entity::pod::Entity::find() - .filter( - Condition::all() - .add(cds_db::entity::pod::Column::UserId.eq(operator.id)) - ) + .filter(Condition::all().add(cds_db::entity::pod::Column::UserId.eq(operator.id))) .count(get_db()) .await?; diff --git a/crates/web/src/router/api/submission/mod.rs b/crates/web/src/router/api/submission/mod.rs index 82da080..a48b9ef 100644 --- a/crates/web/src/router/api/submission/mod.rs +++ b/crates/web/src/router/api/submission/mod.rs @@ -4,9 +4,11 @@ use axum::{Router, http::StatusCode}; use cds_db::{ entity::{submission::Status, user::Group}, get_db, + transfer::Submission, }; use sea_orm::{ - ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, Condition, EntityTrait, QueryFilter, Set, + ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, Condition, EntityTrait, PaginatorTrait, + QueryFilter, QuerySelect, Set, }; use serde::{Deserialize, Serialize}; use serde_json::json; @@ -41,29 +43,60 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, -) -> Result>, WebError> { +) -> Result>, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && params.is_detailed.unwrap_or(false) { return Err(WebError::Forbidden(json!(""))); } - let (mut submissions, total) = cds_db::transfer::submission::find( - params.id, - params.user_id, - params.team_id, - params.game_id, - params.challenge_id, - params.status, - params.page, - params.size, - ) - .await?; + let mut sql = cds_db::entity::submission::Entity::find(); + + if let Some(id) = params.id { + sql = sql.filter(cds_db::entity::submission::Column::Id.eq(id)); + } + + if let Some(user_id) = params.user_id { + sql = sql.filter(cds_db::entity::submission::Column::UserId.eq(user_id)); + } + + if let Some(team_id) = params.team_id { + sql = sql.filter(cds_db::entity::submission::Column::TeamId.eq(team_id)); + } + + if let Some(game_id) = params.game_id { + sql = sql.filter(cds_db::entity::submission::Column::GameId.eq(game_id)); + } + + if let Some(challenge_id) = params.challenge_id { + sql = sql.filter(cds_db::entity::submission::Column::ChallengeId.eq(challenge_id)); + } + + if let Some(status) = params.status { + sql = sql.filter(cds_db::entity::submission::Column::Status.eq(status)); + } + + let total = sql.clone().count(get_db()).await?; + + if let (Some(page), Some(size)) = (params.page, params.size) { + let offset = (page - 1) * size; + sql = sql.offset(offset).limit(size); + } - let is_detailed = params.is_detailed.unwrap_or(false); - for submission in submissions.iter_mut() { - if !is_detailed { - submission.desensitize(); + let submissions = sql.all(get_db()).await?; + let mut submissions = submissions + .into_iter() + .map(Submission::from) + .collect::>(); + + submissions = cds_db::transfer::submission::preload(submissions).await?; + + match params.is_detailed { + Some(true) => { + for submission in submissions.iter_mut() { + submission.desensitize(); + } } + _ => {} } Ok(WebResponse { @@ -76,7 +109,7 @@ pub async fn get( pub async fn get_by_id( Extension(ext): Extension, Path(id): Path, -) -> Result, WebError> { +) -> Result, WebError> { let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let submission = cds_db::entity::submission::Entity::find_by_id(id) @@ -104,60 +137,60 @@ pub struct CreateRequest { pub user_id: Option, pub team_id: Option, pub game_id: Option, - pub challenge_id: Option, + pub challenge_id: uuid::Uuid, } pub async fn create( Extension(ext): Extension, Json(mut body): Json, -) -> Result, WebError> { +) -> Result, WebError> { let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; body.user_id = Some(operator.id); - if let Some(challenge_id) = body.challenge_id.clone() { - let challenge = cds_db::entity::challenge::Entity::find_by_id(challenge_id) - .one(get_db()) - .await?; + let challenge = cds_db::entity::challenge::Entity::find_by_id(body.challenge_id) + .one(get_db()) + .await? + .ok_or(WebError::BadRequest(json!("challenge_not_found")))?; - if challenge.is_none() { - return Err(WebError::BadRequest(json!("challenge_not_found"))); - } - } else { - return Err(WebError::BadRequest(json!("challenge_id_required"))); + if body.game_id.is_some() != body.team_id.is_some() { + return Err(WebError::BadRequest(json!("invalid"))); + } + + if !challenge.is_public && (body.game_id.is_none() || body.team_id.is_none()) { + return Err(WebError::BadRequest(json!("challenge_not_found"))); } if let (Some(game_id), Some(team_id)) = (body.game_id, body.team_id) { let game = cds_db::entity::game::Entity::find_by_id(game_id) .one(get_db()) - .await?; - - if game.is_none() { - return Err(WebError::BadRequest(json!("game_not_found"))); - } + .await? + .ok_or(WebError::BadRequest(json!("game_not_found")))?; let team = cds_db::entity::team::Entity::find_by_id(team_id) .one(get_db()) - .await?; - - if team.is_none() { - return Err(WebError::BadRequest(json!("team_not_found"))); - } + .await? + .ok_or(WebError::BadRequest(json!("team_not_found")))?; - let game_challenge = cds_db::entity::game_challenge::Entity::find() + let _ = cds_db::entity::game_challenge::Entity::find() .filter( Condition::all() - .add(cds_db::entity::game_challenge::Column::GameId.eq(game_id)) - .add( - cds_db::entity::game_challenge::Column::ChallengeId - .eq(body.challenge_id.unwrap()), - ), + .add(cds_db::entity::game_challenge::Column::GameId.eq(game.id)) + .add(cds_db::entity::game_challenge::Column::ChallengeId.eq(body.challenge_id)), ) .one(get_db()) - .await?; + .await? + .ok_or(WebError::BadRequest(json!("game_challenge_not_found"))); - if game_challenge.is_none() { - return Err(WebError::BadRequest(json!("game_challenge_not_found"))); - } + let _ = cds_db::entity::game_challenge::Entity::find() + .filter( + Condition::all() + .add(cds_db::entity::game_team::Column::TeamId.eq(team.id)) + .add(cds_db::entity::game_team::Column::GameId.eq(game.id)) + .add(cds_db::entity::game_team::Column::IsAllowed.eq(true)), + ) + .one(get_db()) + .await? + .ok_or(WebError::BadRequest(json!("game_team_not_found"))); } let submission = cds_db::entity::submission::ActiveModel { @@ -165,7 +198,7 @@ pub async fn create( user_id: body.user_id.map_or(NotSet, Set), team_id: body.team_id.map_or(NotSet, |v| Set(Some(v))), game_id: body.game_id.map_or(NotSet, |v| Set(Some(v))), - challenge_id: body.challenge_id.map_or(NotSet, Set), + challenge_id: Set(body.challenge_id), status: Set(Status::Pending), ..Default::default() }