Commit 29764829 authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Merge branch 'v1.7' into deploy

parents a9e54f35 7c39b28c
Pipeline #949 failed with stages
in 16 minutes and 47 seconds
......@@ -13,7 +13,6 @@ config.json
*.pdf
*.json
*.yaml
*.sql
*.sqlite
*.csv
/bulma
......@@ -1064,7 +1064,7 @@ checksum = "79c56d6a0b07f9e19282511c83fc5b086364cbae4ba8c7d5f190c3d9b0425a48"
[[package]]
name = "medal"
version = "1.6.0"
version = "1.7.0"
dependencies = [
"bcrypt",
"csv",
......
[package]
version = "1.6.0"
version = "1.7.0"
name = "medal"
authors = ["Robert Czechowski <czechowski@bwinf.de>", "Daniel Brüning <bruening@bwinf.de>"]
......
......@@ -20,7 +20,7 @@ format: src/db_conn_postgres.rs
cargo +nightly fmt
clippy: src/db_conn_postgres.rs
cargo clippy --all-targets --features 'complete debug' -- -D warnings -A clippy::type-complexity -A clippy::option-map-unit-fn -A clippy::len-zero -A clippy::option-as-ref-deref -A clippy::or-fun-call
cargo clippy --all-targets --features 'complete debug' -- -D warnings -A clippy::type-complexity -A clippy::option-map-unit-fn -A clippy::len-zero -A clippy::option-as-ref-deref -A clippy::or-fun-call -A clippy::comparison-to-empty -A clippy::result-unit-err
src/db_conn_postgres.rs: src/db_conn_warning_header.txt src/db_conn_sqlite_new.header.rs src/db_conn_postgres.header.rs src/db_conn.base.rs
cd src; ./generate_connectors.sh
......
ALTER TABLE contest ADD COLUMN requires_contest TEXT;
ALTER TABLE usergroup ADD COLUMN group_created TIMESTAMP;
UPDATE usergroup SET group_created = NOW() WHERE group_created IS NULL;
ALTER TABLE session ADD COLUMN account_created TIMESTAMP;
UPDATE session SET account_created = NOW() WHERE account_created IS NULL;
ALTER TABLE contest ADD COLUMN requires_contest TEXT;
ALTER TABLE usergroup ADD COLUMN group_created TEXT;
UPDATE usergroup SET group_created = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE group_created IS NULL;
ALTER TABLE session ADD COLUMN account_created TEXT;
UPDATE session SET account_created = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE account_created IS NULL;
......@@ -12,18 +12,31 @@
* You should have received a copy of the GNU Affero General Public License along with this program. If not, see *
\* <http://www.gnu.org/licenses/>. */
use oauth_provider;
use std::path::{Path, PathBuf};
use structopt::StructOpt;
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct OauthProvider {
pub provider_id: String,
pub medal_oauth_type: String,
pub url: String,
pub client_id: String,
pub client_secret: String,
pub access_token_url: String,
pub user_data_url: String,
pub school_data_url: Option<String>,
pub school_data_secret: Option<String>,
pub allow_teacher_login_without_school: Option<bool>,
pub login_link_text: String,
}
#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct Config {
pub host: Option<String>,
pub port: Option<u16>,
pub self_url: Option<String>,
pub oauth_providers: Option<Vec<oauth_provider::OauthProvider>>,
pub oauth_providers: Option<Vec<OauthProvider>>,
pub database_file: Option<PathBuf>,
pub database_url: Option<String>,
pub template: Option<String>,
......
......@@ -27,6 +27,7 @@ struct ContestYaml {
public_listing: Option<bool>,
requires_login: Option<bool>,
requires_contest: Option<Vec<String>>,
secret: Option<String>,
message: Option<String>,
......@@ -62,6 +63,7 @@ pub fn parse_yaml(content: &str, filename: &str, directory: &str) -> Option<Cont
config.max_grade,
config.position,
config.requires_login,
config.requires_contest.map(|list| list.join(",")),
config.secret,
config.message);
// TODO: Timeparsing should fail more pleasantly (-> Panic, thus shows message)
......
......@@ -21,7 +21,7 @@ use db_objects::OptionSession;
use db_objects::SessionUser;
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
use helpers;
use oauth_provider::OauthProvider;
use config::OauthProvider;
use webfw_iron::{json_val, to_json};
#[derive(Serialize, Deserialize)]
......@@ -286,6 +286,18 @@ pub struct ContestStartConstraints {
pub grade_matching: bool,
}
fn check_contest_qualification<T: MedalConnection>(conn: &T, session: &SessionUser, contest: &Contest) -> Option<bool> {
let required_contests = contest.requires_contest.as_ref()?.split(',');
for req_contest in required_contests {
if conn.has_participation_by_contest_file(session.id, &contest.location, req_contest) {
return Some(true);
}
}
Some(false)
}
fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> ContestStartConstraints {
let now = time::get_time();
let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };
......@@ -305,7 +317,8 @@ fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> Contes
contest_running,
grade_too_low,
grade_too_high,
grade_matching }
grade_matching,
}
}
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str,
......@@ -333,11 +346,13 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token
fill_oauth_data(login_info, &mut data);
let constraints = check_contest_constraints(&session, &contest);
let is_qualified = check_contest_qualification(conn, &session, &contest).unwrap_or(true);
let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching && is_qualified;
let has_duration = contest.duration > 0;
data.insert("constraints".to_string(), to_json(&constraints));
data.insert("is_qualified".to_string(), to_json(&is_qualified));
data.insert("has_duration".to_string(), to_json(&has_duration));
data.insert("can_start".to_string(), to_json(&can_start));
......@@ -516,6 +531,12 @@ pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_toke
return Err(MedalError::AccessDenied);
}
let is_qualified = check_contest_qualification(conn, &session, &contest);
if is_qualified == Some(false) {
return Err(MedalError::AccessDenied);
}
// Start contest
match conn.new_participation(&session_token, contest_id) {
Ok(_) => Ok(()),
......@@ -587,8 +608,7 @@ pub fn signup<T: MedalConnection>(conn: &T, session_token: Option<String>, signu
pub fn signupdata(query_string: Option<String>) -> json_val::Map<String, json_val::Value> {
let mut data = json_val::Map::new();
if let Some(query) = query_string {
if query.starts_with("status=") {
let status: &str = &query[7..];
if let Some(status) = query.strip_prefix("status=") {
if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
......@@ -912,6 +932,14 @@ pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_tok
user.firstname = Some(line[2].clone());
user.lastname = Some(line[3].clone());
use db_objects::Sex;
match line[4].as_str() {
"m" => user.sex = Some(Sex::Male as i32),
"f" => user.sex = Some(Sex::Female as i32),
"d" => user.sex = Some(Sex::Diverse as i32),
_ => user.sex = None,
}
group.members.push(user);
}
conn.create_group_with_users(group);
......@@ -978,8 +1006,7 @@ pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id:
data.insert("ownprofile".into(), to_json(&true));
if let Some(query) = query_string {
if query.starts_with("status=") {
let status: &str = &query[7..];
if let Some(status) = query.strip_prefix("status=") {
if ["NothingChanged",
"DataChanged",
"PasswordChanged",
......@@ -1043,8 +1070,7 @@ pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id:
data.insert("ownprofile".into(), to_json(&false));
if let Some(query) = query_string {
if query.starts_with("status=") {
let status: &str = &query[7..];
if let Some(status) = query.strip_prefix("status=") {
if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
data.insert((status).to_string(), to_json(&true));
}
......@@ -1063,8 +1089,8 @@ pub enum ProfileStatus {
PasswordChanged,
PasswordMissmatch,
}
impl std::convert::Into<String> for ProfileStatus {
fn into(self) -> String { format!("{:?}", self) }
impl From<ProfileStatus> for String {
fn from(s: ProfileStatus) -> String { format!("{:?}", s) }
}
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
......@@ -1482,6 +1508,49 @@ pub fn admin_contest_export<T: MedalConnection>(conn: &T, contest_id: i32, sessi
Ok(filename)
}
pub fn admin_show_cleanup<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();
data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
Ok(("admin_cleanup".to_string(), data))
}
pub fn admin_do_cleanup<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str)
-> MedalValueResult {
let session = conn.get_session(&session_token)
.ensure_logged_in()
.ok_or(MedalError::NotLoggedIn)?
.ensure_admin()
.ok_or(MedalError::AccessDenied)?;
if session.csrf_token != csrf_token {
return Err(MedalError::CsrfCheckFailed);
}
let now = time::get_time();
let maxstudentage = now - time::Duration::days(180); // Delete managed users after 180 days of inactivity
let maxteacherage = now - time::Duration::days(1095); // Delete teachers after 3 years of inactivity
let maxage = now - time::Duration::days(3650); // Delete every user after 10 years of inactivity
let result = conn.remove_old_users_and_groups(maxstudentage, Some(maxteacherage), Some(maxage));
let mut data = json_val::Map::new();
if let Ok((n_users, n_groups, n_teachers, n_other)) = result {
let infodata = format!(",\"n_users\":{},\"n_groups\":{},\"n_teachers\":{},\"n_other\":{}",
n_users, n_groups, n_teachers, n_other);
data.insert("data".to_string(), to_json(&infodata));
Ok(("delete_ok".to_string(), data))
} else {
data.insert("reason".to_string(), to_json(&"Fehler."));
Ok(("delete_fail".to_string(), data))
}
}
#[derive(PartialEq, Clone, Copy)]
pub enum UserType {
User,
......
......@@ -25,9 +25,10 @@ impl MedalObject<Connection> for Group {
match self.get_id() {
Some(_id) => unimplemented!(),
None => {
let query = "INSERT INTO usergroup (name, groupcode, tag, admin)
VALUES ($1, $2, $3, $4)";
conn.execute(query, &[&self.name, &self.groupcode, &self.tag, &self.admin]).unwrap();
let query = "INSERT INTO usergroup (name, groupcode, tag, admin, group_created)
VALUES ($1, $2, $3, $4, $5)";
let now = time::get_time();
conn.execute(query, &[&self.name, &self.groupcode, &self.tag, &self.admin, &now]).unwrap();
self.set_id(conn.get_last_id().unwrap());
}
}
......@@ -124,7 +125,7 @@ impl MedalObject<Connection> for Contest {
let query = "UPDATE contest
SET location = $2,filename = $3, name = $4, duration = $5, public = $6, start_date = $7,
end_date = $8, min_grade = $9, max_grade = $10, positionalnumber = $11,
requires_login = $12, secret = $13, message = $14
requires_login = $12, requires_contest = $13, secret = $14, message = $15
WHERE id = $1";
conn.execute(query,
&[&id,
......@@ -139,6 +140,7 @@ impl MedalObject<Connection> for Contest {
&self.max_grade,
&self.positionalnumber,
&self.requires_login,
&self.requires_contest,
&self.secret,
&self.message])
.unwrap();
......@@ -146,8 +148,9 @@ impl MedalObject<Connection> for Contest {
}
None => {
let query = "INSERT INTO contest (location, filename, name, duration, public, start_date, end_date,
min_grade, max_grade, positionalnumber, requires_login, secret, message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)";
min_grade, max_grade, positionalnumber, requires_login,
requires_contest, secret, message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)";
conn.execute(query,
&[&self.location,
&self.filename,
......@@ -160,6 +163,7 @@ impl MedalObject<Connection> for Contest {
&self.max_grade,
&self.positionalnumber,
&self.requires_login,
&self.requires_contest,
&self.secret,
&self.message])
.unwrap();
......@@ -202,8 +206,8 @@ impl MedalConnection for Connection {
// fn get_session<T: ToSql>(&self, key: T, keyname: &str) -> Option<SessionUser> {
fn get_session(&self, key: &str) -> Option<SessionUser> {
let query = "SELECT id, csrf_token, last_login, last_activity, permanent_login, username, password, salt,
logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname, street,
let query = "SELECT id, csrf_token, last_login, last_activity, account_created, username, password,
salt, logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname, street,
zip, city, nation, grade, sex, is_admin, is_teacher, managed_by, oauth_provider, oauth_foreign_id
FROM session
WHERE session_token = $1";
......@@ -212,7 +216,7 @@ impl MedalConnection for Connection {
csrf_token: row.get(1),
last_login: row.get(2),
last_activity: row.get(3),
permanent_login: row.get(4),
account_created: row.get(4),
username: row.get(5),
password: row.get(6),
......@@ -274,7 +278,7 @@ impl MedalConnection for Connection {
sex = $11,
is_admin = $12,
is_teacher = $13,
permanent_login = $14,
managed_by = $14,
email = $15,
email_unconfirmed = $16
WHERE id = $17",
......@@ -291,7 +295,7 @@ impl MedalConnection for Connection {
&session.sex,
&session.is_admin,
&session.is_teacher,
&session.permanent_login,
&session.managed_by,
&session.email,
&session.email_unconfirmed,
&session.id])
......@@ -301,15 +305,21 @@ impl MedalConnection for Connection {
let csrf_token = helpers::make_csrf_token();
let now = time::get_time();
let query = "INSERT INTO session (session_token, csrf_token, last_activity, permanent_login, grade, sex,
let query = "INSERT INTO session (session_token, csrf_token, last_activity, account_created, grade, sex,
is_teacher)
VALUES ($1, $2, $3, $4, $5, $6, $7)";
self.execute(query, &[&session_token, &csrf_token, &now, &false, &0, &None::<i32>, &false]).unwrap();
self.execute(query, &[&session_token, &csrf_token, &now, &None::<time::Timespec>, &0, &None::<i32>, &false]).unwrap();
let id = self.get_last_id().expect("Expected to get last row id");
SessionUser::minimal(id, session_token.to_owned(), csrf_token)
}
fn session_set_activity_dates(&self, session_id: i32, account_created: Option<time::Timespec>, last_login: Option<time::Timespec>, last_activity: Option<time::Timespec>) {
let query = "UPDATE session
SET account_created = $2, last_login = $3, last_activity = $4
WHERE id = $1";
self.execute(query, &[&session_id, &account_created, &last_login, &last_activity]).unwrap();
}
fn get_session_or_new(&self, key: &str) -> Result<SessionUser, ()> {
fn disable_old_session_and_create_new(conn: &Connection, key: &str) -> Result<SessionUser, ()> {
let query = "UPDATE session
......@@ -328,7 +338,7 @@ impl MedalConnection for Connection {
}
fn get_user_by_id(&self, user_id: i32) -> Option<SessionUser> {
let query = "SELECT session_token, csrf_token, last_login, last_activity, permanent_login, username, password,
let query = "SELECT session_token, csrf_token, last_login, last_activity, account_created, username, password,
salt, logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname,
street, zip, city, nation, grade, sex, is_admin, is_teacher, managed_by, oauth_provider,
oauth_foreign_id
......@@ -339,7 +349,7 @@ impl MedalConnection for Connection {
csrf_token: row.get(1),
last_login: row.get(2),
last_activity: row.get(3),
permanent_login: row.get(4),
account_created: row.get(4),
username: row.get(5),
password: row.get(6),
......@@ -492,14 +502,15 @@ impl MedalConnection for Connection {
// Add!
_ => {
let query = "INSERT INTO session (session_token, csrf_token, last_login, last_activity,
permanent_login, grade, sex, is_teacher, is_admin, oauth_foreign_id,
account_created, grade, sex, is_teacher, is_admin, oauth_foreign_id,
oauth_provider, firstname, lastname)
VALUES ($1, $2, $3, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)";
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)";
self.execute(query,
&[&session_token,
&csrf_token,
&now,
&false,
&now,
&now,
&(if is_teacher { 255 } else { 0 }),
&sex,
&is_teacher,
......@@ -533,7 +544,7 @@ impl MedalConnection for Connection {
let login_code = helpers::make_login_code(); // TODO: check for collisions
let now = time::get_time();
let query = "INSERT INTO session (session_token, csrf_token, last_login, last_activity, permanent_login,
let query = "INSERT INTO session (session_token, csrf_token, last_login, last_activity, account_created,
logincode, grade, sex, is_teacher, managed_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)";
self.execute(query,
......@@ -541,7 +552,7 @@ impl MedalConnection for Connection {
&csrf_token,
&now,
&now,
&false,
&now,
&login_code,
&0,
&None::<i32>,
......@@ -556,21 +567,23 @@ impl MedalConnection for Connection {
// Generate group ID:
group.save(self);
let now = time::get_time();
for user in group.members {
let csrf_token = helpers::make_csrf_token();
let login_code = helpers::make_login_code(); // TODO: check for collisions
let query = "INSERT INTO session (firstname, lastname, csrf_token, permanent_login, logincode, grade, sex,
let query = "INSERT INTO session (firstname, lastname, csrf_token, account_created, logincode, grade, sex,
is_teacher, managed_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
self.execute(query,
&[&user.firstname,
&user.lastname,
&csrf_token,
&false,
&now,
&login_code,
&user.grade,
&None::<i32>,
&user.sex,
&false,
&group.id])
.unwrap();
......@@ -860,7 +873,7 @@ impl MedalConnection for Connection {
.unwrap_or_default()
}
/* Warning: This function makes no use of rusts type safety. Handle with care when changeing */
/* Warning: This function makes no use of rusts typeb 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();
......@@ -975,8 +988,8 @@ impl MedalConnection for Connection {
teacher_school_id,
row.get::<_, Option<String>>(17)),
row.get::<_, Option<i32>>(18),
row.get::<_, Option<self::time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &self::time::at(ts)).unwrap()),
row.get::<_, Option<time::Timespec>>(19)
.map(|ts| self::time::strftime("%FT%T%z", &time::at(ts)).unwrap()),
points))
.unwrap();
})
......@@ -986,9 +999,9 @@ impl MedalConnection for Connection {
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, message
positionalnumber, requires_login, requires_contest, secret, message
FROM contest
ORDER BY positionalnumber";
ORDER BY positionalnumber DESC";
self.query_map_many(query, &[], |row| Contest { id: Some(row.get(0)),
location: row.get(1),
filename: row.get(2),
......@@ -1001,15 +1014,16 @@ impl MedalConnection for Connection {
max_grade: row.get(9),
positionalnumber: row.get(10),
requires_login: row.get(11),
secret: row.get(12),
message: row.get(13),
requires_contest: row.get(12),
secret: row.get(13),
message: row.get(14),
taskgroups: Vec::new() })
.unwrap()
}
fn get_contest_by_id(&self, contest_id: i32) -> Contest {
let query = "SELECT location, filename, name, duration, public, start_date, end_date, min_grade, max_grade,
requires_login, secret, message
requires_login, requires_contest, secret, message
FROM contest
WHERE id = $1";
self.query_map_one(query, &[&contest_id], |row| Contest { id: Some(contest_id),
......@@ -1024,8 +1038,9 @@ impl MedalConnection for Connection {
max_grade: row.get(8),
positionalnumber: None,
requires_login: row.get(9),
secret: row.get(10),
message: row.get(11),
requires_contest: row.get(10),
secret: row.get(11),
message: row.get(12),
taskgroups: Vec::new() })
.unwrap()
.unwrap() // TODO: Should return Option?
......@@ -1034,7 +1049,7 @@ impl MedalConnection for Connection {
fn get_contest_by_id_complete(&self, contest_id: i32) -> Contest {
let query = "SELECT contest.location, contest.filename, contest.name, contest.duration, contest.public,
contest.start_date, contest.end_date, contest.min_grade, contest.max_grade,
contest.requires_login, contest.secret, contest.message,
contest.requires_login, contest.requires_contest, contest.secret, contest.message,
taskgroup.id, taskgroup.name,
task.id, task.location, task.stars
FROM contest
......@@ -1057,16 +1072,17 @@ impl MedalConnection for Connection {
max_grade: row.get(8),
positionalnumber: None,
requires_login: row.get(9),
secret: row.get(10),
message: row.get(11),
requires_contest: row.get(10),
secret: row.get(11),
message: row.get(12),
taskgroups: Vec::new() },
Taskgroup { id: Some(row.get(12)),
Taskgroup { id: Some(row.get(13)),
contest: contest_id,
name: row.get(13),
name: row.get(14),
active: true,
positionalnumber: None,
tasks: Vec::new() },
Task { id: Some(row.get(14)), taskgroup: row.get(12), location: row.get(15), stars: row.get(16) })
Task { id: Some(row.get(15)), taskgroup: row.get(13), location: row.get(16), stars: row.get(17) })
})
.unwrap();
let mut taskgroupcontest_iter = taskgroupcontest.into_iter();
......@@ -1093,7 +1109,7 @@ impl MedalConnection for Connection {
fn get_contest_by_id_partial(&self, contest_id: i32) -> Contest {
let query = "SELECT contest.location, contest.filename, contest.name, contest.duration, contest.public,
contest.start_date, contest.end_date, contest.min_grade, contest.max_grade,
contest.requires_login, contest.secret, contest.message,
contest.requires_login, contest.requires_contest, contest.secret, contest.message,
taskgroup.id, taskgroup.name
FROM contest
JOIN taskgroup ON contest.id = taskgroup.contest
......@@ -1112,12 +1128,13 @@ impl MedalConnection for Connection {
max_grade: row.get(8),
positionalnumber: None,
requires_login: row.get(9),
secret: row.get(10),
message: row.get(11),
requires_contest: row.get(10),
secret: row.get(11),
message: row.get(12),
taskgroups: Vec::new() },
Taskgroup { id: Some(row.get(12)),
Taskgroup { id: Some(row.get(13)),
contest: contest_id,
name: row.get(13),
name: row.get(14),
active: true,
positionalnumber: None,
tasks: Vec::new() })
......@@ -1159,7 +1176,8 @@ impl MedalConnection for Connection {
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, message
contest.start_date, end_date, min_grade, max_grade, requires_login, requires_contest,
secret, message
FROM participation
JOIN contest ON participation.contest = contest.id
WHERE participation.session = $1";
......@@ -1177,13 +1195,24 @@ impl MedalConnection for Connection {
max_grade: row.get(10),
positionalnumber: None,
requires_login: row.get(11),
secret: row.get(12),
message: row.get(13),
requires_contest: row.get(12),
secret: row.get(13),
message: row.get(14),