diff --git a/src/main.rs b/src/main.rs index 6707abe76906b4b80ab5ce08e4659969b970011d..8350df69c4c6068f85da65e164b93b9ec4b9c3f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -212,6 +212,8 @@ async fn main() -> Result<(), Box<dyn Error>> { .service(pages::rejudge_submission::rejudge_submission) .service(pages::create_submission::create_submission) .service(pages::create_contest::create_contest) + .service(pages::get_problems::get_problems) + .service(pages::get_about::get_about) .service(pages::create_user::create_user) .service(pages::impersonate_user::impersonate_user) .service(pages::submission_updates::submission_updates) diff --git a/src/models/problem.rs b/src/models/problem.rs index 9cc1b359205e0e62237828485b118db7456b63e9..0d9f63def7e80a6a6727135a21334dc926961b53 100644 --- a/src/models/problem.rs +++ b/src/models/problem.rs @@ -94,6 +94,69 @@ pub struct ProblemByContestWithScore { pub user_accepted_count: i32, } +pub fn get_problems_user_with_score( + connection: &mut PgConnection, + user_id: i32, +) -> QueryResult<Vec<ProblemByContestWithScore>> { + diesel::sql_query( + r#" + with first_ac as ( + select + min(submission_instant) as first_ac_submission_instant, + contest_problem_id, + submission.user_id + from submission + where submission.verdict = 'AC' + group by submission.user_id, submission.contest_problem_id + ), failed_submissions as ( + select + submission.user_id, + submission.contest_problem_id, + cast(count(*) as int) as count + from submission + left join first_ac on first_ac.contest_problem_id = submission.contest_problem_id + and submission.user_id = first_ac.user_id + where ( + first_ac_submission_instant is null or + submission.submission_instant < first_ac.first_ac_submission_instant + ) + group by submission.user_id, submission.contest_problem_id + ), user_acs_count as ( + select + cast(count(distinct submission.user_id) as int) as user_accepted_count, + submission.contest_problem_id + from submission + where submission.verdict = 'AC' + group by submission.contest_problem_id + ) + select + "user".name as user_name, + first_ac_submission_instant, + coalesce(failed_submissions.count, 0) as failed_submissions, + contest_problems.id, + problem.name, + contest_problems.label, + problem.memory_limit_bytes, + problem.time_limit_ms, + coalesce(user_acs_count.user_accepted_count, 0) as user_accepted_count + from contest_problems + inner join problem on problem.id = contest_problems.problem_id + inner join "user" on "user".id = $1 + left join failed_submissions + on failed_submissions.contest_problem_id = contest_problems.id + and failed_submissions.user_id = "user".id + left join first_ac + on first_ac.contest_problem_id = contest_problems.id + and first_ac.user_id = "user".id + left join user_acs_count + on user_acs_count.contest_problem_id = contest_problems.id + order by contest_problems.label + "#, + ) + .bind::<sql_types::Integer, _>(user_id) + .load(connection) +} + pub fn get_problems_user_by_contest_id_with_score( connection: &mut PgConnection, user_id: i32, diff --git a/src/pages/get_about.rs b/src/pages/get_about.rs new file mode 100644 index 0000000000000000000000000000000000000000..89cb48b70e0ca8801199ed187f8b50cd5f0b59fe --- /dev/null +++ b/src/pages/get_about.rs @@ -0,0 +1,6 @@ +use crate::pages::prelude::*; + +#[get("/about")] +async fn get_about(hb: Data<Handlebars<'_>>) -> PageResult { + render(&hb, "about", &()) +} diff --git a/src/pages/get_problems.rs b/src/pages/get_problems.rs new file mode 100644 index 0000000000000000000000000000000000000000..a930fb74d7f6bc211453e1edb85f41a353615a34 --- /dev/null +++ b/src/pages/get_problems.rs @@ -0,0 +1,48 @@ +use crate::pages::prelude::*; +use crate::models::problem; +use crate::pages::{ + FormattedProblemByContestWithScore, +}; + +#[get("/problems/")] +pub async fn get_problems( + base: BaseContext, + identity: Identity, + pool: Data<DbPool>, + hb: Data<Handlebars<'_>>, + tz: Data<Tz>, +) -> PageResult { + let logged_user = require_identity(&identity)?; + + #[derive(Serialize)] + struct Context { + base: BaseContext, + problems: Vec<FormattedProblemByContestWithScore>, + } + + let mut connection = pool.get()?; + let problems = problem::get_problems_user_with_score(&mut connection, logged_user.id)?; + + render( + &hb, + "problems", + &Context { + base, + problems: problems + .iter() + .map(|p| + FormattedProblemByContestWithScore { + first_ac_submission_time: "".into(), + first_ac_submission_minutes: None, + 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(), + }, + ) +} diff --git a/src/pages/mod.rs b/src/pages/mod.rs index 69702214fc9a72067aaca1926f69d9acdd9e9316..e4ca1bc6baeeafa5477e55deeea3bb91bacb8f5a 100644 --- a/src/pages/mod.rs +++ b/src/pages/mod.rs @@ -38,6 +38,8 @@ pub mod post_logout; pub mod prelude; pub mod rejudge_submission; pub mod submission_updates; +pub mod get_problems; +pub mod get_about; use prelude::*; diff --git a/templates/about.hbs b/templates/about.hbs new file mode 100644 index 0000000000000000000000000000000000000000..945ada4f911317a07878ff44f54f61e73c522e78 --- /dev/null +++ b/templates/about.hbs @@ -0,0 +1,10 @@ +{{ #> base title="Sobre" }} + <div id="contests"> + O Juĝisto é o juiz de maratona da Universidade Federal do Paraná. + Seu objetivo é ser utilizado para lecionar a disciplina de Desafios de Programação. + + <p> + O código é aberto e licenciado sob GPLv3 e pode ser encontrado + <a href="gitlab.c3sl.ufpr.br/maratona-ufpr/jughisto">no GitLab do C3SL</a>. + </div> +{{ /base }} diff --git a/templates/base.hbs b/templates/base.hbs index 42fd43a1db872ad084f3f8c27c2bedc80b322d03..8124d86f65f5a5da9e4f8efe0fc93c831c865adc 100644 --- a/templates/base.hbs +++ b/templates/base.hbs @@ -18,7 +18,7 @@ <a href="problems/">Problemas</a> <a href="submissions/">Submissões</a> <a href="setting/">Criações</a> - <a href="about/">Sobre</a> + <a href="about">Sobre</a> </nav> <div class="span"></div> <div class="logged"> diff --git a/templates/problems.hbs b/templates/problems.hbs new file mode 100644 index 0000000000000000000000000000000000000000..a63a99ed7d6c956ebb153abb094922f2849916fd --- /dev/null +++ b/templates/problems.hbs @@ -0,0 +1,51 @@ +{{ #> base title="Problemas" }} + <div id="contest"> + <div id="breadcrumb"> + <a href=".">Início</a> + / + <a href="problems/">Problemas</a> + </div> + + <div id="problems"> + {{ #each problems }} + <a href="problems/{{ this.id }}" class="problem"> + <div class="problem-info"> + <div class="name"> + {{ this.label }} · {{ this.name }} + </div> + <div class="extra"> + {{this.time_limit}}s · {{ this.memory_limit_mib }}MiB + </div> + </div> + <div class="span"></div> + {{#if this.user_accepted_count}} + <div class="score"> + <i class="gg-user"></i> + <div class="time">x{{this.user_accepted_count}}</div> + </div> + {{/if}} + {{#if this.first_ac_submission_time}} + <div class="score accepted"> + <div> + +{{#if this.failed_submissions}}{{this.failed_submissions}}{{/if}} + </div> + {{#if (ne this.first_ac_submission_time "*")}} + <div class="time"> + {{this.first_ac_submission_time}} + </div> + {{/if}} + </div> + {{else}} + {{#if this.failed_submissions}} + <div class="score wrong-answer"> + <div>–{{ this.failed_submissions }}</div> + </div> + {{else}} + <div class="score"></div> + {{/if}} + {{/if}} + </a> + {{ /each }} + </div> + </div> +{{ /base }}