diff --git a/build.rs b/build.rs index 53aee09..5208f94 100644 --- a/build.rs +++ b/build.rs @@ -16,7 +16,6 @@ fn main() { println!( "cargo:rustc-env=BUILD_AT={}", - chrono::Utc::now() - .format("%Y-%m-%d %H:%M:%S UTC") + chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC") ); } diff --git a/src/config/mod.rs b/src/config/mod.rs index 10d9817..a83aace 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,16 +4,11 @@ pub mod site; pub mod traits; use futures_util::StreamExt; -use once_cell::sync::Lazy; use sea_orm::EntityTrait; use serde::{Deserialize, Serialize}; -use tokio::sync::RwLock; -use tracing::info; use crate::db::get_db; -pub static APP_CONFIG: Lazy> = Lazy::new(|| RwLock::new(Config::default())); - #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct Config { pub site: site::Config, @@ -32,33 +27,19 @@ impl From for Config { } pub async fn init() { - tokio::spawn(async move { - let mut messages = crate::queue::subscribe("config").await.unwrap(); - while let Some(result) = messages.next().await { - if result.is_err() { - continue; - } - let message = result.unwrap(); - let _ = String::from_utf8(message.payload.to_vec()).unwrap(); - sync().await; - message.ack().await.unwrap(); + let config = crate::cache::get::("config").await.unwrap(); + if config.is_none() { + let model = crate::db::entity::config::Entity::find() + .one(get_db()) + .await + .unwrap(); + if let Some(model) = model { + let _ = crate::cache::set("config", Config::from(model.clone())).await; } - }); - sync().await; - info!("Configuration synchronizer initialized successfully."); -} - -pub async fn sync() { - let config = crate::db::entity::config::Entity::find() - .one(get_db()) - .await - .unwrap(); - if let Some(config) = config { - *APP_CONFIG.write().await = config.into(); } } pub async fn get_config() -> Config { - let config = APP_CONFIG.read().await; - config.clone() + let config = crate::cache::get::("config").await.unwrap(); + config.clone().unwrap() } diff --git a/src/config/site/mod.rs b/src/config/site/mod.rs index b66e15d..b3c5033 100644 --- a/src/config/site/mod.rs +++ b/src/config/site/mod.rs @@ -1,7 +1,6 @@ use sea_orm::FromJsonQueryResult; use serde::{Deserialize, Serialize}; - #[derive(Clone, Debug, Serialize, Deserialize, FromJsonQueryResult, PartialEq, Eq, Default)] pub struct Config { pub title: String, diff --git a/src/db/entity/challenge.rs b/src/db/entity/challenge.rs index f2b05e8..13bf5c0 100644 --- a/src/db/entity/challenge.rs +++ b/src/db/entity/challenge.rs @@ -1,8 +1,8 @@ use async_trait::async_trait; use sea_orm::{ ActiveModelBehavior, ActiveModelTrait, ColumnTrait, ConnectionTrait, DbErr, DeriveActiveEnum, - DeriveEntityModel, DerivePrimaryKey, EntityTrait, EnumIter, FromJsonQueryResult, PrimaryKeyTrait, Related, RelationDef, RelationTrait, - Set, + DeriveEntityModel, DerivePrimaryKey, EntityTrait, EnumIter, FromJsonQueryResult, + PrimaryKeyTrait, Related, RelationDef, RelationTrait, Set, }; use serde::{Deserialize, Serialize}; use serde_repr::{Deserialize_repr, Serialize_repr}; diff --git a/src/db/entity/config.rs b/src/db/entity/config.rs index cab4190..27c8c29 100644 --- a/src/db/entity/config.rs +++ b/src/db/entity/config.rs @@ -25,7 +25,7 @@ impl ActiveModelBehavior for ActiveModel { async fn after_save(model: Model, _db: &C, _insert: bool) -> Result where C: ConnectionTrait, { - let _ = crate::queue::publish("config", "").await.unwrap(); + let _ = crate::cache::set("config", crate::config::Config::from(model.clone())).await; Ok(model) } } diff --git a/src/db/migration.rs b/src/db/migration.rs index 5202c9d..47919f0 100644 --- a/src/db/migration.rs +++ b/src/db/migration.rs @@ -16,7 +16,9 @@ where let schema = Schema::new(builder); let stmt = builder.build(schema.create_table_from_entity(entity).if_not_exists()); - if let Err(e) = db.execute(stmt).await { error!("Error: {}", e) } + if let Err(e) = db.execute(stmt).await { + error!("Error: {}", e) + } } pub async fn migrate(db: &DbConn) { diff --git a/src/db/mod.rs b/src/db/mod.rs index ce8788a..d3795a5 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -77,7 +77,7 @@ pub async fn init_config() { let config = entity::config::ActiveModel { auth: Set(crate::config::auth::Config { jwt: crate::config::auth::jwt::Config { - secret_key: String::from("123456"), + secret_key: String::from(uuid::Uuid::new_v4()), expiration: 1800, }, registration: crate::config::auth::registration::Config { diff --git a/src/db/transfer/game_challenge.rs b/src/db/transfer/game_challenge.rs index d2fc9b4..2718907 100644 --- a/src/db/transfer/game_challenge.rs +++ b/src/db/transfer/game_challenge.rs @@ -39,9 +39,7 @@ impl From for GameChallenge { } } -async fn preload( - models: Vec, -) -> Result, DbErr> { +async fn preload(models: Vec) -> Result, DbErr> { let challenges = models .load_one(entity::challenge::Entity, get_db()) .await? diff --git a/src/db/transfer/game_team.rs b/src/db/transfer/game_team.rs index fb11044..c9cb398 100644 --- a/src/db/transfer/game_team.rs +++ b/src/db/transfer/game_team.rs @@ -1,7 +1,9 @@ -use sea_orm::entity::prelude::*; +use std::str::FromStr; + +use sea_orm::{entity::prelude::*, Order, QueryOrder, QuerySelect}; use serde::{Deserialize, Serialize}; -use super::{team, Game, Team}; +use super::{team, Game, Submission, Team}; use crate::db::{entity, get_db}; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -50,7 +52,8 @@ async fn preload(mut game_teams: Vec) -> Result, DbErr> } pub async fn find( - game_id: Option, team_id: Option, + game_id: Option, team_id: Option, is_allowed: Option, sorts: Option, + page: Option, size: Option, ) -> Result<(Vec, u64), DbErr> { let mut sql = entity::game_team::Entity::find(); @@ -62,8 +65,36 @@ pub async fn find( sql = sql.filter(entity::game_team::Column::TeamId.eq(team_id)); } + if let Some(is_allowed) = is_allowed { + sql = sql.filter(entity::game_team::Column::IsAllowed.eq(is_allowed)); + } + + if let Some(sorts) = sorts { + let sorts = sorts.split(",").collect::>(); + for sort in sorts { + let col = match crate::db::entity::game_team::Column::from_str( + sort.replace("-", "").as_str(), + ) { + Ok(col) => col, + Err(_) => return Err(DbErr::Custom("invalid sort column".to_string())), + }; + 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) = page { + if let Some(size) = size { + let offset = (page - 1) * size; + sql = sql.offset(offset).limit(size); + } + } + let game_teams = sql.all(get_db()).await?; let mut game_teams = game_teams .into_iter() diff --git a/src/db/transfer/pod.rs b/src/db/transfer/pod.rs index 1c7e85a..aaf20ad 100644 --- a/src/db/transfer/pod.rs +++ b/src/db/transfer/pod.rs @@ -148,10 +148,7 @@ pub async fn find( let total = sql.clone().count(get_db()).await?; let pods = sql.all(get_db()).await?; - let mut pods = pods - .into_iter() - .map(Pod::from) - .collect::>(); + let mut pods = pods.into_iter().map(Pod::from).collect::>(); pods = preload(pods).await?; diff --git a/src/db/transfer/submission.rs b/src/db/transfer/submission.rs index efa3b9b..0c68016 100644 --- a/src/db/transfer/submission.rs +++ b/src/db/transfer/submission.rs @@ -1,4 +1,4 @@ -use sea_orm::{entity::prelude::*, QueryOrder, QuerySelect}; +use sea_orm::{entity::prelude::*, Condition, QueryOrder, QuerySelect}; use serde::{Deserialize, Serialize}; use super::{Challenge, Game, Team, User}; @@ -185,3 +185,28 @@ pub async fn get_by_challenge_ids(challenge_ids: Vec) -> Result, status: Option, +) -> Result, DbErr> { + let mut sql = entity::submission::Entity::find().filter( + Condition::all() + .add(entity::submission::Column::GameId.eq(game_id)) + .add(entity::submission::Column::TeamId.is_in(team_ids)), + ); + + if let Some(status) = status { + sql = sql.filter(entity::submission::Column::Status.eq(status)); + } + + let submissions = sql.all(get_db()).await?; + + let mut submissions = submissions + .into_iter() + .map(Submission::from) + .collect::>(); + + submissions = preload(submissions).await?; + + Ok(submissions) +} diff --git a/src/db/transfer/team.rs b/src/db/transfer/team.rs index f718c24..5b92db2 100644 --- a/src/db/transfer/team.rs +++ b/src/db/transfer/team.rs @@ -76,12 +76,7 @@ async fn preload(mut teams: Vec) -> Result, DbErr> { .load_many_to_many(entity::user::Entity, entity::user_team::Entity, get_db()) .await? .into_iter() - .map(|users| { - users - .into_iter() - .map(User::from) - .collect::>() - }) + .map(|users| users.into_iter().map(User::from).collect::>()) .collect::>>(); for (i, team) in teams.iter_mut().enumerate() { diff --git a/src/db/transfer/user.rs b/src/db/transfer/user.rs index 2b9a3ea..d33dfdf 100644 --- a/src/db/transfer/user.rs +++ b/src/db/transfer/user.rs @@ -70,12 +70,7 @@ async fn preload(mut users: Vec) -> Result, DbErr> { .load_many_to_many(entity::team::Entity, entity::user_team::Entity, get_db()) .await? .into_iter() - .map(|teams| { - teams - .into_iter() - .map(Team::from) - .collect::>() - }) + .map(|teams| teams.into_iter().map(Team::from).collect::>()) .collect::>>(); for (i, user) in users.iter_mut().enumerate() { diff --git a/src/main.rs b/src/main.rs index d9bb1f5..3f767d8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,10 +38,10 @@ async fn bootstrap() { logger::init().await; env::init().await; queue::init().await; - db::init().await; cache::init().await; - cluster::init().await; + db::init().await; config::init().await; + cluster::init().await; web::init().await; let addr = format!("{}:{}", env::get_env().axum.host, env::get_env().axum.port); diff --git a/src/media/mod.rs b/src/media/mod.rs index cda9b12..56769fe 100644 --- a/src/media/mod.rs +++ b/src/media/mod.rs @@ -70,7 +70,7 @@ pub async fn delete(path: String, filename: String) -> Result<(), MediaError> { Ok(()) } -pub async fn delete_dir(path: String) -> Result<(), Box> { +pub async fn delete_dir(path: String) -> Result<(), MediaError> { let filepath = PathBuf::from(crate::env::consts::path::MEDIA).join(path); if metadata(&filepath).await.is_ok() { remove_dir_all(&filepath).await?; diff --git a/src/web/middleware/error.rs b/src/web/middleware/error.rs index 14cd278..7f29bfa 100644 --- a/src/web/middleware/error.rs +++ b/src/web/middleware/error.rs @@ -1,5 +1,6 @@ use axum::response::IntoResponse; use serde_json::json; + use crate::web::traits::WebError; pub async fn validation_error(err: validator::ValidationError) -> impl IntoResponse {} diff --git a/src/web/middleware/mod.rs b/src/web/middleware/mod.rs index f973949..4ddd714 100644 --- a/src/web/middleware/mod.rs +++ b/src/web/middleware/mod.rs @@ -1,21 +1,30 @@ pub mod error; use std::net::SocketAddr; -use axum::body::Body; -use axum::extract::{ConnectInfo, Request}; -use axum::http::header::COOKIE; -use axum::middleware::Next; -use axum::response::Response; + +use axum::{ + body::Body, + extract::{ConnectInfo, Request}, + http::header::COOKIE, + middleware::Next, + response::Response, +}; use jsonwebtoken::{decode, DecodingKey, Validation}; use sea_orm::EntityTrait; use serde_json::json; -use crate::db::entity::user::Group; -use crate::db::get_db; -use crate::web; -use crate::web::traits::{Ext, WebError}; + +use crate::{ + db::{entity::user::Group, get_db}, + web, + web::traits::{Ext, WebError}, +}; pub async fn auth(mut req: Request, next: Next) -> Result { - let mut ext = req.extensions().get::().unwrap_or(&Ext::default()).to_owned(); + let mut ext = req + .extensions() + .get::() + .unwrap_or(&Ext::default()) + .to_owned(); let cookies = req .headers() @@ -61,11 +70,17 @@ pub async fn auth(mut req: Request, next: Next) -> Result, next: Next) -> Result { - let mut ext = req.extensions().get::().unwrap_or(&Ext::default()).to_owned(); + let mut ext = req + .extensions() + .get::() + .unwrap_or(&Ext::default()) + .to_owned(); let ConnectInfo(addr) = req.extensions().get::>().unwrap(); @@ -80,4 +95,4 @@ pub async fn network(mut req: Request, next: Next) -> Result Router { Router::new() @@ -61,11 +60,9 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(serde_json::json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && params.is_detailed.unwrap_or(false) { - return Err(WebError::Forbidden(serde_json::json!(""))); + return Err(WebError::Forbidden(json!(""))); } let (mut challenges, total) = crate::db::transfer::challenge::find( @@ -114,9 +111,7 @@ pub struct StatusResult { pub async fn get_status( Extension(ext): Extension, Json(body): Json, ) -> Result>, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(serde_json::json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let mut submissions = crate::db::transfer::submission::get_by_challenge_ids(body.cids.clone()).await?; @@ -266,9 +261,7 @@ pub struct UpdateRequest { pub async fn update( Extension(ext): Extension, Path(id): Path, VJson(mut body): VJson, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -307,9 +300,7 @@ pub async fn update( pub async fn delete( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -327,9 +318,7 @@ pub async fn delete( pub async fn get_attachment( Extension(ext): Extension, Path(id): Path, ) -> Result { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let path = format!("challenges/{}/attachment", id); match crate::media::scan_dir(path.clone()).await?.first() { Some((filename, _size)) => { @@ -350,9 +339,7 @@ pub async fn get_attachment( pub async fn get_attachment_metadata( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let path = format!("challenges/{}/attachment", id); match crate::media::scan_dir(path.clone()).await?.first() { @@ -371,9 +358,7 @@ pub async fn get_attachment_metadata( pub async fn save_attachment( Extension(ext): Extension, Path(id): Path, mut multipart: Multipart, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -408,9 +393,7 @@ pub async fn save_attachment( pub async fn delete_attachment( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } diff --git a/src/web/router/api/config/mod.rs b/src/web/router/api/config/mod.rs index e1f25d4..693ebaf 100644 --- a/src/web/router/api/config/mod.rs +++ b/src/web/router/api/config/mod.rs @@ -7,6 +7,7 @@ use axum::{ }; use sea_orm::{ActiveModelTrait, ActiveValue::Set}; use serde_json::json; + use crate::{ config::get_config, db::{entity::user::Group, get_db}, @@ -29,9 +30,7 @@ pub fn router() -> Router { pub async fn get( Extension(ext): Extension, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -46,9 +45,7 @@ pub async fn get( pub async fn update( Extension(ext): Extension, Json(body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -83,18 +80,14 @@ pub async fn get_icon() -> impl IntoResponse { pub async fn save_icon( Extension(ext): Extension, multipart: Multipart, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } let path = String::from("configs"); let filename = String::from("icon.webp"); let data = handle_image_multipart(multipart).await?; - crate::media::delete(path.clone(), filename.clone()) - .await - .unwrap(); + crate::media::delete(path.clone(), filename.clone()).await?; let data = crate::media::util::img_convert_to_webp(data).await?; crate::media::save(path, filename, data) .await @@ -107,17 +100,13 @@ pub async fn save_icon( } pub async fn delete_icon(Extension(ext): Extension) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } let path = String::from("configs"); let filename = String::from("icon.webp"); - crate::media::delete(path.clone(), filename.clone()) - .await - .unwrap(); + crate::media::delete(path.clone(), filename.clone()).await?; Ok(WebResult { code: StatusCode::OK.as_u16(), diff --git a/src/web/router/api/game/calculator.rs b/src/web/router/api/game/calculator.rs index baceeb3..00cf75b 100644 --- a/src/web/router/api/game/calculator.rs +++ b/src/web/router/api/game/calculator.rs @@ -154,5 +154,5 @@ pub async fn init() { message.ack().await.unwrap(); } }); - info!("game calculator initialized successfully."); + info!("Game calculator initialized successfully."); } diff --git a/src/web/router/api/game/mod.rs b/src/web/router/api/game/mod.rs index 81ade07..749b61c 100644 --- a/src/web/router/api/game/mod.rs +++ b/src/web/router/api/game/mod.rs @@ -1,9 +1,8 @@ pub mod calculator; use axum::{ - body::Body, extract::{DefaultBodyLimit, Multipart, Path, Query}, - http::{Response, StatusCode}, + http::StatusCode, response::IntoResponse, Router, }; @@ -13,16 +12,18 @@ use serde_json::json; use validator::Validate; use crate::{ - db::{entity::user::Group, get_db}, - media::util::hash, + db::{ + entity::{submission::Status, user::Group}, + get_db, + transfer::{GameTeam, Submission}, + }, web::{ - extract::{Extension, Json}, + extract::{Extension, Json, VJson}, model::Metadata, traits::{Ext, WebError, WebResult}, - util::handle_image_multipart, + util, }, }; -use crate::web::extract::VJson; pub async fn router() -> Router { calculator::init().await; @@ -54,10 +55,15 @@ pub async fn router() -> Router { axum::routing::delete(delete_notice), ) .route("/:id/calculate", axum::routing::post(calculate)) - // .route( - // "/:id/submissions", - // get(handler::game::get_submission).layer(from_fn(auth::jwt(Group::User))), - // ) + .route("/:id/scoreboard", axum::routing::get(get_scoreboard)) + .route("/:id/icon", axum::routing::get(get_icon)) + .route( + "/:id/icon", + axum::routing::post(save_icon) + .layer(DefaultBodyLimit::max(3 * 1024 * 1024 /* MB */)), + ) + .route("/:id/icon/metadata", axum::routing::get(get_icon_metadata)) + .route("/:id/icon", axum::routing::delete(delete_icon)) .route("/:id/poster", axum::routing::get(get_poster)) .route( "/:id/poster", @@ -83,9 +89,7 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && !params.is_enabled.unwrap_or(true) { return Err(WebError::Forbidden(json!(""))); } @@ -129,9 +133,7 @@ pub struct CreateRequest { pub async fn create( Extension(ext): Extension, VJson(body): VJson, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -186,9 +188,7 @@ pub struct UpdateRequest { pub async fn update( Extension(ext): Extension, Path(id): Path, VJson(mut body): VJson, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -227,9 +227,7 @@ pub async fn update( pub async fn delete( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -255,9 +253,7 @@ pub struct GetChallengeRequest { pub async fn get_challenge( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let (game_challenges, _) = crate::db::transfer::game_challenge::find( params.game_id, @@ -289,9 +285,7 @@ pub struct CreateChallengeRequest { pub async fn create_challenge( Extension(ext): Extension, Json(body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -336,9 +330,7 @@ pub async fn update_challenge( Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, i64)>, Json(mut body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -372,9 +364,7 @@ pub async fn update_challenge( pub async fn delete_challenge( Extension(ext): Extension, Path((id, challenge_id)): Path<(i64, i64)>, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -399,13 +389,18 @@ pub struct GetTeamRequest { pub async fn get_team( Extension(ext): Extension, Query(params): Query, -) -> Result>, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; +) -> Result>, WebError> { + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; - let (game_teams, total) = - crate::db::transfer::game_team::find(params.game_id, params.team_id).await?; + let (game_teams, total) = crate::db::transfer::game_team::find( + params.game_id, + params.team_id, + None, + None, + None, + None, + ) + .await?; Ok(WebResult { code: StatusCode::OK.as_u16(), @@ -423,10 +418,8 @@ pub struct CreateTeamRequest { pub async fn create_team( Extension(ext): Extension, Json(body): Json, -) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; +) -> Result, WebError> { + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -458,10 +451,8 @@ pub struct UpdateTeamRequest { pub async fn update_team( Extension(ext): Extension, Path((id, team_id)): Path<(i64, i64)>, Json(mut body): Json, -) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; +) -> Result, WebError> { + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -489,9 +480,7 @@ pub async fn update_team( pub async fn delete_team( Extension(ext): Extension, Path((id, team_id)): Path<(i64, i64)>, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -527,9 +516,7 @@ pub async fn delete_notice() -> Result { pub async fn calculate( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -542,112 +529,141 @@ pub async fn calculate( }) } -// pub async fn get_submission( -// Path(id): Path, Query(params): Query, -// ) -> Result { -// let submissions = crate::transfer::submission::get_with_pts(id, -// params.status).await?; - -// return Ok(( -// StatusCode::OK, -// Json(GetSubmissionResponse { -// code: StatusCode::OK.as_u16(), -// data: submissions, -// }), -// )); -// } - -// pub async fn get_scoreboard(Path(id): Path) -> Result { pub struct TeamScoreRecord {} - -// let submissions = -// crate::transfer::submission::get_with_pts(id, -// Some(crate::transfer::submission::Status::Correct)) .await; - -// let game_teams = crate::transfer::game_team::Entity::find() -// .filter( -// Condition::all() -// .add(crate::transfer::game_team::Column::GameId.eq(id)) -// .add(crate::transfer::game_team::Column::IsAllowed.eq(true)), -// ) -// .all(get_db()) -// .await?; - -// return Ok(()); -// } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GetScoreboardRequest { + pub size: u64, + pub page: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct ScoreRecord { + pub game_team: GameTeam, + pub submissions: Vec, +} -pub async fn get_poster(Path(id): Path) -> Result { - let path = format!("games/{}/poster", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, _size)) => { - let buffer = crate::media::get(path, filename.to_string()).await?; - Ok(Response::builder().body(Body::from(buffer)).unwrap()) +pub async fn get_scoreboard( + Path(id): Path, Query(params): Query, +) -> Result>, WebError> { + let (game_teams, total) = crate::db::transfer::game_team::find( + Some(id), + None, + None, + Some("-pts".to_string()), + Some(params.page), + Some(params.size), + ) + .await?; + + let team_ids = game_teams.iter().map(|t| t.team_id).collect::>(); + + let submissions = crate::db::transfer::submission::get_by_game_id_and_team_ids( + id, + team_ids, + Some(Status::Correct), + ) + .await?; + + let mut result: Vec = Vec::new(); + + for game_team in game_teams { + let mut submissions = submissions + .iter() + .filter(|s| s.team_id.unwrap() == game_team.team_id) + .cloned() + .collect::>(); + for submission in submissions.iter_mut() { + submission.flag.clear(); + submission.team = None; + submission.challenge = None; + submission.game = None; } - None => Err(WebError::NotFound(json!(""))), + + result.push(ScoreRecord { + game_team, + submissions, + }); } + + Ok(WebResult { + code: StatusCode::OK.as_u16(), + data: Some(result), + total: Some(total), + ..Default::default() + }) +} + +pub async fn get_poster(Path(id): Path) -> Result { + let path = format!("games/{}/poster", id); + + util::media::get_img(path).await } pub async fn get_poster_metadata(Path(id): Path) -> Result, WebError> { let path = format!("games/{}/poster", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, size)) => Ok(WebResult { - code: StatusCode::OK.as_u16(), - data: Some(Metadata { - filename: filename.to_string(), - size: *size, - }), - ..WebResult::default() - }), - None => Err(WebError::NotFound(json!(""))), - } + + util::media::get_img_metadata(path).await } pub async fn save_poster( Extension(ext): Extension, Path(id): Path, multipart: Multipart, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } let path = format!("games/{}/poster", id); - let data = handle_image_multipart(multipart).await?; - crate::media::delete_dir(path.clone()).await.unwrap(); - - let data = crate::media::util::img_convert_to_webp(data).await?; - let filename = format!("{}.webp", hash(data.clone())); - - crate::media::save(path, filename, data) - .await - .map_err(|_| WebError::InternalServerError(json!("")))?; - - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) + util::media::save_img(path, multipart).await } pub async fn delete_poster( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } let path = format!("games/{}/poster", id); - crate::media::delete_dir(path) - .await - .map_err(|_| WebError::InternalServerError(json!("")))?; + util::media::delete_img(path).await +} - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) +pub async fn get_icon(Path(id): Path) -> Result { + let path = format!("games/{}/icon", id); + + util::media::get_img(path).await +} + +pub async fn get_icon_metadata(Path(id): Path) -> Result, WebError> { + let path = format!("games/{}/icon", id); + + util::media::get_img_metadata(path).await +} + +pub async fn save_icon( + Extension(ext): Extension, Path(id): Path, multipart: Multipart, +) -> Result, WebError> { + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; + if operator.group != Group::Admin { + return Err(WebError::Forbidden(json!(""))); + } + + let path = format!("games/{}/icon", id); + + util::media::save_img(path, multipart).await +} + +pub async fn delete_icon( + Extension(ext): Extension, Path(id): Path, +) -> Result, WebError> { + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; + if operator.group != Group::Admin { + return Err(WebError::Forbidden(json!(""))); + } + + let path = format!("games/{}/icon", id); + + util::media::delete_img(path).await } diff --git a/src/web/router/api/mod.rs b/src/web/router/api/mod.rs index a435841..e9aad74 100644 --- a/src/web/router/api/mod.rs +++ b/src/web/router/api/mod.rs @@ -10,7 +10,8 @@ pub mod user; use axum::{http::StatusCode, response::IntoResponse, Router}; use serde_json::json; -use crate::web::traits::{WebResult}; + +use crate::web::traits::WebResult; pub async fn router() -> Router { Router::new() diff --git a/src/web/router/api/pod/mod.rs b/src/web/router/api/pod/mod.rs index 0f3b87c..378458c 100644 --- a/src/web/router/api/pod/mod.rs +++ b/src/web/router/api/pod/mod.rs @@ -46,9 +46,7 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let (mut pods, total) = crate::db::transfer::pod::find( params.id, @@ -88,9 +86,7 @@ pub struct CreateRequest { pub async fn create( Extension(ext): Extension, Json(mut body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; body.user_id = Some(operator.id); let challenge = crate::db::entity::challenge::Entity::find_by_id(body.challenge_id) @@ -98,9 +94,7 @@ pub async fn create( .await? .map(crate::db::transfer::Challenge::from); - let challenge = challenge.ok_or(WebError::BadRequest(json!( - "challenge_not_found" - )))?; + let challenge = challenge.ok_or(WebError::BadRequest(json!("challenge_not_found")))?; let ctn_name = format!("cds-{}", Uuid::new_v4().simple()); @@ -165,9 +159,7 @@ macro_rules! check_permission { pub async fn renew( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let pod = crate::db::entity::pod::Entity::find() .filter(crate::db::entity::pod::Column::Id.eq(id)) @@ -195,9 +187,7 @@ pub async fn renew( pub async fn stop( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let pod = crate::db::entity::pod::Entity::find_by_id(id) .one(get_db()) diff --git a/src/web/router/api/submission/checker.rs b/src/web/router/api/submission/checker.rs index 3cec6a7..226605a 100644 --- a/src/web/router/api/submission/checker.rs +++ b/src/web/router/api/submission/checker.rs @@ -124,12 +124,14 @@ async fn check(id: i64) { } } - for exist_submission in exist_submissions { - if exist_submission.user_id == submission.user_id - || (submission.game_id.is_some() && exist_submission.team_id == submission.team_id) - { - status = Status::Invalid; - break; + if status == Status::Correct { + for exist_submission in exist_submissions { + if (exist_submission.user_id == submission.user_id + || (submission.game_id.is_some() && exist_submission.team_id == submission.team_id)) + { + status = Status::Invalid; + break; + } } } diff --git a/src/web/router/api/submission/mod.rs b/src/web/router/api/submission/mod.rs index 36c19bc..49235d0 100644 --- a/src/web/router/api/submission/mod.rs +++ b/src/web/router/api/submission/mod.rs @@ -5,9 +5,12 @@ use axum::{ http::StatusCode, Router, }; -use sea_orm::{ActiveModelTrait, ActiveValue::NotSet, EntityTrait, Set}; +use sea_orm::{ + ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, Condition, EntityTrait, QueryFilter, Set, +}; use serde::{Deserialize, Serialize}; use serde_json::json; + use crate::{ db::{ entity::{submission::Status, user::Group}, @@ -45,9 +48,7 @@ pub struct GetRequest { pub async fn get( Extension(ext): Extension, Query(params): Query, ) -> Result>, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + 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!(""))); } @@ -82,9 +83,7 @@ pub async fn get( pub async fn get_by_id( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let submission = crate::db::entity::submission::Entity::find_by_id(id) .one(get_db()) @@ -117,25 +116,23 @@ pub struct CreateRequest { pub async fn create( Extension(ext): Extension, Json(mut body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; body.user_id = Some(operator.id); - if let Some(challenge_id) = body.challenge_id { + if let Some(challenge_id) = body.challenge_id.clone() { let challenge = crate::db::entity::challenge::Entity::find_by_id(challenge_id) .one(get_db()) .await?; if challenge.is_none() { - return Err(WebError::BadRequest(json!( - "challenge_not_found" - ))); + return Err(WebError::BadRequest(json!("challenge_not_found"))); } + } else { + return Err(WebError::BadRequest(json!("challenge_id_required"))); } - if let Some(game_id) = body.game_id { + if let (Some(game_id), Some(team_id)) = (body.game_id, body.team_id) { let game = crate::db::entity::game::Entity::find_by_id(game_id) .one(get_db()) .await?; @@ -143,9 +140,7 @@ pub async fn create( if game.is_none() { return Err(WebError::BadRequest(json!("game_not_found"))); } - } - if let Some(team_id) = body.team_id { let team = crate::db::entity::team::Entity::find_by_id(team_id) .one(get_db()) .await?; @@ -153,6 +148,22 @@ pub async fn create( if team.is_none() { return Err(WebError::BadRequest(json!("team_not_found"))); } + + let game_challenge = crate::db::entity::game_challenge::Entity::find() + .filter( + Condition::all() + .add(crate::db::entity::game_challenge::Column::GameId.eq(game_id)) + .add( + crate::db::entity::game_challenge::Column::ChallengeId + .eq(body.challenge_id.unwrap()), + ), + ) + .one(get_db()) + .await?; + + if game_challenge.is_none() { + return Err(WebError::BadRequest(json!("game_challenge_not_found"))); + } } let submission = crate::db::entity::submission::ActiveModel { @@ -180,9 +191,7 @@ pub async fn create( pub async fn delete( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } diff --git a/src/web/router/api/team/mod.rs b/src/web/router/api/team/mod.rs index 370c697..c82425c 100644 --- a/src/web/router/api/team/mod.rs +++ b/src/web/router/api/team/mod.rs @@ -16,15 +16,13 @@ use validator::Validate; use crate::{ db::{entity::user::Group, get_db}, - media::util::hash, web::{ - extract::{Extension, Json}, + extract::{Extension, Json, VJson}, model::Metadata, traits::{Ext, WebError, WebResult}, - util::handle_image_multipart, + util, }, }; -use crate::web::extract::VJson; pub fn router() -> Router { Router::new() @@ -146,9 +144,7 @@ pub struct UpdateRequest { pub async fn update( Extension(ext): Extension, Path(id): Path, VJson(mut body): VJson, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); @@ -177,9 +173,7 @@ pub async fn update( pub async fn delete( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); @@ -208,9 +202,7 @@ pub struct CreateUserRequest { pub async fn create_user( Extension(ext): Extension, Json(body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin { return Err(WebError::Forbidden(json!(""))); } @@ -231,9 +223,7 @@ pub async fn create_user( pub async fn delete_user( Extension(ext): Extension, Path((id, user_id)): Path<(i64, i64)>, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator.clone(), id) && operator.id != user_id { return Err(WebError::Forbidden(json!(""))); } @@ -253,9 +243,7 @@ pub async fn delete_user( pub async fn get_invite_token( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); @@ -278,9 +266,7 @@ pub async fn get_invite_token( pub async fn update_invite_token( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); } @@ -313,9 +299,7 @@ pub struct JoinRequest { pub async fn join( Extension(ext): Extension, Json(mut body): Json, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; body.user_id = operator.id; @@ -330,9 +314,7 @@ pub async fn join( .ok_or_else(|| WebError::NotFound(json!("invalid_user_or_team")))?; if Some(body.invite_token.clone()) != team.invite_token { - return Err(WebError::BadRequest(json!( - "invalid_invite_token" - ))); + return Err(WebError::BadRequest(json!("invalid_invite_token"))); } let user_team = crate::db::entity::user_team::ActiveModel { @@ -355,78 +337,40 @@ pub async fn leave() -> impl IntoResponse { todo!() } -pub async fn get_avatar_metadata(Path(id): Path) -> Result, WebError> { +pub async fn get_avatar(Path(id): Path) -> Result { let path = format!("teams/{}/avatar", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, size)) => Ok(WebResult { - code: StatusCode::OK.as_u16(), - data: Some(Metadata { - filename: filename.to_string(), - size: *size, - }), - ..WebResult::default() - }), - None => Err(WebError::NotFound(json!(""))), - } + + util::media::get_img(path).await } -pub async fn get_avatar(Path(id): Path) -> Result { +pub async fn get_avatar_metadata(Path(id): Path) -> Result, WebError> { let path = format!("teams/{}/avatar", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, _size)) => { - let buffer = crate::media::get(path, filename.to_string()).await?; - Ok(Response::builder().body(Body::from(buffer)).unwrap()) - } - None => Err(WebError::NotFound(json!(""))), - } + + util::media::get_img_metadata(path).await } pub async fn save_avatar( Extension(ext): Extension, Path(id): Path, multipart: Multipart, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); } let path = format!("teams/{}/avatar", id); - let data = handle_image_multipart(multipart).await?; - crate::media::delete_dir(path.clone()).await.unwrap(); - - let data = crate::media::util::img_convert_to_webp(data).await?; - let filename = format!("{}.webp", hash(data.clone())); - - crate::media::save(path, filename, data) - .await - .map_err(|_| WebError::InternalServerError(json!("")))?; - - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) + util::media::save_img(path, multipart).await } pub async fn delete_avatar( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !can_modify_team(operator, id) { return Err(WebError::Forbidden(json!(""))); } let path = format!("teams/{}/avatar", id); - crate::media::delete_dir(path) - .await - .map_err(|_| WebError::InternalServerError(json!("")))?; - - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) + util::media::delete_img(path).await } diff --git a/src/web/router/api/user/mod.rs b/src/web/router/api/user/mod.rs index aad53a9..805cd35 100644 --- a/src/web/router/api/user/mod.rs +++ b/src/web/router/api/user/mod.rs @@ -3,9 +3,8 @@ use argon2::{ Argon2, PasswordHash, PasswordHasher, PasswordVerifier, }; use axum::{ - body::Body, extract::{DefaultBodyLimit, Multipart, Path, Query}, - http::{header::SET_COOKIE, HeaderMap, Response}, + http::{header::SET_COOKIE, HeaderMap}, response::IntoResponse, Router, }; @@ -24,15 +23,14 @@ use validator::Validate; use crate::{ config, db::{entity::user::Group, get_db}, - media::util::hash, web::{ - extract::{Extension, Json}, + extract::{Extension, Json, VJson}, model::Metadata, traits::{Ext, WebError, WebResult}, - util::{handle_image_multipart, jwt}, + util, + util::jwt, }, }; -use crate::web::extract::VJson; pub fn router() -> Router { Router::new() @@ -155,9 +153,7 @@ pub struct UpdateRequest { pub async fn update( Extension(ext): Extension, Path(id): Path, VJson(mut body): VJson, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; body.id = Some(id); if !(operator.group == Group::Admin || (operator.id == body.id.unwrap_or(0) @@ -197,9 +193,7 @@ pub async fn update( pub async fn delete( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if !(operator.group == Group::Admin || operator.id == id) { return Err(WebError::Forbidden(json!(""))); } @@ -221,9 +215,7 @@ pub async fn delete( pub async fn get_teams( Extension(ext): Extension, Path(id): Path, ) -> Result>, WebError> { - let _ = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let _ = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; let teams = crate::db::transfer::team::find_by_user_id(id).await?; @@ -370,77 +362,38 @@ pub async fn register( pub async fn get_avatar(Path(id): Path) -> Result { let path = format!("users/{}/avatar", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, _size)) => { - let buffer = crate::media::get(path, filename.to_string()).await?; - Ok(Response::builder().body(Body::from(buffer)).unwrap()) - } - None => Err(WebError::NotFound(serde_json::json!(""))), - } + + util::media::get_img(path).await } pub async fn get_avatar_metadata(Path(id): Path) -> Result, WebError> { let path = format!("users/{}/avatar", id); - match crate::media::scan_dir(path.clone()).await?.first() { - Some((filename, size)) => Ok(WebResult { - code: StatusCode::OK.as_u16(), - data: Some(Metadata { - filename: filename.to_string(), - size: *size, - }), - ..WebResult::default() - }), - None => Err(WebError::NotFound(json!(""))), - } + + util::media::get_img_metadata(path).await } pub async fn save_avatar( Extension(ext): Extension, Path(id): Path, multipart: Multipart, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && operator.id != id { return Err(WebError::Forbidden(json!(""))); } let path = format!("users/{}/avatar", id); - let data = handle_image_multipart(multipart).await?; - - crate::media::delete_dir(path.clone()).await.unwrap(); - - let data = crate::media::util::img_convert_to_webp(data).await?; - let filename = format!("{}.webp", hash(data.clone())); - - let _ = crate::media::save(path, filename, data) - .await - .map_err(|_| WebError::InternalServerError(json!(""))); - - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) + util::media::save_img(path, multipart).await } pub async fn delete_avatar( Extension(ext): Extension, Path(id): Path, ) -> Result, WebError> { - let operator = ext - .operator - .ok_or(WebError::Unauthorized(json!("")))?; + let operator = ext.operator.ok_or(WebError::Unauthorized(json!("")))?; if operator.group != Group::Admin && operator.id != id { return Err(WebError::Forbidden(json!(""))); } let path = format!("users/{}/avatar", id); - let _ = crate::media::delete_dir(path) - .await - .map_err(|_| WebError::InternalServerError(json!(""))); - - Ok(WebResult { - code: StatusCode::OK.as_u16(), - ..WebResult::default() - }) + util::media::delete_img(path).await } diff --git a/src/web/router/mod.rs b/src/web/router/mod.rs index f38c5eb..c760bce 100644 --- a/src/web/router/mod.rs +++ b/src/web/router/mod.rs @@ -3,6 +3,7 @@ pub mod metric; use axum::{middleware::from_fn, Router}; use tower_http::trace::TraceLayer; + use crate::web::middleware; pub async fn router() -> Router { diff --git a/src/web/util/media.rs b/src/web/util/media.rs new file mode 100644 index 0000000..6d1b12c --- /dev/null +++ b/src/web/util/media.rs @@ -0,0 +1,69 @@ +use axum::{ + body::Body, + extract::Multipart, + http::{Response, StatusCode}, + response::IntoResponse, +}; +use serde_json::json; + +use crate::{ + media::util::hash, + web::{ + model::Metadata, + traits::{WebError, WebResult}, + util::handle_image_multipart, + }, +}; + +pub async fn get_img(path: String) -> Result { + match crate::media::scan_dir(path.clone()).await?.first() { + Some((filename, _size)) => { + let buffer = crate::media::get(path, filename.to_string()).await?; + Ok(Response::builder().body(Body::from(buffer)).unwrap()) + } + None => Err(WebError::NotFound(json!(""))), + } +} + +pub async fn get_img_metadata(path: String) -> Result, WebError> { + match crate::media::scan_dir(path.clone()).await?.first() { + Some((filename, size)) => Ok(WebResult { + code: StatusCode::OK.as_u16(), + data: Some(Metadata { + filename: filename.to_string(), + size: *size, + }), + ..WebResult::default() + }), + None => Err(WebError::NotFound(json!(""))), + } +} + +pub async fn save_img(path: String, multipart: Multipart) -> Result, WebError> { + let data = handle_image_multipart(multipart).await?; + + crate::media::delete_dir(path.clone()).await?; + + let data = crate::media::util::img_convert_to_webp(data).await?; + let filename = format!("{}.webp", hash(data.clone())); + + crate::media::save(path, filename, data) + .await + .map_err(|_| WebError::InternalServerError(json!("")))?; + + Ok(WebResult { + code: StatusCode::OK.as_u16(), + ..WebResult::default() + }) +} + +pub async fn delete_img(path: String) -> Result, WebError> { + crate::media::delete_dir(path) + .await + .map_err(|_| WebError::InternalServerError(json!("")))?; + + Ok(WebResult { + code: StatusCode::OK.as_u16(), + ..WebResult::default() + }) +} diff --git a/src/web/util/mod.rs b/src/web/util/mod.rs index cdcb71c..6df4951 100644 --- a/src/web/util/mod.rs +++ b/src/web/util/mod.rs @@ -1,10 +1,12 @@ use axum::extract::Multipart; use mime::Mime; use serde_json::json; + use crate::web::traits::WebError; pub mod jwt; pub mod math; +pub mod media; pub async fn handle_image_multipart(mut multipart: Multipart) -> Result, WebError> { while let Some(field) = multipart.next_field().await.unwrap() { @@ -12,9 +14,7 @@ pub async fn handle_image_multipart(mut multipart: Multipart) -> Result, let content_type = field.content_type().unwrap().to_string(); let mime: Mime = content_type.parse().unwrap(); if mime.type_() != mime::IMAGE { - return Err(WebError::BadRequest(json!( - "forbidden_file_type" - ))); + return Err(WebError::BadRequest(json!("forbidden_file_type"))); } let data = match field.bytes().await { Ok(bytes) => bytes.to_vec(),