Commit e2791fac authored by Robert Czechowski's avatar Robert Czechowski

Merge branch 'admin-contest-overview'

parents fac0cfce 42a269ae
......@@ -170,6 +170,17 @@ dependencies = [
"serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "bstr"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
"regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "buf_redux"
version = "0.6.3"
......@@ -351,6 +362,26 @@ dependencies = [
"generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
"csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "csv-core"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "digest"
version = "0.7.6"
......@@ -895,6 +926,7 @@ name = "medal"
version = "1.3.0"
dependencies = [
"bcrypt 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
"handlebars-iron 0.25.2 (registry+https://github.com/rust-lang/crates.io-index)",
"iron 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
"iron-sessionstorage 0.7.0",
......@@ -1650,6 +1682,14 @@ dependencies = [
"regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-automata"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "regex-syntax"
version = "0.5.6"
......@@ -2533,6 +2573,7 @@ dependencies = [
"checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3"
"checksum bodyparser 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6928e817538b74a73d1dd6e9a942a2a35c632a597b6bb14fd009480f859a6bf5"
"checksum bodyparser 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afa8a0260bb79363b68e8bfbc6e2a2d1be61f5a086aab8d9fe7d0a304a34f6a9"
"checksum bstr 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
"checksum buf_redux 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b9279646319ff816b05fb5897883ece50d7d854d12b59992683d4f8a71b0f949"
"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
......@@ -2554,6 +2595,8 @@ dependencies = [
"checksum crossbeam-queue 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum crypto-mac 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0999b4ff4d3446d4ddb19a63e9e00c1876e75cd7000d20e57a693b4b3f08d958"
"checksum csv 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279"
"checksum csv-core 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90"
"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90"
"checksum dtoa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dd841b58510c9618291ffa448da2e4e0f699d984d436122372f446dae62263d"
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
......@@ -2693,6 +2736,7 @@ dependencies = [
"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
"checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384"
"checksum regex 1.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3"
"checksum regex-automata 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4"
"checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7"
"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
......
......@@ -38,6 +38,7 @@ structopt = "0.2.18"
reqwest = "0.9.19"
linked-hash-map = "0.5.1"
bcrypt = "0.3"
csv = "1.1"
[dependencies.serde_json]
version = "1.0.20"
......
......@@ -1162,6 +1162,12 @@ pub fn admin_show_user<T: MedalConnection>(conn: &T, user_id: i32, session_token
data.insert("group".to_string(), to_json(&v));
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
let parts = conn.get_all_participations_complete(user_id);
let pi: Vec<(i32, String)> = parts.into_iter().map(|(_, c)| (c.id.unwrap(), c.name)).collect();
data.insert("participations".to_string(), to_json(&pi));
Ok(("admin_user".to_string(), data))
}
......@@ -1218,14 +1224,52 @@ pub fn admin_delete_group<T: MedalConnection>(conn: &T, group_id: i32, session_t
}
#[allow(unused_variables)]
pub fn admin_show_participation<T: MedalConnection>(conn: &T, participation_id: i32, session_token: &str)
pub fn admin_show_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str)
-> MedalValueResult {
let data = json_val::Map::new();
Ok(("profile".to_string(), data))
conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let contest = conn.get_contest_by_id_complete(contest_id);
let subms: Vec<(String, Vec<(i32, Vec<(String, i32)>)>)> = contest.taskgroups
.into_iter()
.map(|tg| {
(tg.name,
tg.tasks
.into_iter()
.map(|t| {
(t.stars,
conn.get_all_submissions(user_id, t.id.unwrap(), None)
.into_iter()
.map(|s| (self::time::strftime("%FT%T%z", &self::time::at(s.date)).unwrap(), s.grade))
.collect())
})
.collect())
})
.collect();
let mut data = json_val::Map::new();
data.insert("submissions".to_string(), to_json(&subms));
data.insert("contestid".to_string(), to_json(&contest.id));
data.insert("contestname".to_string(), to_json(&contest.name));
let user = conn.get_user_by_id(user_id).ok_or(MedalError::AccessDenied)?;
fill_user_data(&user, &mut data);
data.insert("userid".to_string(), to_json(&user.id));
let participation =
conn.get_participation(&user.session_token.unwrap(), contest_id).ok_or(MedalError::AccessDenied)?;
data.insert("start_date".to_string(),
to_json(&self::time::strftime("%FT%T%z", &self::time::at(participation.start)).unwrap()));
Ok(("admin_participation".to_string(), data))
}
#[allow(unused_variables)]
pub fn admin_delete_participation<T: MedalConnection>(conn: &T, participation_id: i32, session_token: &str,
pub fn admin_delete_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str,
csrf_token: &str)
-> MedalValueResult
{
......@@ -1233,6 +1277,43 @@ pub fn admin_delete_participation<T: MedalConnection>(conn: &T, participation_id
Ok(("profile".to_string(), data))
}
pub fn admin_show_contests<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let mut data = json_val::Map::new();
// TODO: This returns the contests ordered by 'positionalnumber'
// Should this ordering be changed?
let contests: Vec<_> = conn.get_contest_list().into_iter().map(|contest| (contest.id, contest.name)).collect();
data.insert("contests".to_string(), to_json(&contests));
Ok(("admin_contests".to_string(), data))
}
pub fn admin_contest_export<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalResult<String> {
conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
let contest = conn.get_contest_by_id_complete(contest_id);
let taskgroup_ids: Vec<(i32, String)> =
contest.taskgroups.into_iter().map(|tg| (tg.id.unwrap(), tg.name)).collect();
let filename = format!("contest_{}__{}__{}.csv", contest_id, self::time::strftime("%F_%H-%M-%S", &self::time::now()).unwrap(), helpers::make_filename_secret());
conn.export_contest_results_to_file(contest_id, &taskgroup_ids, &format!("./export/{}", filename));
Ok(filename)
}
#[derive(PartialEq)]
pub enum UserType {
User,
......
......@@ -609,6 +609,28 @@ impl MedalConnection for Connection {
}
}
}
fn get_all_submissions(&self, session_id: i32, task: i32, subtask: Option<&str>) -> Vec<Submission> {
match subtask {
None => {
let query = "SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
FROM submission
WHERE task = $1
AND session = $2";
self.query_map_many(query, &[&task, &session_id], |row| Submission { id: Some(row.get(0)),
task: task,
session_user: session_id,
grade: row.get(1),
validated: row.get(2),
nonvalidated_grade: row.get(3),
subtask_identifier: None,
value: row.get(4),
date: row.get(5),
needs_validation: row.get(6) })
.unwrap()
}
_ => unimplemented!(),
}
}
fn submit_submission(&self, mut submission: Submission) {
submission.save(self);
......@@ -784,6 +806,116 @@ impl MedalConnection for Connection {
.unwrap_or_default()
}
/* Warning: This function makes no use of rusts type safety. Handle with care when changeing */
fn export_contest_results_to_file(&self, contest_id: i32, taskgroups: &[(i32, String)], filename: &str) {
use std::fs::OpenOptions;
let file = OpenOptions::new().write(true).create(true).truncate(true).open(filename).unwrap();
let mut headers = vec!["id",
"username",
"logincode",
"oauth_foreign_id",
"oauth_provider",
"firstname",
"lastname",
"grade",
"sex",
"is_teacher",
"group_id",
"group_name",
"group_tag",
"teacher_id",
"teacher_firstname",
"teacher_lastname",
"teacher_oauth_foreign_id",
"teacher_oauth_provider",
"contest_id",
"start_date"];
let mut select_part = String::new();
let mut join_part = String::new();
let mut join_params = gen_tosql_vector();
join_params.push(&contest_id);
for (n, (id, name)) in taskgroups.iter().enumerate() {
select_part.push_str(&format!(",\n g{}.grade ", n));
join_part.push_str(&format!("\n LEFT JOIN grade AS g{} ON session.id = g{}.session AND g{}.taskgroup = ${} ", n, n, n, n + 2));
join_params.push(id);
headers.push(&name);
}
let query = format!("SELECT session.id,
session.username,
session.logincode,
session.oauth_foreign_id,
session.oauth_provider,
session.firstname,
session.lastname,
session.grade,
session.sex,
session.is_teacher,
session.managed_by,
usergroup.name,
usergroup.tag,
teacher.id,
teacher.firstname,
teacher.lastname,
teacher.oauth_foreign_id,
teacher.oauth_provider,
participation.contest,
participation.start_date
{}
FROM participation
JOIN session ON participation.session = session.id
{}
LEFT JOIN usergroup ON session.managed_by = usergroup.id
LEFT JOIN session AS teacher ON usergroup.admin = teacher.id
WHERE participation.contest = $1",
select_part, join_part);
use csv::Writer;
let mut wtr = Writer::from_writer(file);
wtr.serialize(&headers).unwrap();
wtr.flush().unwrap();
let file = wtr.into_inner().unwrap();
let mut wtr = Writer::from_writer(file);
self.query_map_many(&query, join_params.as_slice(), |row| {
let mut points = Vec::new();
for i in 20..20 + taskgroups.len() {
points.push(row.get::<_, Option<i32>>(i));
}
// Serialized as several tuples because Serde only supports tuples up to a certain length
// (16 according to https://docs.serde.rs/serde/trait.Deserialize.html)
wtr.serialize(((row.get::<_, i32>(0),
row.get::<_, Option<String>>(1),
row.get::<_, Option<String>>(2),
row.get::<_, Option<String>>(3),
row.get::<_, Option<String>>(4),
row.get::<_, Option<String>>(5),
row.get::<_, Option<String>>(6),
row.get::<_, i32>(7),
row.get::<_, Option<i32>>(8),
row.get::<_, bool>(9)),
(row.get::<_, Option<i32>>(10),
row.get::<_, Option<String>>(11),
row.get::<_, Option<String>>(12),
row.get::<_, Option<i32>>(13),
row.get::<_, Option<String>>(14),
row.get::<_, Option<String>>(15),
row.get::<_, Option<String>>(16),
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<String>>(19),
points))
.unwrap();
})
.unwrap();
wtr.flush().unwrap();
}
fn get_contest_list(&self) -> Vec<Contest> {
let query = "SELECT id, location, filename, name, duration, public, start_date, end_date, min_grade, max_grade,
positionalnumber, requires_login, secret
......@@ -934,6 +1066,33 @@ impl MedalConnection for Connection {
start: row.get(1) })
.ok()?
}
fn get_all_participations_complete(&self, session_id: i32) -> Vec<(Participation, Contest)> {
let query = "SELECT participation.start_date, contest.id, location, filename, name, duration, public,
contest.start_date, end_date, min_grade, max_grade, requires_login, secret
FROM participation
JOIN contest ON participation.contest = contest.id
WHERE participation.session = $1";
self.query_map_many(query, &[&session_id], |row| {
(Participation { contest: row.get(1), user: session_id, start: row.get(0) },
Contest { id: Some(row.get(1)),
location: row.get(2),
filename: row.get(3),
name: row.get(4),
duration: row.get(5),
public: row.get(6),
start: row.get(7),
end: row.get(8),
min_grade: row.get(9),
max_grade: row.get(10),
positionalnumber: None,
requires_login: row.get(11),
secret: row.get(12),
taskgroups: Vec::new() })
})
.unwrap()
}
fn new_participation(&self, session: &str, contest_id: i32) -> Result<Participation, ()> {
let query = "SELECT session, start_date
FROM participation
......
......@@ -40,18 +40,21 @@ pub trait MedalConnection {
fn logout(&self, session: &str);
fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission>;
fn get_all_submissions(&self, session_id: i32, task: i32, subtask: Option<&str>) -> Vec<Submission>;
fn submit_submission(&self, submission: Submission);
fn get_grade_by_submission(&self, submission_id: i32) -> Grade;
fn get_contest_groups_grades(&self, session_id: i32, contest_id: i32)
-> (Vec<String>, Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)>);
fn get_taskgroup_user_grade(&self, session: &str, taskgroup_id: i32) -> Grade;
fn get_contest_user_grades(&self, session: &str, contest_id: i32) -> Vec<Grade>;
fn export_contest_results_to_file(&self, contest_id: i32, taskgroups_ids: &[(i32, String)], filename: &str);
fn get_contest_list(&self) -> Vec<Contest>;
fn get_contest_by_id(&self, contest_id: i32) -> Contest;
fn get_contest_by_id_partial(&self, contest_id: i32) -> Contest;
fn get_contest_by_id_complete(&self, contest_id: i32) -> Contest;
fn get_participation(&self, session: &str, contest_id: i32) -> Option<Participation>;
fn get_all_participations_complete(&self, session_id: i32) -> Vec<(Participation, Contest)>;
fn new_participation(&self, session: &str, contest_id: i32) -> Result<Participation, ()>;
fn get_task_by_id(&self, task_id: i32) -> Task;
fn get_task_by_id_complete(&self, task_id: i32) -> (Task, Taskgroup, Contest);
......
......@@ -24,6 +24,8 @@ use db_conn::{MedalConnection, MedalObject};
use db_objects::*;
use helpers;
fn gen_tosql_vector() -> Vec<&'static dyn postgres::types::ToSql> { Vec::new() }
trait Queryable {
fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F)
-> postgres::Result<Option<T>>
......
......@@ -37,6 +37,8 @@ use db_conn::{MedalConnection, MedalObject};
use db_objects::*;
use helpers;
fn gen_tosql_vector() -> Vec<&'static dyn postgres::types::ToSql> { Vec::new() }
trait Queryable {
fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F)
-> postgres::Result<Option<T>>
......@@ -719,6 +721,28 @@ impl MedalConnection for Connection {
}
}
}
fn get_all_submissions(&self, session_id: i32, task: i32, subtask: Option<&str>) -> Vec<Submission> {
match subtask {
None => {
let query = "SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
FROM submission
WHERE task = $1
AND session = $2";
self.query_map_many(query, &[&task, &session_id], |row| Submission { id: Some(row.get(0)),
task: task,
session_user: session_id,
grade: row.get(1),
validated: row.get(2),
nonvalidated_grade: row.get(3),
subtask_identifier: None,
value: row.get(4),
date: row.get(5),
needs_validation: row.get(6) })
.unwrap()
}
_ => unimplemented!(),
}
}
fn submit_submission(&self, mut submission: Submission) {
submission.save(self);
......@@ -894,6 +918,116 @@ impl MedalConnection for Connection {
.unwrap_or_default()
}
/* Warning: This function makes no use of rusts type safety. Handle with care when changeing */
fn export_contest_results_to_file(&self, contest_id: i32, taskgroups: &[(i32, String)], filename: &str) {
use std::fs::OpenOptions;
let file = OpenOptions::new().write(true).create(true).truncate(true).open(filename).unwrap();
let mut headers = vec!["id",
"username",
"logincode",
"oauth_foreign_id",
"oauth_provider",
"firstname",
"lastname",
"grade",
"sex",
"is_teacher",
"group_id",
"group_name",
"group_tag",
"teacher_id",
"teacher_firstname",
"teacher_lastname",
"teacher_oauth_foreign_id",
"teacher_oauth_provider",
"contest_id",
"start_date"];
let mut select_part = String::new();
let mut join_part = String::new();
let mut join_params = gen_tosql_vector();
join_params.push(&contest_id);
for (n, (id, name)) in taskgroups.iter().enumerate() {
select_part.push_str(&format!(",\n g{}.grade ", n));
join_part.push_str(&format!("\n LEFT JOIN grade AS g{} ON session.id = g{}.session AND g{}.taskgroup = ${} ", n, n, n, n + 2));
join_params.push(id);
headers.push(&name);
}
let query = format!("SELECT session.id,
session.username,
session.logincode,
session.oauth_foreign_id,
session.oauth_provider,
session.firstname,
session.lastname,
session.grade,
session.sex,
session.is_teacher,
session.managed_by,
usergroup.name,
usergroup.tag,
teacher.id,
teacher.firstname,
teacher.lastname,
teacher.oauth_foreign_id,
teacher.oauth_provider,
participation.contest,
participation.start_date
{}
FROM participation
JOIN session ON participation.session = session.id
{}
LEFT JOIN usergroup ON session.managed_by = usergroup.id
LEFT JOIN session AS teacher ON usergroup.admin = teacher.id
WHERE participation.contest = $1",
select_part, join_part);
use csv::Writer;
let mut wtr = Writer::from_writer(file);
wtr.serialize(&headers).unwrap();
wtr.flush().unwrap();
let file = wtr.into_inner().unwrap();
let mut wtr = Writer::from_writer(file);
self.query_map_many(&query, join_params.as_slice(), |row| {
let mut points = Vec::new();
for i in 20..20 + taskgroups.len() {
points.push(row.get::<_, Option<i32>>(i));
}
// Serialized as several tuples because Serde only supports tuples up to a certain length
// (16 according to https://docs.serde.rs/serde/trait.Deserialize.html)
wtr.serialize(((row.get::<_, i32>(0),
row.get::<_, Option<String>>(1),
row.get::<_, Option<String>>(2),
row.get::<_, Option<String>>(3),
row.get::<_, Option<String>>(4),
row.get::<_, Option<String>>(5),
row.get::<_, Option<String>>(6),
row.get::<_, i32>(7),
row.get::<_, Option<i32>>(8),
row.get::<_, bool>(9)),
(row.get::<_, Option<i32>>(10),
row.get::<_, Option<String>>(11),
row.get::<_, Option<String>>(12),
row.get::<_, Option<i32>>(13),
row.get::<_, Option<String>>(14),
row.get::<_, Option<String>>(15),
row.get::<_, Option<String>>(16),
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<String>>(19),
points))
.unwrap();
})
.unwrap();
wtr.flush().unwrap();
}
fn get_contest_list(&self) -> Vec<Contest> {
let query = "SELECT id, location, filename, name, duration, public, start_date, end_date, min_grade, max_grade,
positionalnumber, requires_login, secret
......@@ -1044,6 +1178,33 @@ impl MedalConnection for Connection {
start: row.get(1) })
.ok()?
}
fn get_all_participations_complete(&self, session_id: i32) -> Vec<(Participation, Contest)> {
let query = "SELECT participation.start_date, contest.id, location, filename, name, duration, public,
contest.start_date, end_date, min_grade, max_grade, requires_login, secret
FROM participation
JOIN contest ON participation.contest = contest.id
WHERE participation.session = $1";
self.query_map_many(query, &[&session_id], |row| {
(Participation { contest: row.get(1), user: session_id, start: row.get(0) },
Contest { id: Some(row.get(1)),
location: row.get(2),
filename: row.get(3),
name: row.get(4),
duration: row.get(5),
public: row.get(6),
start: row.get(7),
end: row.get(8),
min_grade: row.get(9),
max_grade: row.get(10),
positionalnumber: None,
requires_login: row.get(11),
secret: row.get(12),
taskgroups: Vec::new() })
})
.unwrap()
}
fn new_participation(&self, session: &str, contest_id: i32) -> Result<Participation, ()> {
let query = "SELECT session, start_date
FROM participation
......
......@@ -24,6 +24,8 @@ use db_conn::{MedalConnection, MedalObject};
use db_objects::*;
use helpers;
fn gen_tosql_vector() -> Vec<&'static dyn rusqlite::types::ToSql> { Vec::new() }
trait Queryable {
fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn rusqlite::types::ToSql], f: F)
-> rusqlite::Result<Option<T>>
......
......@@ -37,6 +37,8 @@ use db_conn::{MedalConnection, MedalObject};
use db_objects::*;
use helpers;
fn gen_tosql_vector() -> Vec<&'static dyn rusqlite::types::ToSql> { Vec::new() }
trait Queryable {
fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn rusqlite::types::ToSql], f: F)
-> rusqlite::Result<Option<T>>
......@@ -719,6 +721,28 @@ impl MedalConnection for Connection {
}
}
}
fn get_all_submissions(&self, session_id: i32, task: i32, subtask: Option<&str>) -> Vec<Submission> {
match subtask {
None => {
let query = "SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
FROM submission
WHERE task = ?1
AND session = ?2";
self.query_map_many(query, &[&task, &session_id], |row| Submission { id: Some(row.get(0)),
task: task,
session_user: session_id,
grade: row.get(1),
validated: row.get(2),
nonvalidated_grade: row.get(3),
subtask_identifier: None,
value: row.get(4),
date: row.get(5),
needs_validation: row.get(6) })
.unwrap()
}
_ => unimplemented!(),
}
}
fn submit_submission(&self, mut submission: Submission) {
submission.save(self);
......@@ -894,6 +918,116 @@ impl MedalConnection for Connection {