diff --git a/build.rs b/build.rs index f380587770f575569db6e552a52840f1c2258d25..e237713f8fecef134f8bf735e90d3560c982d26b 100644 --- a/build.rs +++ b/build.rs @@ -10,6 +10,8 @@ fn main() -> Result<()> { .parent() .expect("proto file should reside in a directory"); - tonic_build::configure().protoc_arg("--experimental_allow_proto3_optional").compile(&[proto_path], &[proto_dir])?; + tonic_build::configure() + .protoc_arg("--experimental_allow_proto3_optional") + .compile(&[proto_path], &[proto_dir])?; Ok(()) } diff --git a/src/broadcaster.rs b/src/broadcaster.rs index 7eceb55eecbcda18ba1ef9e99cc7845edf72bf88..c71b9685e6134516d0a2d9c717354dc241ab34f1 100644 --- a/src/broadcaster.rs +++ b/src/broadcaster.rs @@ -1,3 +1,4 @@ +use crate::queue::job_protocol::{job_result, JobResult}; use actix_web::rt::time::{interval_at, Instant}; use actix_web::web::{Bytes, Data}; use actix_web::Error; @@ -6,9 +7,8 @@ use std::pin::Pin; use std::sync::Mutex; use std::task::{Context, Poll}; use std::time::Duration; -use tokio::sync::mpsc::{channel, Receiver, Sender}; use tokio::sync::broadcast; -use crate::queue::job_protocol::{JobResult, job_result}; +use tokio::sync::mpsc::{channel, Receiver, Sender}; pub struct Broadcaster { clients: Vec<Sender<Bytes>>, @@ -28,14 +28,18 @@ impl Broadcaster { } } - fn spawn_receiver(me: Data<Mutex<Self>>, mut job_result_receiver: broadcast::Receiver<JobResult>) { + fn spawn_receiver( + me: Data<Mutex<Self>>, + mut job_result_receiver: broadcast::Receiver<JobResult>, + ) { actix_web::rt::spawn(async move { loop { let job_result = job_result_receiver.recv().await.unwrap(); if let JobResult { which: Some(job_result::Which::Judgement(_judgement)), .. - } = job_result { + } = job_result + { me.lock().unwrap().send("update_submission", ""); } } diff --git a/src/import_contest.rs b/src/import_contest.rs index 9a637e72b88cf736faf1ba605c7846a1df791380..2d77f528dd2b35504bc8386a07a108ad6cb403bc 100644 --- a/src/import_contest.rs +++ b/src/import_contest.rs @@ -1,8 +1,8 @@ use lazy_static::lazy_static; +use regex::Captures; use regex::Regex; use std::io::{Read, Seek}; use zip::ZipArchive; -use regex::Captures; mod error { use quick_xml::de::DeError; @@ -418,11 +418,9 @@ pub fn format_width(pattern_path: &String, i: usize) -> String { lazy_static! { static ref WIDTH_REGEX: Regex = Regex::new(r"%0(\d)+d").unwrap(); } - WIDTH_REGEX.replace( - pattern_path, - |caps: &Captures| { + WIDTH_REGEX + .replace(pattern_path, |caps: &Captures| { return format!("{:0width$}", i, width = caps[1].parse().unwrap()); - }, - ).into() + }) + .into() } - diff --git a/src/language.rs b/src/language.rs index 9ee99ad2feab67b6c27180f28f1ac4337aa8b07b..2272ae6f30db073f05a9ea8a89ae8d4370fab577 100644 --- a/src/language.rs +++ b/src/language.rs @@ -1,10 +1,10 @@ use crate::queue::job_protocol::job_result; use crate::queue::job_protocol::{job, Job, JobResult}; use async_channel::Sender; +use log::info; +use thiserror::Error; use tokio::sync::broadcast; use uuid::Uuid; -use thiserror::Error; -use log::info; #[derive(Error, Debug, Clone)] #[error("job failed")] @@ -24,18 +24,20 @@ pub async fn run_cached( let uuid = Uuid::new_v4(); let mut job_result_receiver = job_result_sender.subscribe(); info!("Sending job"); - job_sender.send(Job { - uuid: uuid.to_string(), - language: language.to_string(), - memory_limit_kib, - time_limit_ms, - which: Some(job::Which::RunCached(job::RunCached { - source_path, - arguments, - stdin_path, - stdout_path, - })) - }).await?; + job_sender + .send(Job { + uuid: uuid.to_string(), + language: language.to_string(), + memory_limit_kib, + time_limit_ms, + which: Some(job::Which::RunCached(job::RunCached { + source_path, + arguments, + stdin_path, + stdout_path, + })), + }) + .await?; info!("Sent"); loop { @@ -45,7 +47,8 @@ pub async fn run_cached( if let JobResult { which: Some(job_result::Which::RunCached(compile)), .. - } = job_result { + } = job_result + { return Ok(compile); } return Err(Box::new(JobFailedError)); @@ -67,19 +70,21 @@ pub async fn judge( ) -> Result<job_result::Judgement, Box<dyn std::error::Error>> { let uuid = Uuid::new_v4(); let mut job_result_receiver = job_result_sender.subscribe(); - job_sender.send(Job { - uuid: uuid.to_string(), - language: language.to_string(), - memory_limit_kib, - time_limit_ms, - which: Some(job::Which::Judgement(job::Judgement { - source_text, - test_count, - test_pattern, - checker_language, - checker_source_path, - })) - }).await?; + job_sender + .send(Job { + uuid: uuid.to_string(), + language: language.to_string(), + memory_limit_kib, + time_limit_ms, + which: Some(job::Which::Judgement(job::Judgement { + source_text, + test_count, + test_pattern, + checker_language, + checker_source_path, + })), + }) + .await?; loop { let job_result = job_result_receiver.recv().await?; @@ -87,7 +92,8 @@ pub async fn judge( if let JobResult { which: Some(job_result::Which::Judgement(compile)), .. - } = job_result { + } = job_result + { return Ok(compile); } return Err(Box::new(JobFailedError)); diff --git a/src/lib.rs b/src/lib.rs index a5fc4f300f594fa0b7c167baf4e95ca679ac26f5..4b4c22247a98385c56f8875fb05925436426aed6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ -mod queue; pub mod import_contest; +mod queue; pub mod job_protocol { pub use crate::queue::job_protocol::*; diff --git a/src/main.rs b/src/main.rs index 97a7b738c90931e01febfe78b1826ad68e0ecc0a..05244235292bf34b784120d3381a48b316e82882 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,90 +3,106 @@ use serde::{Deserialize, Serialize}; use actix_files::Files; use actix_identity::Identity; -use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; -use actix_web::{dev, get, http, middleware, post, web, App, HttpServer}; -use diesel::pg::PgConnection; -use std::env; -use std::fs::File; -use uuid::Uuid; use actix_identity::IdentityMiddleware; -use actix_session::Session; use actix_session::storage::CookieSessionStore; +use actix_session::Session; use actix_session::SessionMiddleware; +use actix_web::middleware::{ErrorHandlerResponse, ErrorHandlers}; use actix_web::HttpResponse; +use actix_web::{dev, get, http, middleware, post, web, App, HttpServer}; use chrono::prelude::*; +use diesel::pg::PgConnection; use diesel::r2d2::ConnectionManager; use handlebars::Handlebars; use log::{error, info}; use models::submission; use models::user; +use std::env; +use std::fs::File; +use uuid::Uuid; mod broadcaster; mod import_contest; +mod language; mod models; +mod queue; mod schema; mod setup; -mod queue; -mod language; use broadcaster::Broadcaster; use listenfd::ListenFd; use std::sync::Mutex; type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>; +use actix_web::web::Data; +use actix_web::HttpMessage; +use async_channel::Sender; use chrono_tz::Tz; -use std::time::Duration; -use queue::{JobQueuer}; +use futures::TryFutureExt; use queue::job_protocol::job_queue_server::JobQueueServer; -use queue::job_protocol::{Language, job, Job, JobResult, job_result}; -use tonic::transport::Server; +use queue::job_protocol::{job, job_result, Job, JobResult, Language}; +use queue::JobQueuer; use std::error::Error; -use async_channel::Sender; -use futures::TryFutureExt; -use actix_web::web::Data; -use tokio::sync::broadcast; use std::fs; +use std::time::Duration; use submission::SubmissionCompletion; -use actix_web::HttpMessage; +use tokio::sync::broadcast; +use tonic::transport::Server; -async fn update_database(mut job_result_receiver: broadcast::Receiver<JobResult>, pool: DbPool) -> Result<(), PostError> { +async fn update_database( + mut job_result_receiver: broadcast::Receiver<JobResult>, + pool: DbPool, +) -> Result<(), PostError> { loop { let job_result = job_result_receiver.recv().await.unwrap(); if let JobResult { which: Some(job_result::Which::Judgement(judgement)), .. - } = job_result { + } = job_result + { let mut connection = pool.get().unwrap(); - submission::complete_submission(&mut connection, SubmissionCompletion { - uuid: job_result.uuid, - verdict: match job_result::judgement::Verdict::from_i32(judgement.verdict) { - Some(job_result::judgement::Verdict::Accepted) => "AC".into(), - Some(job_result::judgement::Verdict::WrongAnswer) => "WA".into(), - Some(job_result::judgement::Verdict::CompilationError) => "CE".into(), - Some(job_result::judgement::Verdict::TimeLimitExceeded) => "TL".into(), - Some(job_result::judgement::Verdict::MemoryLimitExceeded) => "ML".into(), - Some(job_result::judgement::Verdict::RuntimeError) => "RE".into(), - None => "XX".into(), + submission::complete_submission( + &mut connection, + SubmissionCompletion { + uuid: job_result.uuid, + verdict: match job_result::judgement::Verdict::from_i32(judgement.verdict) { + Some(job_result::judgement::Verdict::Accepted) => "AC".into(), + Some(job_result::judgement::Verdict::WrongAnswer) => "WA".into(), + Some(job_result::judgement::Verdict::CompilationError) => "CE".into(), + Some(job_result::judgement::Verdict::TimeLimitExceeded) => "TL".into(), + Some(job_result::judgement::Verdict::MemoryLimitExceeded) => "ML".into(), + Some(job_result::judgement::Verdict::RuntimeError) => "RE".into(), + None => "XX".into(), + }, + judge_start_instant: chrono::NaiveDateTime::parse_from_str( + &judgement.judge_start_instant, + "%Y-%m-%dT%H:%M:%S%.f", + ) + .unwrap(), + judge_end_instant: chrono::NaiveDateTime::parse_from_str( + &judgement.judge_end_instant, + "%Y-%m-%dT%H:%M:%S%.f", + ) + .unwrap(), + memory_kib: Some(judgement.memory_kib), + time_ms: Some(judgement.time_ms), + time_wall_ms: Some(judgement.time_wall_ms), + error_output: Some(judgement.error_output), + failed_test: match judgement.failed_test { + 0 => None, + test => Some(test), + }, }, - judge_start_instant: chrono::NaiveDateTime::parse_from_str(&judgement.judge_start_instant, "%Y-%m-%dT%H:%M:%S%.f").unwrap(), - judge_end_instant: chrono::NaiveDateTime::parse_from_str(&judgement.judge_end_instant, "%Y-%m-%dT%H:%M:%S%.f").unwrap(), - memory_kib: Some(judgement.memory_kib), - time_ms: Some(judgement.time_ms), - time_wall_ms: Some(judgement.time_wall_ms), - error_output: Some(judgement.error_output), - failed_test: match judgement.failed_test { - 0 => None, - test => Some(test), - } - }).unwrap(); + ) + .unwrap(); } } } use base64; -use diesel_migrations::{embed_migrations, EmbeddedMigrations}; -use diesel_migrations::MigrationHarness; use actix_web::cookie::Key; +use diesel_migrations::MigrationHarness; +use diesel_migrations::{embed_migrations, EmbeddedMigrations}; pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); use actix_web_flash_messages::storage::CookieMessageStore; @@ -97,8 +113,11 @@ async fn main() -> Result<(), Box<dyn Error>> { std::env::set_var("RUST_LOG", "info"); env_logger::init(); - let private_key = base64::decode(env::var("IDENTITY_SECRET_KEY") - .expect("IDENTITY_SECRET_KEY environment variable is not set")).unwrap(); + let private_key = base64::decode( + env::var("IDENTITY_SECRET_KEY") + .expect("IDENTITY_SECRET_KEY environment variable is not set"), + ) + .unwrap(); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL environment variable is not set"); @@ -109,7 +128,10 @@ async fn main() -> Result<(), Box<dyn Error>> { .build(manager) .expect("Failed to create pool."); - pool.get().expect("couldn't get connection from pool").run_pending_migrations(MIGRATIONS).unwrap(); + pool.get() + .expect("couldn't get connection from pool") + .run_pending_migrations(MIGRATIONS) + .unwrap(); setup::setup_admin(&mut pool.get().expect("couldn't get connection from the pool")); let mut handlebars = Handlebars::new(); @@ -128,21 +150,26 @@ async fn main() -> Result<(), Box<dyn Error>> { let (job_sender, job_receiver) = async_channel::unbounded(); let (job_result_sender, job_result_receiver) = broadcast::channel(40); - for (submission, metadata) in submission::get_waiting_judge_submissions(&mut pool.get().expect("couldn't get connection from pool"))? { - job_sender.send(Job { - uuid: submission.uuid, - language: submission.language, - time_limit_ms: metadata.time_limit_ms, - memory_limit_kib: metadata.memory_limit_bytes / 1_024, - - which: Some(job::Which::Judgement(job::Judgement { - source_text: submission.source_text.into(), - test_count: metadata.test_count, - test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), - checker_language: metadata.checker_language, - checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path).into(), - })) - }).await?; + for (submission, metadata) in submission::get_waiting_judge_submissions( + &mut pool.get().expect("couldn't get connection from pool"), + )? { + job_sender + .send(Job { + uuid: submission.uuid, + language: submission.language, + time_limit_ms: metadata.time_limit_ms, + memory_limit_kib: metadata.memory_limit_bytes / 1_024, + + which: Some(job::Which::Judgement(job::Judgement { + source_text: submission.source_text.into(), + test_count: metadata.test_count, + test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), + checker_language: metadata.checker_language, + checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path) + .into(), + })), + }) + .await?; } let broadcaster = Broadcaster::create(job_result_receiver); @@ -161,41 +188,49 @@ async fn main() -> Result<(), Box<dyn Error>> { .app_data(Data::new(tz.clone())) .wrap(ErrorHandlers::new().handler(http::StatusCode::UNAUTHORIZED, render_401)) .wrap(ErrorHandlers::new().handler(http::StatusCode::BAD_REQUEST, render_400)) - .wrap(FlashMessagesFramework::builder(CookieMessageStore::builder(Key::from(&private_key)).build()).minimum_level(Level::Info).build()) + .wrap( + FlashMessagesFramework::builder( + CookieMessageStore::builder(Key::from(&private_key)).build(), + ) + .minimum_level(Level::Info) + .build(), + ) .wrap(IdentityMiddleware::default()) - .wrap(SessionMiddleware::builder( - CookieSessionStore::default(), Key::from(&private_key)) + .wrap( + SessionMiddleware::builder(CookieSessionStore::default(), Key::from(&private_key)) .cookie_name("user".into()) .cookie_secure(false) - .build() + .build(), ) .wrap(middleware::Logger::default()) .app_data(broadcaster.clone()) .app_data(handlebars_ref.clone()) - .service(web::scope("/jughisto") - .service(get_login) - .service(get_me) - .service(change_password) - .service(post_login) - .service(post_logout) - .service(get_main) - .service(get_contests) - .service(get_contest_by_id) - .service(get_contest_scoreboard_by_id) - .service(get_contest_problem_by_id_label) - .service(get_submissions_me) - .service(get_submission) - .service(get_submissions) - .service(get_submissions_me_by_contest_id) - .service(get_submissions_me_by_contest_id_problem_label) - .service(rejudge_submission) - .service(create_submission) - .service(create_contest) - .service(create_user) - .service(impersonate_user) - .service(submission_updates) - .service(Files::new("/static/", "./static/")) - .service(get_problem_by_id_assets)) + .service( + web::scope("/jughisto") + .service(get_login) + .service(get_me) + .service(change_password) + .service(post_login) + .service(post_logout) + .service(get_main) + .service(get_contests) + .service(get_contest_by_id) + .service(get_contest_scoreboard_by_id) + .service(get_contest_problem_by_id_label) + .service(get_submissions_me) + .service(get_submission) + .service(get_submissions) + .service(get_submissions_me_by_contest_id) + .service(get_submissions_me_by_contest_id_problem_label) + .service(rejudge_submission) + .service(create_submission) + .service(create_contest) + .service(create_user) + .service(impersonate_user) + .service(submission_updates) + .service(Files::new("/static/", "./static/")) + .service(get_problem_by_id_assets), + ) }); server = if let Some(l) = listenfd @@ -221,8 +256,11 @@ async fn main() -> Result<(), Box<dyn Error>> { })) .serve(addr) .map_err(|e| Into::<Box<dyn Error>>::into(e)), - update_database(update_dabase_sender.subscribe(), pool.clone()) - .map_err(|e| Into::<Box<dyn Error>>::into(e)) + update_database(update_dabase_sender.subscribe(), pool.clone()).map_err(|e| Into::< + Box<dyn Error>, + >::into( + e + )) )?; Ok(()) @@ -350,8 +388,7 @@ async fn get_login( &LoginContext { logged_user, flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set") + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), }, )?)) } @@ -374,8 +411,7 @@ async fn get_me( &MeContext { logged_user, flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set") + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), }, )?)) } @@ -386,42 +422,48 @@ use actix_files::NamedFile; async fn get_problem_by_id_assets( identity: Identity, pool: web::Data<DbPool>, - path: web::Path<(i32, String,)>, + path: web::Path<(i32, String)>, ) -> Result<NamedFile, GetError> { let logged_user = require_identity(&identity)?; let path = path.into_inner(); let mut connection = pool.get()?; let problem = problem::get_problem_by_contest_id_metadata(&mut connection, path.0)?; let contest = contest::get_contest_by_id(&mut connection, problem.contest_id)?; - if contest.start_instant.map(|s| s > Local::now().naive_utc()).unwrap_or(false) && !logged_user.is_admin { + if contest + .start_instant + .map(|s| s > Local::now().naive_utc()) + .unwrap_or(false) + && !logged_user.is_admin + { return Err(GetError::Unauthorized(UnauthorizedError {})); } - let file_path = PathBuf::from("/data/").join(problem.id).join("statements/.html/portuguese/").join(path.1); + let file_path = PathBuf::from("/data/") + .join(problem.id) + .join("statements/.html/portuguese/") + .join(path.1); Ok(NamedFile::open(file_path)?) } -use actix_web_flash_messages::FlashMessage; -use actix_web::http::header::HeaderValue; -use actix_web::http::header::ContentType; use actix_web::http::header; +use actix_web::http::header::ContentType; +use actix_web::http::header::HeaderValue; +use actix_web_flash_messages::FlashMessage; -fn render_401<B>( - mut res: dev::ServiceResponse<B>, -) -> actix_web::Result<ErrorHandlerResponse<B>> { +fn render_401<B>(mut res: dev::ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>> { FlashMessage::error("Você precisa estar logado para acessar esta página").send(); res.response_mut().headers_mut().insert( header::LOCATION, - HeaderValue::from_str(&format!("{}login", - &env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"))).unwrap() + HeaderValue::from_str(&format!( + "{}login", + &env::var("BASE_URL").expect("BASE_URL environment variable is not set") + )) + .unwrap(), ); *res.response_mut().status_mut() = StatusCode::SEE_OTHER; Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } -fn render_400<B>( - res: dev::ServiceResponse<B>, -) -> actix_web::Result<ErrorHandlerResponse<B>> { +fn render_400<B>(res: dev::ServiceResponse<B>) -> actix_web::Result<ErrorHandlerResponse<B>> { FlashMessage::error("Entrada inválida").send(); Ok(ErrorHandlerResponse::Response(res.map_into_left_body())) } @@ -437,7 +479,9 @@ use models::problem::ProblemByContest; fn get_identity(identity: &Identity) -> Option<LoggedUser> { let identity = identity.id(); - identity.ok().and_then(|identity| serde_json::from_str(&identity).ok()) + identity + .ok() + .and_then(|identity| serde_json::from_str(&identity).ok()) } fn require_identity(identity: &Identity) -> Result<LoggedUser, UnauthorizedError> { @@ -445,15 +489,20 @@ fn require_identity(identity: &Identity) -> Result<LoggedUser, UnauthorizedError } fn format_duration(duration: chrono::Duration) -> String { - format!("{}{}{:02}:{:02}", - if duration.num_milliseconds() >= 0 { "" } else { "-" }, + format!( + "{}{}{:02}:{:02}", + if duration.num_milliseconds() >= 0 { + "" + } else { + "-" + }, if duration.num_days() != 0 { format!("{}:", duration.num_days().abs()) } else { "".into() }, - duration.num_hours().abs()%24, - duration.num_minutes().abs()%60 + duration.num_hours().abs() % 24, + duration.num_minutes().abs() % 60 ) } @@ -498,42 +547,61 @@ async fn get_contest_by_id( let mut connection = pool.get()?; let contest = contest::get_contest_by_id(&mut connection, path.0)?; - if contest.start_instant.map(|s| s > Local::now().naive_utc()).unwrap_or(false) && !logged_user.is_admin { - return Ok(redirect_to_referer("Essa competição ainda não começou".into(), &request)); + if contest + .start_instant + .map(|s| s > Local::now().naive_utc()) + .unwrap_or(false) + && !logged_user.is_admin + { + return Ok(redirect_to_referer( + "Essa competição ainda não começou".into(), + &request, + )); } - let problems = problem::get_problems_user_by_contest_id_with_score(&mut connection, logged_user.id, path.0)?; - let submissions = submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; + let problems = problem::get_problems_user_by_contest_id_with_score( + &mut connection, + logged_user.id, + path.0, + )?; + let submissions = + submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "contest", &ContestContext { contest: get_formatted_contest(&tz, &contest), - problems: problems.iter().map(|p| FormattedProblemByContestWithScore { - first_ac_submission_time: p.first_ac_submission_instant - .map(|t| match contest.start_instant { - Some(cs) => format_duration(t - cs), - None => "*".into() - }).unwrap_or("".into()), - failed_submissions: p.failed_submissions, - id: p.id, - name: p.name.clone(), - label: p.label.clone(), - memory_limit_mib: p.memory_limit_bytes / 1_024 / 1_024, - time_limit: format!("{}", f64::from(p.time_limit_ms) / 1000.0).replacen(".", ",", 1), - user_accepted_count: p.user_accepted_count - }).collect(), + problems: problems + .iter() + .map(|p| FormattedProblemByContestWithScore { + first_ac_submission_time: p + .first_ac_submission_instant + .map(|t| match contest.start_instant { + Some(cs) => format_duration(t - cs), + None => "*".into(), + }) + .unwrap_or("".into()), + failed_submissions: p.failed_submissions, + id: p.id, + name: p.name.clone(), + label: p.label.clone(), + memory_limit_mib: p.memory_limit_bytes / 1_024 / 1_024, + time_limit: format!("{}", f64::from(p.time_limit_ms) / 1000.0) + .replacen(".", ",", 1), + user_accepted_count: p.user_accepted_count, + }) + .collect(), logged_user, flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), submissions: submissions .iter() .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } use itertools::Itertools; @@ -586,12 +654,21 @@ async fn get_contest_scoreboard_by_id( let mut connection = pool.get()?; let contest = contest::get_contest_by_id(&mut connection, path.0)?; - if contest.start_instant.map(|s| s > Local::now().naive_utc()).unwrap_or(false) && !logged_user.is_admin { - return Ok(redirect_to_referer("Essa competição ainda não começou".into(), &request)); + if contest + .start_instant + .map(|s| s > Local::now().naive_utc()) + .unwrap_or(false) + && !logged_user.is_admin + { + return Ok(redirect_to_referer( + "Essa competição ainda não começou".into(), + &request, + )); } let scores = problem::get_problems_by_contest_id_with_score(&mut connection, path.0)?; - let submissions = submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; + let submissions = + submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; let mut scores: Vec<_> = scores.into_iter().group_by(|e| e.user_name.as_ref().map(|s| s.clone())).into_iter().map(|(user_name, problems)| { let problems: Vec<_> = problems.map(|p| FormattedProblemByContestWithScore { first_ac_submission_time: p.first_ac_submission_instant @@ -624,9 +701,22 @@ async fn get_contest_scoreboard_by_id( problems } }).collect(); - scores.sort_by(|a, b| (a.user_name != None, -a.solved_count, a.penalty, &a.user_name).cmp(&(b.user_name != None, -b.solved_count, b.penalty, &b.user_name))); + scores.sort_by(|a, b| { + ( + a.user_name != None, + -a.solved_count, + a.penalty, + &a.user_name, + ) + .cmp(&( + b.user_name != None, + -b.solved_count, + b.penalty, + &b.user_name, + )) + }); - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "scoreboard", &ScoreboardContext { @@ -634,14 +724,14 @@ async fn get_contest_scoreboard_by_id( scores, logged_user, flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), submissions: submissions .iter() .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[get("/contests/{id}/{label}")] @@ -652,7 +742,7 @@ async fn get_contest_problem_by_id_label( hb: web::Data<Handlebars<'_>>, languages: web::Data<Arc<DashMap<String, Language>>>, session: Session, - path: web::Path<(i32,String)>, + path: web::Path<(i32, String)>, tz: web::Data<Tz>, request: HttpRequest, ) -> GetResult { @@ -680,7 +770,13 @@ async fn get_contest_problem_by_id_label( let mut languages = languages .iter() - .filter(|kv| if logged_user.is_admin { true } else { kv.key() == "cpp.17.g++" }) + .filter(|kv| { + if logged_user.is_admin { + true + } else { + kv.key() == "cpp.17.g++" + } + }) .map(|kv| LanguageContext { order: kv.value().order, value: kv.key().into(), @@ -692,14 +788,27 @@ async fn get_contest_problem_by_id_label( let mut connection = pool.get()?; let path = path.into_inner(); let contest = contest::get_contest_by_id(&mut connection, path.0)?; - if contest.start_instant.map(|s| s > Local::now().naive_utc()).unwrap_or(false) && !logged_user.is_admin { - return Ok(redirect_to_referer("Essa competição ainda não começou".into(), &request)); + if contest + .start_instant + .map(|s| s > Local::now().naive_utc()) + .unwrap_or(false) + && !logged_user.is_admin + { + return Ok(redirect_to_referer( + "Essa competição ainda não começou".into(), + &request, + )); } let problems = problem::get_problems_by_contest_id(&mut connection, path.0)?; let problem = problem::get_problem_by_contest_id_label(&mut connection, path.0, &path.1)?; - let submissions = submission::get_submissions_user_by_contest_problem(&mut connection, logged_user.id, path.0, &path.1)?; + let submissions = submission::get_submissions_user_by_contest_problem( + &mut connection, + logged_user.id, + path.0, + &path.1, + )?; - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "contest_problem", &ContestProblemContext { @@ -708,8 +817,7 @@ async fn get_contest_problem_by_id_label( problems, problem, flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), language: session.get("language")?, logged_user, submissions: submissions @@ -717,7 +825,8 @@ async fn get_contest_problem_by_id_label( .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[derive(Serialize, Deserialize, Clone)] @@ -728,12 +837,17 @@ struct LoggedUser { } #[post("/logout")] -async fn post_logout( - identity: Identity, -) -> PostResult { +async fn post_logout(identity: Identity) -> PostResult { identity.logout(); - Ok(HttpResponse::SeeOther().append_header( - (header::LOCATION, HeaderValue::from_str(&env::var("BASE_URL").expect("BASE_URL environment variable is not set")).unwrap())).finish()) + Ok(HttpResponse::SeeOther() + .append_header(( + header::LOCATION, + HeaderValue::from_str( + &env::var("BASE_URL").expect("BASE_URL environment variable is not set"), + ) + .unwrap(), + )) + .finish()) } #[post("/login")] @@ -746,14 +860,15 @@ async fn post_login( use user::PasswordMatched; use user::UserHashingError; - match web::block(move || user::check_matching_password(&mut connection, &form.name, &form.password)) - .await - .map_err(|e| PostError::Web(e.into()))?.map_err(|e| match e { - UserHashingError::Database(e) => PostError::Database(e), - UserHashingError::Hash(_) => { - PostError::Validation("Senha inválida".into()) - }, - })? { + match web::block(move || { + user::check_matching_password(&mut connection, &form.name, &form.password) + }) + .await + .map_err(|e| PostError::Web(e.into()))? + .map_err(|e| match e { + UserHashingError::Database(e) => PostError::Database(e), + UserHashingError::Hash(_) => PostError::Validation("Senha inválida".into()), + })? { PasswordMatched::UserDoesntExist => { Err(PostError::Validation("Usuário inexistente".into())) } @@ -769,9 +884,17 @@ async fn post_login( is_admin: logged_user.is_admin, }) .map_err(|_| PostError::Custom("Usuário no banco de dados inconsistente".into()))?, - ).map_err(|_| PostError::Custom("Impossível fazer login".into()))?; - Ok(HttpResponse::SeeOther().append_header((header::LOCATION, HeaderValue::from_str(&env::var("BASE_URL") - .expect("BASE_URL environment variable is not set")).unwrap())).finish()) + ) + .map_err(|_| PostError::Custom("Impossível fazer login".into()))?; + Ok(HttpResponse::SeeOther() + .append_header(( + header::LOCATION, + HeaderValue::from_str( + &env::var("BASE_URL").expect("BASE_URL environment variable is not set"), + ) + .unwrap(), + )) + .finish()) } } } @@ -829,7 +952,7 @@ fn format_submission( memory: match submission.memory_kib { None | Some(0) => "".into(), Some(k) if k < 1_024 => format!("{}KiB", k), - Some(k) => format!("{}MiB", k / 1_024) + Some(k) => format!("{}MiB", k / 1_024), }, failed_test: submission.failed_test, } @@ -867,7 +990,8 @@ async fn get_submissions( .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[get("/submissions/me/")] @@ -888,7 +1012,7 @@ async fn get_submissions_me( let submissions = submission::get_submissions_user(&mut connection, logged_user.id)?; - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "submissions", &SubmissionsContext { @@ -898,7 +1022,8 @@ async fn get_submissions_me( .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[get("/submissions/{uuid}")] @@ -936,36 +1061,34 @@ async fn get_submission( } let path = path.into_inner(); - let (submission, user, contest_problem, contest) - = submission::get_submission_by_uuid(&mut connection, path.0)?; + let (submission, user, contest_problem, contest) = + submission::get_submission_by_uuid(&mut connection, path.0)?; if user.id != logged_user.id && !logged_user.is_admin { return Err(GetError::Unauthorized(UnauthorizedError {})); } - Ok(HttpResponse::Ok().body( - hb.render( - "submission", - &SubmissionContext { - logged_user, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), - submission: Submission { - uuid: submission.uuid, - verdict: submission.verdict, - source_text: submission.source_text, - language: submission.language, - memory_kib: submission.memory_kib, - time_ms: submission.time_ms, - time_wall_ms: submission.time_wall_ms, - error_output: submission.error_output, - user_name: user.name, - problem_label: contest_problem.label, - contest_name: contest.name, - failed_test: submission.failed_test, - } + Ok(HttpResponse::Ok().body(hb.render( + "submission", + &SubmissionContext { + logged_user, + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), + submission: Submission { + uuid: submission.uuid, + verdict: submission.verdict, + source_text: submission.source_text, + language: submission.language, + memory_kib: submission.memory_kib, + time_ms: submission.time_ms, + time_wall_ms: submission.time_wall_ms, + error_output: submission.error_output, + user_name: user.name, + problem_label: contest_problem.label, + contest_name: contest.name, + failed_test: submission.failed_test, }, - )?)) + }, + )?)) } #[get("/submissions/me/contests/{id}")] @@ -986,9 +1109,10 @@ async fn get_submissions_me_by_contest_id( } let path = path.into_inner(); - let submissions = submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; + let submissions = + submission::get_submissions_user_by_contest(&mut connection, logged_user.id, path.0)?; - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "submissions", &SubmissionsContext { @@ -998,7 +1122,8 @@ async fn get_submissions_me_by_contest_id( .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[get("/submissions/me/contests/{id}/{label}")] @@ -1019,9 +1144,14 @@ async fn get_submissions_me_by_contest_id_problem_label( } let path = path.into_inner(); - let submissions = submission::get_submissions_user_by_contest_problem(&mut connection, logged_user.id, path.0, &path.1)?; + let submissions = submission::get_submissions_user_by_contest_problem( + &mut connection, + logged_user.id, + path.0, + &path.1, + )?; - Ok(HttpResponse::Ok().body( + Ok(HttpResponse::Ok().body( hb.render( "submissions", &SubmissionsContext { @@ -1031,7 +1161,8 @@ async fn get_submissions_me_by_contest_id_problem_label( .map(|(s, c, u)| format_submission(&tz, s, c, u)) .collect(), }, - )?)) + )?, + )) } #[derive(Serialize, Deserialize)] @@ -1041,9 +1172,9 @@ struct SubmissionForm { source_text: String, } -use std::sync::Arc; use dashmap::DashMap; use std::collections::HashMap; +use std::sync::Arc; use actix_web::HttpRequest; @@ -1055,7 +1186,9 @@ fn redirect_to_referer(message: String, request: &HttpRequest) -> HttpResponse { .map(|s| s.into()) .unwrap_or(env::var("BASE_URL").expect("BASE_URL environment variable is not set")); FlashMessage::info(message).send(); - HttpResponse::SeeOther().append_header((header::LOCATION, HeaderValue::from_str(&referer).unwrap())).finish() + HttpResponse::SeeOther() + .append_header((header::LOCATION, HeaderValue::from_str(&referer).unwrap())) + .finish() } #[derive(Serialize, Deserialize)] @@ -1074,15 +1207,26 @@ async fn change_password( ) -> PostResult { let identity = require_identity(&identity)?; if form.new_password != form.new_password_repeat { - return Err(PostError::Validation("Senhas são diferentes".into())); + return Err(PostError::Validation("Senhas são diferentes".into())); } let mut connection = pool.get()?; use user::PasswordMatched; - match user::change_password(&mut connection, identity.id, &form.old_password, &form.new_password)? { - PasswordMatched::PasswordMatches(_) => Ok(redirect_to_referer("Senha alterada com sucesso".into(), &request)), - _ => Ok(redirect_to_referer("Senha antiga incorreta".into(), &request)) + match user::change_password( + &mut connection, + identity.id, + &form.old_password, + &form.new_password, + )? { + PasswordMatched::PasswordMatches(_) => Ok(redirect_to_referer( + "Senha alterada com sucesso".into(), + &request, + )), + _ => Ok(redirect_to_referer( + "Senha antiga incorreta".into(), + &request, + )), } } @@ -1107,15 +1251,21 @@ async fn create_user( let mut connection = pool.get()?; - user::insert_new_user(&mut connection, user::NewUser { - name: &form.name, - password: &form.password, - is_admin: form.is_admin.unwrap_or(false), - creation_instant: Local::now().naive_utc(), - creation_user_id: Some(identity.id) - })?; + user::insert_new_user( + &mut connection, + user::NewUser { + name: &form.name, + password: &form.password, + is_admin: form.is_admin.unwrap_or(false), + creation_instant: Local::now().naive_utc(), + creation_user_id: Some(identity.id), + }, + )?; - Ok(redirect_to_referer("Usuário criado com sucesso".into(), &request)) + Ok(redirect_to_referer( + "Usuário criado com sucesso".into(), + &request, + )) } #[derive(Serialize, Deserialize)] @@ -1146,9 +1296,13 @@ async fn impersonate_user( is_admin: user.is_admin, }) .map_err(|_| PostError::Custom("Usuário no banco de dados inconsistente".into()))?, - ).map_err(|_| PostError::Custom("Impossível fazer login".into()))?; + ) + .map_err(|_| PostError::Custom("Impossível fazer login".into()))?; - Ok(redirect_to_referer("Personificado com sucesso".into(), &request)) + Ok(redirect_to_referer( + "Personificado com sucesso".into(), + &request, + )) } #[post("/submissions/")] @@ -1169,7 +1323,9 @@ async fn create_submission( .ok_or(PostError::Validation("Linguagem inexistente".into()))?; if !logged_user.is_admin && form.language != "cpp.17.g++" { - return Err(PostError::Validation("Somente é possível submeter em C++".into())); + return Err(PostError::Validation( + "Somente é possível submeter em C++".into(), + )); } let uuid = Uuid::new_v4(); @@ -1185,31 +1341,45 @@ async fn create_submission( }, )?; - let contest = contest::get_contest_by_contest_problem_id(&mut connection, form.contest_problem_id)?; - if contest.start_instant.map(|s| s > Local::now().naive_utc()).unwrap_or(false) && !logged_user.is_admin { - return Ok(redirect_to_referer("Essa competição ainda não começou".into(), &request)); + let contest = + contest::get_contest_by_contest_problem_id(&mut connection, form.contest_problem_id)?; + if contest + .start_instant + .map(|s| s > Local::now().naive_utc()) + .unwrap_or(false) + && !logged_user.is_admin + { + return Ok(redirect_to_referer( + "Essa competição ainda não começou".into(), + &request, + )); } let metadata = problem::get_problem_by_contest_id_metadata(&mut connection, form.contest_problem_id)?; - job_sender.send(Job { - uuid: uuid.to_string(), - language: (&form.language).into(), - time_limit_ms: metadata.time_limit_ms, - memory_limit_kib: metadata.memory_limit_bytes / 1_024, + job_sender + .send(Job { + uuid: uuid.to_string(), + language: (&form.language).into(), + time_limit_ms: metadata.time_limit_ms, + memory_limit_kib: metadata.memory_limit_bytes / 1_024, - which: Some(job::Which::Judgement(job::Judgement { - source_text: (&form.source_text).into(), - test_count: metadata.test_count, - test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), - checker_language: metadata.checker_language, - checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path).into(), - })) - }).await?; + which: Some(job::Which::Judgement(job::Judgement { + source_text: (&form.source_text).into(), + test_count: metadata.test_count, + test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), + checker_language: metadata.checker_language, + checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path).into(), + })), + }) + .await?; session.insert("language", &form.language)?; - Ok(redirect_to_referer(format!("Submetido {} com sucesso!", uuid), &request)) + Ok(redirect_to_referer( + format!("Submetido {} com sucesso!", uuid), + &request, + )) } #[post("/submissions/{uuid}/rejudge")] @@ -1229,27 +1399,33 @@ async fn rejudge_submission( let path = path.into_inner(); let uuid = path.0; - let (submission, _, _, _) - = submission::get_submission_by_uuid(&mut connection, uuid.clone())?; - let metadata = - problem::get_problem_by_contest_id_metadata(&mut connection, submission.contest_problem_id)?; - - job_sender.send(Job { - uuid: uuid.clone(), - language: submission.language.into(), - time_limit_ms: metadata.time_limit_ms, - memory_limit_kib: metadata.memory_limit_bytes / 1_024, - - which: Some(job::Which::Judgement(job::Judgement { - source_text: submission.source_text.into(), - test_count: metadata.test_count, - test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), - checker_language: metadata.checker_language, - checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path).into(), - })) - }).await?; - - Ok(redirect_to_referer(format!("Rejulgando {}", uuid), &request)) + let (submission, _, _, _) = submission::get_submission_by_uuid(&mut connection, uuid.clone())?; + let metadata = problem::get_problem_by_contest_id_metadata( + &mut connection, + submission.contest_problem_id, + )?; + + job_sender + .send(Job { + uuid: uuid.clone(), + language: submission.language.into(), + time_limit_ms: metadata.time_limit_ms, + memory_limit_kib: metadata.memory_limit_bytes / 1_024, + + which: Some(job::Which::Judgement(job::Judgement { + source_text: submission.source_text.into(), + test_count: metadata.test_count, + test_pattern: format!("./{}/{}", metadata.id, metadata.test_pattern).into(), + checker_language: metadata.checker_language, + checker_source_path: format!("./{}/{}", metadata.id, metadata.checker_path).into(), + })), + }) + .await?; + + Ok(redirect_to_referer( + format!("Rejulgando {}", uuid), + &request, + )) } #[derive(Serialize)] @@ -1299,29 +1475,36 @@ fn get_formatted_contest_acs(tz: &Tz, contest: &ContestWithAcs) -> FormattedCont accepted_total_count: contest.accepted_count + contest.accepted_after_count, problem_count: contest.problem_count, grade: match contest.grade_ratio { - Some(grade_ratio) => - format!("{:.2}", 10.0*(f64::from(contest.accepted_count) * 1.0/f64::from(grade_ratio) - + match contest.grade_after_ratio { - Some(grade_after_ratio) => - f64::from(contest.accepted_after_count) * 1.0/f64::from(grade_after_ratio), - None => 0.0 - })).replacen(".", ",", 1), + Some(grade_ratio) => format!( + "{:.2}", + 10.0 * (f64::from(contest.accepted_count) * 1.0 / f64::from(grade_ratio) + + match contest.grade_after_ratio { + Some(grade_after_ratio) => + f64::from(contest.accepted_after_count) * 1.0 + / f64::from(grade_after_ratio), + None => 0.0, + }) + ) + .replacen(".", ",", 1), None => "".into(), - } + }, } } -fn get_formatted_contests(connection: &mut PgConnection, user_id: Option<i32>, tz: &Tz) -> Result<Vec<FormattedContest>, GetError> { +fn get_formatted_contests( + connection: &mut PgConnection, + user_id: Option<i32>, + tz: &Tz, +) -> Result<Vec<FormattedContest>, GetError> { Ok(match user_id { - Some(user_id) => - contest::get_contests_with_acs(connection, user_id)? - .iter() - .map(|c| get_formatted_contest_acs(tz, c)) - .collect(), + Some(user_id) => contest::get_contests_with_acs(connection, user_id)? + .iter() + .map(|c| get_formatted_contest_acs(tz, c)) + .collect(), None => contest::get_contests(connection)? - .iter() - .map(|c| get_formatted_contest(tz, c)) - .collect() + .iter() + .map(|c| get_formatted_contest(tz, c)) + .collect(), }) } @@ -1348,20 +1531,20 @@ async fn get_main( let submissions = submission::get_submissions(&mut connection)?; Ok(HttpResponse::Ok().body( - hb.render( - "main", - &MainContext { - flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), - logged_user: logged_user.clone(), - contests: get_formatted_contests(&mut connection, logged_user.map(|u| u.id), &tz)?, - submissions: submissions - .iter() - .map(|(s, c, u)| format_submission(&tz, s, c, u)) - .collect(), - }, - )?)) + hb.render( + "main", + &MainContext { + flash_messages, + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), + logged_user: logged_user.clone(), + contests: get_formatted_contests(&mut connection, logged_user.map(|u| u.id), &tz)?, + submissions: submissions + .iter() + .map(|(s, c, u)| format_submission(&tz, s, c, u)) + .collect(), + }, + )?, + )) } #[get("/contests/")] @@ -1383,18 +1566,15 @@ async fn get_contests( } let mut connection = pool.get()?; - Ok(HttpResponse::Ok().body( - hb.render( - "contests", - &ContestsContext { - logged_user: logged_user.clone(), - flash_messages, - base_url: env::var("BASE_URL") - .expect("BASE_URL environment variable is not set"), - contests: get_formatted_contests(&mut connection, Some(logged_user.id), &tz)?, - }, - )? - )) + Ok(HttpResponse::Ok().body(hb.render( + "contests", + &ContestsContext { + logged_user: logged_user.clone(), + flash_messages, + base_url: env::var("BASE_URL").expect("BASE_URL environment variable is not set"), + contests: get_formatted_contests(&mut connection, Some(logged_user.id), &tz)?, + }, + )?)) } use crate::models::contest; @@ -1407,8 +1587,8 @@ use std::io::Cursor; use std::io::Read; use std::io::Write; use std::iter::FromIterator; -use std::str; use std::path::PathBuf; +use std::str; #[post("/contests/")] async fn create_contest( @@ -1422,7 +1602,7 @@ async fn create_contest( ) -> PostResult { let logged_user = require_identity(&identity)?; if !logged_user.is_admin { - return Err(PostError::Unauthorized(UnauthorizedError {})); + return Err(PostError::Unauthorized(UnauthorizedError {})); } #[derive(Debug)] @@ -1471,8 +1651,12 @@ async fn create_contest( Some("end_instant") => { form.end_instant = Some(parse_field("end_instant", &mut cursor)?) } - Some("grade_ratio") => form.grade_ratio = parse_field("grade_ratio", &mut cursor)?.parse().ok(), - Some("grade_after_ratio") => form.grade_after_ratio = parse_field("grade_after_ratio", &mut cursor)?.parse().ok(), + Some("grade_ratio") => { + form.grade_ratio = parse_field("grade_ratio", &mut cursor)?.parse().ok() + } + Some("grade_after_ratio") => { + form.grade_after_ratio = parse_field("grade_after_ratio", &mut cursor)?.parse().ok() + } Some("polygon_zip") => form.polygon_zip = Some(cursor), _ => {} } @@ -1482,9 +1666,7 @@ async fn create_contest( .polygon_zip .ok_or(PostError::Validation("Arquivo não informado".into()))?; let imported = import_contest::import_file(polygon_zip) - .map_err(|e| { - PostError::Validation(format!("Não foi possível importar: {}", e)) - })?; + .map_err(|e| PostError::Validation(format!("Não foi possível importar: {}", e)))?; let mut connection = pool.get()?; let contest = if form.name.as_ref().unwrap() != "" { @@ -1492,8 +1674,14 @@ async fn create_contest( &mut connection, contest::NewContest { name: form.name.clone().unwrap(), - start_instant: form.start_instant.and_then(|s| tz.datetime_from_str(&s, "%Y-%m-%d %H:%M:%S").ok()).map(|d| d.naive_utc()), - end_instant: form.end_instant.and_then(|s| tz.datetime_from_str(&s, "%Y-%m-%d %H:%M:%S").ok()).map(|d| d.naive_utc()), + start_instant: form + .start_instant + .and_then(|s| tz.datetime_from_str(&s, "%Y-%m-%d %H:%M:%S").ok()) + .map(|d| d.naive_utc()), + end_instant: form + .end_instant + .and_then(|s| tz.datetime_from_str(&s, "%Y-%m-%d %H:%M:%S").ok()) + .map(|d| d.naive_utc()), creation_instant: Local::now().naive_utc(), creation_user_id: logged_user.id, grade_ratio: form.grade_ratio, @@ -1616,7 +1804,8 @@ async fn create_contest( .find(|s| s.tag == "main") .ok_or(PostError::Validation("No main solution".into()))? .source - .path.clone(), + .path + .clone(), main_solution_language: map_codeforces_language( &metadata .assets @@ -1681,7 +1870,9 @@ async fn create_contest( })?; if run_stats.result != i32::from(job_result::run_cached::Result::Ok) { - return Err(PostError::Validation("Couldn't run an intermediate program".into())); + return Err(PostError::Validation( + "Couldn't run an intermediate program".into(), + )); } } @@ -1699,7 +1890,9 @@ async fn create_contest( .await .map_err(|_| PostError::Validation("Couldn't run solution on test".into()))?; if run_stats.exit_code != 0 { - return Err(PostError::Validation("Couldn't run solution on test".into())); + return Err(PostError::Validation( + "Couldn't run solution on test".into(), + )); } } @@ -1707,9 +1900,10 @@ async fn create_contest( &job_sender, &job_result_sender, &problem.main_solution_language, - fs::read_to_string( - PathBuf::from(format!("/data/{}/{}", problem.id, problem.main_solution_path)) - )?, + fs::read_to_string(PathBuf::from(format!( + "/data/{}/{}", + problem.id, problem.main_solution_path + )))?, problem.test_count, format!("./{}/{}", problem.id, problem.test_pattern).into(), problem.checker_language, @@ -1739,8 +1933,14 @@ async fn create_contest( } if form.name.as_ref().unwrap() != "" { - Ok(redirect_to_referer("Competição criada com sucesso".into(), &request)) + Ok(redirect_to_referer( + "Competição criada com sucesso".into(), + &request, + )) } else { - Ok(redirect_to_referer("Problemas adicionados com sucesso".into(), &request)) + Ok(redirect_to_referer( + "Problemas adicionados com sucesso".into(), + &request, + )) } } diff --git a/src/models/contest.rs b/src/models/contest.rs index 3e502e5e86c65323bb71feca190f33cc8bdfba12..410c57e6e4cbb2617afe0ba15676e6ce75a4d075 100644 --- a/src/models/contest.rs +++ b/src/models/contest.rs @@ -87,8 +87,12 @@ pub fn insert_contest( contest::table.order(contest::id.desc()).first(connection) } -pub fn get_contests_with_acs(connection: &mut PgConnection, user_id: i32) -> QueryResult<Vec<ContestWithAcs>> { - diesel::sql_query(r#" +pub fn get_contests_with_acs( + connection: &mut PgConnection, + user_id: i32, +) -> QueryResult<Vec<ContestWithAcs>> { + diesel::sql_query( + r#" with first_ac as ( select min(submission_instant) as first_ac_submission_instant, @@ -121,7 +125,8 @@ pub fn get_contests_with_acs(connection: &mut PgConnection, user_id: i32) -> Que left join first_ac on first_ac.contest_problem_id = contest_problems.id group by contest.id order by creation_instant desc - "#) + "#, + ) .bind::<sql_types::Integer, _>(user_id) .load(connection) } @@ -133,11 +138,13 @@ pub fn get_contests(connection: &mut PgConnection) -> QueryResult<Vec<Contest>> } pub fn get_contest_by_id(connection: &mut PgConnection, id: i32) -> QueryResult<Contest> { - contest::table.filter(contest::id.eq(id)) - .first(connection) + contest::table.filter(contest::id.eq(id)).first(connection) } -pub fn get_contest_by_contest_problem_id(connection: &mut PgConnection, contest_problem_id: i32) -> QueryResult<Contest> { +pub fn get_contest_by_contest_problem_id( + connection: &mut PgConnection, + contest_problem_id: i32, +) -> QueryResult<Contest> { contest::table .inner_join(contest_problems::table) .filter(contest_problems::id.eq(contest_problem_id)) diff --git a/src/models/problem.rs b/src/models/problem.rs index 9147e8ab22ed574df89da767c5a88924188a740a..34e46ccd3453e9bfa32b0c102acb83d5d5de0b70 100644 --- a/src/models/problem.rs +++ b/src/models/problem.rs @@ -1,7 +1,7 @@ use chrono::prelude::*; use diesel::insert_into; -use diesel::prelude::*; use diesel::pg::PgConnection; +use diesel::prelude::*; use serde::Serialize; use crate::schema::contest_problems; @@ -61,7 +61,12 @@ pub fn get_problems_by_contest_id( problem::table .inner_join(contest_problems::table) .filter(contest_problems::contest_id.eq(contest_id)) - .select((contest_problems::contest_id, contest_problems::id, problem::name, contest_problems::label)) + .select(( + contest_problems::contest_id, + contest_problems::id, + problem::name, + contest_problems::label, + )) .order(contest_problems::label) .load(connection) } @@ -95,7 +100,8 @@ pub fn get_problems_user_by_contest_id_with_score( user_id: i32, contest_id: i32, ) -> QueryResult<Vec<ProblemByContestWithScore>> { - diesel::sql_query(r#" + diesel::sql_query( + r#" with first_ac as ( select min(submission_instant) as first_ac_submission_instant, @@ -148,7 +154,8 @@ pub fn get_problems_user_by_contest_id_with_score( on user_acs_count.contest_problem_id = contest_problems.id where contest_id = $2 order by contest_problems.label - "#) + "#, + ) .bind::<sql_types::Integer, _>(user_id) .bind::<sql_types::Integer, _>(contest_id) .load(connection) @@ -158,7 +165,8 @@ pub fn get_problems_by_contest_id_with_score( connection: &mut PgConnection, contest_id: i32, ) -> QueryResult<Vec<ProblemByContestWithScore>> { - diesel::sql_query(r#" + diesel::sql_query( + r#" with first_ac as ( select min(submission_instant) as first_ac_submission_instant, @@ -232,7 +240,8 @@ pub fn get_problems_by_contest_id_with_score( on user_acs_count.contest_problem_id = contest_problems.id where contest_problems.contest_id = $1 order by "user".name, contest_problems.label) - "#) + "#, + ) .bind::<sql_types::Integer, _>(contest_id) .load(connection) } @@ -246,7 +255,12 @@ pub fn get_problem_by_contest_id_label( .inner_join(contest_problems::table) .filter(contest_problems::contest_id.eq(contest_id)) .filter(contest_problems::label.eq(problem_label)) - .select((contest_problems::contest_id, contest_problems::id, problem::name, contest_problems::label)) + .select(( + contest_problems::contest_id, + contest_problems::id, + problem::name, + contest_problems::label, + )) .order(contest_problems::label) .first(connection) } diff --git a/src/models/submission.rs b/src/models/submission.rs index bbb879a1413cf607ee0c6eed1d4530773637b6e9..077d3e089066a1d0ad9504dd4bace051d2210460 100644 --- a/src/models/submission.rs +++ b/src/models/submission.rs @@ -1,13 +1,13 @@ -use crate::schema::submission; +use crate::contest::{Contest, CONTEST_COLUMNS}; +use crate::schema::contest; use crate::schema::contest_problems; -use crate::schema::user; use crate::schema::problem; +use crate::schema::submission; +use crate::schema::user; use crate::user::{User, USER_COLUMNS}; use chrono::prelude::*; use diesel::insert_into; use diesel::prelude::*; -use crate::contest::{Contest, CONTEST_COLUMNS}; -use crate::schema::contest; #[derive(Queryable)] pub struct Submission { @@ -134,30 +134,37 @@ pub fn complete_submission( use crate::problem::ProblemByContestMetadata; -pub fn get_waiting_judge_submissions(connection: &mut PgConnection) -> QueryResult<Vec<(Submission, ProblemByContestMetadata)>> { +pub fn get_waiting_judge_submissions( + connection: &mut PgConnection, +) -> QueryResult<Vec<(Submission, ProblemByContestMetadata)>> { submission::table .inner_join(contest_problems::table.inner_join(problem::table)) .order_by(submission::submission_instant.asc()) - .select((SUBMISSION_COLUMNS, ( - contest_problems::contest_id, - problem::id, - problem::memory_limit_bytes, - problem::time_limit_ms, - problem::checker_path, - problem::checker_language, - problem::validator_path, - problem::validator_language, - problem::main_solution_path, - problem::main_solution_language, - problem::test_count, - problem::test_pattern, - problem::status, - ))) + .select(( + SUBMISSION_COLUMNS, + ( + contest_problems::contest_id, + problem::id, + problem::memory_limit_bytes, + problem::time_limit_ms, + problem::checker_path, + problem::checker_language, + problem::validator_path, + problem::validator_language, + problem::main_solution_path, + problem::main_solution_language, + problem::test_count, + problem::test_pattern, + problem::status, + ), + )) .filter(submission::verdict.is_null()) .load::<(Submission, ProblemByContestMetadata)>(connection) } -pub fn get_submissions(connection: &mut PgConnection) -> QueryResult<Vec<(Submission, ContestProblem, User)>> { +pub fn get_submissions( + connection: &mut PgConnection, +) -> QueryResult<Vec<(Submission, ContestProblem, User)>> { submission::table .inner_join(contest_problems::table) .inner_join(user::table) @@ -167,16 +174,27 @@ pub fn get_submissions(connection: &mut PgConnection) -> QueryResult<Vec<(Submis .load::<(Submission, ContestProblem, User)>(connection) } -pub fn get_submission_by_uuid(connection: &mut PgConnection, uuid: String) -> QueryResult<(Submission, User, ContestProblem, Contest)> { +pub fn get_submission_by_uuid( + connection: &mut PgConnection, + uuid: String, +) -> QueryResult<(Submission, User, ContestProblem, Contest)> { submission::table .filter(submission::uuid.eq(uuid)) .inner_join(contest_problems::table.inner_join(contest::table)) .inner_join(user::table) - .select((SUBMISSION_COLUMNS, USER_COLUMNS, CONTEST_PROBLEMS_COLUMNS, CONTEST_COLUMNS)) + .select(( + SUBMISSION_COLUMNS, + USER_COLUMNS, + CONTEST_PROBLEMS_COLUMNS, + CONTEST_COLUMNS, + )) .first::<(Submission, User, ContestProblem, Contest)>(connection) } -pub fn get_submissions_user(connection: &mut PgConnection, user_id: i32) -> QueryResult<Vec<(Submission, ContestProblem, User)>> { +pub fn get_submissions_user( + connection: &mut PgConnection, + user_id: i32, +) -> QueryResult<Vec<(Submission, ContestProblem, User)>> { submission::table .filter(submission::user_id.eq(user_id)) .inner_join(contest_problems::table) diff --git a/src/models/user.rs b/src/models/user.rs index 2a7902de843f1e536fa64935d2ea0971a5830a20..0d9a04ff87be8a0f9ee6110615262530788c4ff4 100644 --- a/src/models/user.rs +++ b/src/models/user.rs @@ -1,7 +1,7 @@ use chrono::prelude::*; use diesel::insert_into; -use diesel::prelude::*; use diesel::pg::PgConnection; +use diesel::prelude::*; use serde::Serialize; use std::env; use thiserror::Error; @@ -20,7 +20,8 @@ pub struct User { pub name: String, pub is_admin: bool, } -pub const USER_COLUMNS: (user::id, user::name, user::is_admin) = (user::id, user::name, user::is_admin); +pub const USER_COLUMNS: (user::id, user::name, user::is_admin) = + (user::id, user::name, user::is_admin); #[derive(Insertable)] #[diesel(table_name = user)] @@ -107,7 +108,9 @@ pub fn change_password( .filter(user::id.eq(id)) .set(user::hashed_password.eq(hashed_password)) .execute(connection)?; - Ok(PasswordMatched::PasswordMatches(get_user_by_name(connection, &user.name)?)) + Ok(PasswordMatched::PasswordMatches(get_user_by_name( + connection, &user.name, + )?)) } else { Ok(PasswordMatched::PasswordDoesntMatch) } diff --git a/src/queue.rs b/src/queue.rs index ccdc2c325a798b32218e049f6c5f04a195ee3a96..ebd293ed9e13ee4ac2ca2c1ab64dc53a56f2a436 100644 --- a/src/queue.rs +++ b/src/queue.rs @@ -1,11 +1,11 @@ -use job_protocol::{GetJobRequest, JobResult, JobResultConfirmation, Job, Language}; -use job_protocol::job_queue_server::JobQueue; -use tonic::{Request, Response, Status}; use async_channel::Receiver; -use tokio::sync::broadcast; use dashmap::DashMap; -use std::sync::Arc; +use job_protocol::job_queue_server::JobQueue; +use job_protocol::{GetJobRequest, Job, JobResult, JobResultConfirmation, Language}; use log::info; +use std::sync::Arc; +use tokio::sync::broadcast; +use tonic::{Request, Response, Status}; pub mod job_protocol { tonic::include_proto!("job_protocol"); @@ -20,10 +20,7 @@ pub struct JobQueuer { #[tonic::async_trait] impl JobQueue for JobQueuer { - async fn get_job( - &self, - request: Request<GetJobRequest>, - ) -> Result<Response<Job>, Status> { + async fn get_job(&self, request: Request<GetJobRequest>) -> Result<Response<Job>, Status> { info!("Got GetJob"); let request = request.into_inner(); for language in request.supported_languages { @@ -31,7 +28,11 @@ impl JobQueue for JobQueuer { } info!("Waiting for job to send"); - let job = self.job_receiver.recv().await.expect("Failed to receive from job queue"); + let job = self + .job_receiver + .recv() + .await + .expect("Failed to receive from job queue"); Ok(Response::new(job)) } @@ -41,7 +42,9 @@ impl JobQueue for JobQueuer { ) -> Result<Response<JobResultConfirmation>, Status> { let request = request.into_inner(); println!("{:?}", request); - self.job_result_sender.send(request).expect("Failed to send to job result broadcast"); + self.job_result_sender + .send(request) + .expect("Failed to send to job result broadcast"); Ok(Response::new(JobResultConfirmation {})) } } diff --git a/src/schema.rs b/src/schema.rs index 0ded385bad9754fe9ee4d76631ae84f4d3d41244..11fd8cfba25787620f2447593b32934e8217ded7 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -77,10 +77,4 @@ diesel::joinable!(problem -> user (creation_user_id)); diesel::joinable!(submission -> contest_problems (contest_problem_id)); diesel::joinable!(submission -> user (user_id)); -diesel::allow_tables_to_appear_in_same_query!( - contest, - contest_problems, - problem, - submission, - user, -); +diesel::allow_tables_to_appear_in_same_query!(contest, contest_problems, problem, submission, user,);