Commit 5a4379e5 authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Make contest / task work

parent 0a18dacd
......@@ -18,14 +18,16 @@ pub trait MedalConnection {
fn login_with_code(&self, session: Option<String>, logincode: String) -> Result<SessionUser,()>;
fn logout(&self, session: String);
fn load_submission(&self, session: &SessionUser, task: String, subtask: Option<String>) -> Submission;
fn submit_submission(&self, session: &SessionUser, task: String, subtask: Option<String>, submission: Submission);
fn load_submission(&self, session: &SessionUser, task: u32, subtask: Option<String>) -> Option<Submission>;
fn submit_submission(&self, submission: Submission);
fn get_contest_list(&self) -> Vec<Contest>;
fn get_contest_by_id(&self, contest_id : u32) -> Contest;
fn get_contest_by_id_complete(&self, contest_id : u32) -> Contest;
fn get_task_by_id(&self, task_id : u32) -> Task;
fn get_task_by_id_complete(&self, task_id : u32) -> (Task, Taskgroup, Contest);
fn get_contest_by_id(&self, contest_id: u32) -> Contest;
fn get_contest_by_id_complete(&self, contest_id :u32) -> Contest;
fn get_participation(&self, session: String, contest_id: u32) -> Option<Participation>;
fn new_participation(&self, session: String, contest_id: u32) -> Result<Participation, ()>;
fn get_task_by_id(&self, task_id: u32) -> Task;
fn get_task_by_id_complete(&self, task_id: u32) -> (Task, Taskgroup, Contest);
fn get_submission_to_validate(&self, tasklocation: String, subtask: Option<String>) -> u32;
fn find_next_submission_to_validate(&self, userid: u32, taskgroupid: u32);
......@@ -33,5 +35,5 @@ pub trait MedalConnection {
pub trait MedalObject<T: MedalConnection> {
fn save(&mut self, conn: &mut T);
fn save(&mut self, conn: &T);
}
......@@ -42,7 +42,7 @@ impl MedalConnection for Connection {
}
fn get_session(&self, key: String) -> Option<SessionUser> {
self.query_row("SELECT id, session_token, csrf_token, last_login, last_activity, permanent_login, username, password, logincode, email, email_unconfirmed, email_confirmation_code, firstname, lastname, street, zip, city, nation, grade, is_teacher, managed_by, pms_id, pms_school_id FROM session_user WHERE session_token = ?1", &[&key], |row| {
Some(self.query_row("SELECT id, csrf_token, last_login, last_activity, permanent_login, username, password, logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname, street, zip, city, nation, grade, is_teacher, managed_by, pms_id, pms_school_id FROM session_user WHERE session_token = ?1", &[&key], |row| {
SessionUser {
id: row.get(0),
session_token: Some(key.clone()),
......@@ -72,7 +72,7 @@ impl MedalConnection for Connection {
pms_id: row.get(20),
pms_school_id: row.get(21),
}
}).ok()
}).unwrap())
}
fn new_session(&self) -> SessionUser {
let session_token = "123".to_string();
......@@ -104,8 +104,9 @@ impl MedalConnection for Connection {
let session_token: String = thread_rng().gen_ascii_chars().take(10).collect();
let csrf_token: String = thread_rng().gen_ascii_chars().take(10).collect();
let now = time::get_time();
self.execute("UPDATE session_user SET session_token = ?1, csrf_token = ?2, last_login = date('now'), last_activity = date('now') WHERE id = ?3", &[&session_token, &csrf_token, &id]).unwrap();
self.execute("UPDATE session_user SET session_token = ?1, csrf_token = ?2, last_login = ?3, last_activity = ?3 WHERE id = ?4", &[&session_token, &csrf_token, &now, &id]).unwrap();
Ok(session_token)
}
......@@ -122,8 +123,41 @@ impl MedalConnection for Connection {
}
fn load_submission(&self, session: &SessionUser, task: String, subtask: Option<String>) -> Submission {unimplemented!()}
fn submit_submission(&self, session: &SessionUser, task: String, subtask: Option<String>, submission: Submission) {unimplemented!()}
fn load_submission(&self, session: &SessionUser, task: u32, subtask: Option<String>) -> Option<Submission> {
match subtask {
None => self.query_row("SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation FROM submission WHERE task = ?1 AND session_user = ?2 ORDER BY id DESC LIMIT 1", &[&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),
}
}).ok(),
Some(subtask_id) => self.query_row("SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation FROM submission WHERE task = ?1 AND session_user = ?2 AND subtask_identifier = ?3 ORDER BY id DESC LIMIT 1", &[&task, &session.id, &subtask_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: Some(subtask_id.clone()),
value: row.get(4),
date: row.get(5),
needs_validation: row.get(6),
}
}).ok()
}
}
fn submit_submission(&self, mut submission: Submission) {
submission.save(self);
}
fn get_contest_list(&self) -> Vec<Contest> {
let mut stmt = self.prepare("SELECT id, location, filename, name, duration, public, start_date, end_date FROM contest").unwrap();
......@@ -156,7 +190,7 @@ impl MedalConnection for Connection {
end: row.get(6),
taskgroups: Vec::new(),
}
}).unwrap()
}).unwrap()
}
fn get_contest_by_id_complete(&self, contest_id : u32) -> Contest {
......@@ -200,6 +234,30 @@ impl MedalConnection for Connection {
contest.taskgroups.push(taskgroup);
contest
}
fn get_participation(&self, session: String, contest_id: u32) -> Option<Participation> {
self.query_row("SELECT user, start_date FROM participation JOIN session_user ON session_user.id = user WHERE session_user.session_token = ?1 AND contest = ?2", &[&session, &contest_id], |row| {
Participation {
contest: contest_id,
user: row.get(0),
start: row.get(1)
}
}).ok()
}
fn new_participation(&self, session: String, contest_id: u32) -> Result<Participation, ()> {
match self.query_row("SELECT user, start_date FROM participation JOIN session_user ON session_user.id = user WHERE session_user.session_token = ?1 AND contest = ?2", &[&session, &contest_id], |row| {()}) {
Ok(()) => Err(()),
Err(_) => {
let now = time::get_time();
self.execute(
"INSERT INTO participation (contest, user, start_date)
SELECT ?1, id, ?2 FROM session_user WHERE session_token = ?3",
&[&contest_id, &now, &session]).unwrap();
Ok(self.get_participation(session, contest_id).unwrap())
}
}
}
fn get_task_by_id(&self, task_id : u32) -> Task {
self.query_row(
"SELECT location, stars, taskgroup FROM task WHERE id = ?1",
......@@ -259,7 +317,7 @@ impl MedalConnection for Connection {
impl MedalObject<Connection> for Task {
fn save(&mut self, conn: &mut Connection) {
fn save(&mut self, conn: &Connection) {
conn.query_row("SELECT id FROM task WHERE taskgroup = ?1 AND location = ?2", &[&self.taskgroup, &self.location], |row| {row.get(0)})
.and_then(|id| { self.setId(id); Ok(()) });
......@@ -285,7 +343,7 @@ impl MedalObject<Connection> for Task {
impl MedalObject<Connection> for Taskgroup {
fn save(&mut self, conn: &mut Connection) {
fn save(&mut self, conn: &Connection) {
conn.query_row("SELECT id FROM taskgroup WHERE contest = ?1 AND name = ?2", &[&self.contest, &self.name], |row| {row.get(0)})
.and_then(|id| { self.setId(id); Ok(()) });
......@@ -314,7 +372,7 @@ impl MedalObject<Connection> for Taskgroup {
}
impl MedalObject<Connection> for Contest {
fn save(&mut self, conn: &mut Connection) {
fn save(&mut self, conn: &Connection) {
conn.query_row("SELECT id FROM contest WHERE location = ?1 AND filename = ?2", &[&self.location, &self.filename], |row| {row.get(0)})
.and_then(|id| { self.setId(id); Ok(()) });
......@@ -347,14 +405,40 @@ impl MedalObject<Connection> for Contest {
}
impl MedalObject<Connection> for Grade {
fn save(&mut self, conn: &mut Connection) {
fn save(&mut self, conn: &Connection) {
conn.execute("INSERT OR REPLACE INTO grade (taskgroup, user, grade, validated) VALUES (?1, ?2, ?3, ?4)", &[&self.taskgroup, &self.user, &self.grade, &self.validated]).unwrap();
}
}
impl MedalObject<Connection> for Participation {
fn save(&mut self, conn: &mut Connection) {
fn save(&mut self, conn: &Connection) {
conn.execute("INSERT INTO participation (contest, user, start_date) VALUES (?1, ?2, ?3)", &[&self.contest, &self.user, &self.start]).unwrap();
}
}
impl MedalObject<Connection> for Submission {
fn save(&mut self, conn: &Connection) {
match self.getId() {
Some(id) =>
unimplemented!(),
None => {
conn.execute("INSERT INTO submission (task, session_user, grade, validated, nonvalidated_grade, subtask_identifier, value, date, needs_validation) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)", &[&self.task, &self.session_user, &self.grade, &self.validated, &self.nonvalidated_grade, &self.subtask_identifier, &self.value, &self.date, &self.needs_validation]).unwrap();
// TODO, update ID
}
}
}
}
impl MedalObject<Connection> for Group {
fn save(&mut self, conn: &Connection) {
match self.getId() {
Some(id) =>
unimplemented!(),
None => {
conn.execute("INSERT INTO usergroup (name, groupcode, tag, admin) VALUES (?1, ?2, ?3, ?4)", &[&self.name, &self.groupcode, &self.tag, &self.admin]).unwrap();
// TODO, update ID
}
}
}
}
......@@ -33,6 +33,15 @@ pub struct SessionUser {
pub pms_school_id: Option<u32>,
}
pub struct Group {
pub id: Option<u32>,
pub name: String,
pub groupcode: String,
pub tag: String,
pub admin: u32,
pub members: Vec<SessionUser>
}
pub struct Contest {
pub id: Option<u32>,
pub location: String,
......@@ -45,6 +54,7 @@ pub struct Contest {
pub taskgroups: Vec<Taskgroup>,
}
pub struct Taskgroup {
pub id: Option<u32>,
pub contest: u32,
......@@ -61,16 +71,16 @@ pub struct Task {
pub struct Submission {
id: Option<u32>,
session_user: u32,
task: u32,
contest: u32,
grade: u32,
validated: bool,
nonvalidated_grade: u32,
subtask_identifier: Option<String>,
value: String,
date: Timespec,
pub id: Option<u32>,
pub session_user: u32,
pub task: u32,
pub grade: u32,
pub validated: bool,
pub nonvalidated_grade: u32,
pub needs_validation: bool,
pub subtask_identifier: Option<String>,
pub value: String,
pub date: Timespec,
}
pub struct Grade {
......@@ -91,6 +101,7 @@ impl HasId for Submission { fn getId(&self) -> Option<u32> { self.id } fn setId(
impl HasId for Task { fn getId(&self) -> Option<u32> { self.id } fn setId(&mut self, id: u32) { self.id = Some(id);} }
impl HasId for Taskgroup { fn getId(&self) -> Option<u32> { self.id } fn setId(&mut self, id: u32) { self.id = Some(id);} }
impl HasId for Contest { fn getId(&self) -> Option<u32> { self.id } fn setId(&mut self, id: u32) { self.id = Some(id);} }
impl HasId for Group { fn getId(&self) -> Option<u32> { self.id } fn setId(&mut self, id: u32) { self.id = Some(id);} }
impl Contest {
......
......@@ -2,7 +2,9 @@ use webfw_iron::{to_json, json_val};
use rusqlite::Connection;
use db_conn::MedalConnection;
use db_conn::{MedalConnection, MedalObject};
use db_objects::{Submission, Group};
pub fn blaa() -> (String, json_val::Map<String, json_val::Value>) {
let mut data = json_val::Map::new();
......@@ -45,7 +47,7 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> (String, json_val::Map<Str
}
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32) -> (String, json_val::Map<String, json_val::Value>) {
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) {
let c = conn.get_contest_by_id(contest_id);
let ci = ContestInfo {
id: c.id.unwrap(),
......@@ -56,11 +58,55 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32) -> (String, j
public: c.public
};
let mut data = json_val::Map::new();
data.insert("contest".to_string(), to_json(&ci));
("contest".to_owned(), data)
match conn.get_participation(session_token, contest_id) {
None => {
("contest".to_owned(), data)
},
Some(participation) => {
let now = time::get_time();
let passed_secs = now.sec - participation.start.sec;
if passed_secs < 0 {
// behandle inkonsistente Serverzeit
}
data.insert("participation_start_date".to_string(), to_json(&format!("{}",passed_secs)));
let left_secs = (ci.duration as i64) * 60 - passed_secs;
if left_secs < 0 {
// Contest over
}
else {
let left_min = left_secs / 60;
let left_sec = left_secs % 60;
if left_sec < 10 {
data.insert("time_left".to_string(), to_json(&format!("{}:0{}",left_min,left_sec)));
}
else {
data.insert("time_left".to_string(), to_json(&format!("{}:{}",left_min,left_sec)));
}
}
("contest".to_owned(), data)
}
}
}
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String, csrf_token:String) -> Result<(),(String, json_val::Map<String, json_val::Value>)> {
let mut data = json_val::Map::new();
match conn.new_participation(session_token, contest_id) {
Ok(_) => Ok(()),
_ => Err(("conteststartfail".to_owned(), data))
}
}
......@@ -87,7 +133,78 @@ pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) -> ()
}
}
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> Result<String,()> {
let session = conn.get_session(session_token).unwrap(); // TODO handle error
match conn.load_submission(&session, task_id, None) {
Some(submission) => Ok(submission.value),
None => Ok("{}".to_string())
}
}
pub fn save_submission<T: MedalConnection>(conn: &T, task_id: u32, session_token: String, csrf_token: String, data: String) -> Result<String,()> {
let session = conn.get_session(session_token).unwrap(); // TODO handle error
if session.csrf_token != csrf_token {
return Err(());
}
let submission = Submission {
id: None,
session_user: session.id,
task: task_id,
grade: 0,
validated: false,
nonvalidated_grade: 0,
needs_validation: true,
subtask_identifier: None,
value: data,
date: time::get_time()
};
conn.submit_submission(submission);
Ok("{}".to_string())
}
pub fn show_task<T: MedalConnection>(conn: &T, task_id: u32, session_token: String) -> (String, json_val::Map<String, json_val::Value>) {
let session = conn.get_session(session_token).unwrap(); // TODO handle error
let mut data = json_val::Map::new();
data.insert("name".to_string(), to_json(&"Blubtask"));
data.insert("taskid".to_string(), to_json(&task_id));
data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
("task".to_owned(), data)
}
//?state=42&scope=authenticate&code=250a4f49-e122-4b10-8da0-bc400ba5ea3d
// TOKEN -> {"token_type" : "Bearer","expires" : 3600,"refresh_token" : "R3a716e23-b320-4dab-a529-4c19e6b7ffc5","access_token" : "A6f681904-ded6-4e8b-840e-ac79ca1ffc07"}
// DATA -> {"lastName" : "Czechowski","gender" : "?","userType" : "a","userID" : "12622","dateOfBirth" : "2001-01-01","firstName" : "Robert","eMail" : "czechowski@bwinf.de","schoolId" : -1}
pub fn add_group<T: MedalConnection>(conn: &T, session_token: String, csrf_token: String, name: String, tag: String) -> Result<u32, (String, json_val::Map<String, json_val::Value>)> {
let session = conn.get_session(session_token).unwrap(); // TODO handle error
if session.csrf_token != csrf_token {
let mut data = json_val::Map::new();
return Err(("error".to_owned(), data));
}
let group = Group {
id: None,
name: name,
groupcode: "blub".to_string(),
tag: tag,
admin: session.id,
members: Vec::new()
};
group.save(conn);
Ok(group.id.unwrap())
}
......@@ -126,6 +126,7 @@ fn contests(req: &mut Request) -> IronResult<Response> {
fn contest(req: &mut Request) -> IronResult<Response> {
let contest_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0);
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
let (template, data) = {
// hier ggf. Daten aus dem Request holen
......@@ -133,7 +134,7 @@ fn contest(req: &mut Request) -> IronResult<Response> {
let conn = mutex.lock().unwrap();
// Antwort erstellen und zurücksenden
functions::show_contest(&*conn, contest_id)
functions::show_contest(&*conn, contest_id, session_token.token)
};
let mut resp = Response::new();
......@@ -141,7 +142,38 @@ fn contest(req: &mut Request) -> IronResult<Response> {
Ok(resp)
}
fn login(_: &mut Request) -> IronResult<Response> {
fn contest_post(req: &mut Request) -> IronResult<Response> {
let contest_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0);
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
let csrf_token = {
let formdata = itry!(req.get_ref::<UrlEncodedBody>());
iexpect!(formdata.get("csrftoken"))[0].to_owned()
};
let startcontestresult = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
// Antwort erstellen und zurücksenden
functions::start_contest(&*conn, contest_id, session_token.token, csrf_token)
};
match startcontestresult {
// Start successful
Ok(()) => {
Ok(Response::with((status::Found, Redirect(url_for!(req, "contest", "contestid" => format!("{}",contest_id))))))
},
// Start failed
Err((template, data)) => {
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Forbidden);
Ok(resp)
}
}
}
fn login(_: &mut Request) -> IronResult<Response> {
let mut resp = Response::new();
resp.set_mut(Template::new("login", "".to_owned())).set_mut(status::Ok);
Ok(resp)
......@@ -192,21 +224,226 @@ fn logout(req: &mut Request) -> IronResult<Response> {
}
fn submission(req: &mut Request) -> IronResult<Response> {
let task_id = req.extensions.get::<Router>().unwrap().find("taskid").unwrap_or("").parse::<u32>().unwrap_or(0);
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
println!("{}",task_id);
let result = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
functions::load_submission(&*conn, task_id, session_token.token)
};
match result {
Ok(data) => Ok(Response::with((
status::Ok,
mime!(Application/Json),
format!("{}", data)))),
Err(_) => Ok(Response::with((
status::BadRequest,
mime!(Application/Json),
format!("{{}}"))))
}
}
fn submission_post(req: &mut Request) -> IronResult<Response> {
let task_id = req.extensions.get::<Router>().unwrap().find("taskid").unwrap_or("").parse::<u32>().unwrap_or(0);
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
let (csrf_token, data) = {
let formdata = iexpect!(req.get_ref::<UrlEncodedBody>().ok());
(iexpect!(formdata.get("csrf"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned(),
iexpect!(formdata.get("data"),(status::BadRequest, mime!(Text/Html), format!("400 Bad Request")))[0].to_owned())
};
println!("{}",data);
println!("{}",task_id);
let result = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
functions::save_submission(&*conn, task_id, session_token.token, csrf_token, data)
};
match result {
Ok(_) => Ok(Response::with((
status::Ok,
mime!(Application/Json),
format!("{{}}")))),
Err(_) => Ok(Response::with((
status::BadRequest,
mime!(Application/Json),
format!("{{}}"))))
}
}
fn task(req: &mut Request) -> IronResult<Response> {
let task_id = req.extensions.get::<Router>().unwrap().find("contestid").unwrap_or("").parse::<u32>().unwrap_or(0);
// TODO: Make work without session
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
println!("{}",task_id);
let (template, data) = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
functions::show_task(&*conn, task_id, session_token.token)
};
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
fn groups(req: &mut Request) -> IronResult<Response> {
let (template, data) = {
// hier ggf. Daten aus dem Request holen
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
// Antwort erstellen und zurücksenden
//functions::show_contests(&*conn)
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
("groups", data)
};
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
fn group(req: &mut Request) -> IronResult<Response> {
let (template, data) = {
// hier ggf. Daten aus dem Request holen
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap();
// Antwort erstellen und zurücksenden
//functions::show_contests(&*conn)
let mut data = json_val::Map::new();
data.insert("reason".to_string(), to_json(&"Not implemented".to_string()));
("group", data)
};
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
fn group_post(req: &mut Request) -> IronResult<Response> {
Ok(Response::with((status::Found, Redirect(url_for!(req, "group")))))
}
fn new_group(req: &mut Request) -> IronResult<Response> {
let session_token = SessionRequestExt::session(req).get::<SessionToken>().unwrap().unwrap();
let (csrf, name, tag) = {
let formdata = iexpect!(req