Commit 1f692b9b authored by Robert Czechowski's avatar Robert Czechowski
Browse files

Merge branch 'task-display'

parents 2dffbc23 f6c6ef0e
Pipeline #123 passed with stages
in 18 minutes and 50 seconds
......@@ -10,7 +10,7 @@ pub trait MedalConnection {
fn apply_migration(&mut self, name: &str, contents: &str);
fn get_session(&self, key: &str) -> Option<SessionUser>;
fn new_session(&self) -> SessionUser;
fn new_session(&self, key: &str) -> SessionUser;
fn save_session(&self, session: SessionUser);
fn get_session_or_new(&self, key: &str) -> SessionUser;
......@@ -32,10 +32,12 @@ pub trait MedalConnection {
fn get_grade_by_submission(&self, submission_id: u32) -> Grade;
fn get_contest_groups_grades(&self, session_id: u32, contest_id: u32)
-> (Vec<String>, Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)>);
fn get_contest_user_grades(&self, session: String, contest_id: u32) -> Vec<Grade>;
fn get_taskgroup_user_grade(&self, session: &str, taskgroup_id: u32) -> Grade;
fn get_contest_user_grades(&self, session: &str, contest_id: u32) -> Vec<Grade>;
fn get_contest_list(&self) -> Vec<Contest>;
fn get_contest_by_id(&self, contest_id: u32) -> Contest;
fn get_contest_by_id_partial(&self, contest_id: u32) -> Contest;
fn get_contest_by_id_complete(&self, contest_id: u32) -> Contest;
fn get_participation(&self, session: &str, contest_id: u32) -> Option<Participation>;
fn new_participation(&self, session: &str, contest_id: u32) -> Result<Participation, ()>;
......
......@@ -89,13 +89,19 @@ impl MedalConnection for Connection {
Ok(session) => {
let duration = if session.permanent_login { Duration::days(90) } else { Duration::minutes(90) };
let now = time::get_time();
if now - session.last_activity? < duration {
self.execute("UPDATE session_user SET last_activity = ?1 WHERE id = ?2", &[&now, &session.id])
.unwrap();
Some(session)
if let Some(last_activity) = session.last_activity {
if now - last_activity < duration {
self.execute("UPDATE session_user SET last_activity = ?1 WHERE id = ?2", &[&now, &session.id])
.unwrap();
Some(session)
} else {
// Session timed out
// Should remove session token from session_user
None
}
} else {
// Session timed out
// Should remove session token from session_user
// last_activity undefined
// TODO: What should happen here?
None
}
}
......@@ -121,20 +127,20 @@ impl MedalConnection for Connection {
&session.id])
.unwrap();
}
fn new_session(&self) -> SessionUser {
let session_token = "123".to_string();
let csrf_token = "123".to_string();
fn new_session(&self, session_token: &str) -> SessionUser {
let csrf_token: String = thread_rng().sample_iter(&Alphanumeric).take(10).collect();
self.execute("INSERT INTO session_user (session_token, csrf_token, permanent_login, is_teacher)
VALUES (?1, ?2, 0, 0)",
&[&session_token, &csrf_token])
let now = time::get_time();
self.execute("INSERT INTO session_user (session_token, csrf_token, last_activity, permanent_login, grade, is_teacher)
VALUES (?1, ?2, ?3, 0, 0, 0)",
&[&session_token, &csrf_token, &now])
.unwrap();
let id = self.query_row("SELECT last_insert_rowid()", &[], |row| row.get(0)).unwrap();
SessionUser::minimal(id, session_token, csrf_token)
SessionUser::minimal(id, session_token.to_owned(), csrf_token)
}
fn get_session_or_new(&self, key: &str) -> SessionUser {
self.get_session(&key).unwrap_or_else(|| self.new_session())
self.get_session(&key).unwrap_or_else(|| self.new_session(&key))
}
fn get_user_by_id(&self, user_id: u32) -> Option<SessionUser> {
......@@ -446,7 +452,7 @@ impl MedalConnection for Connection {
(Vec::new(), Vec::new()) // should those be default filled?
}
}
fn get_contest_user_grades(&self, session_token: String, contest_id: u32) -> Vec<Grade> {
fn get_contest_user_grades(&self, session_token: &str, contest_id: u32) -> Vec<Grade> {
let mut stmt = self.prepare("SELECT id, name FROM taskgroup WHERE contest = ?1 ORDER BY id ASC").unwrap();
let tasknames_iter = stmt.query_map(&[&contest_id], |row| {
let x: (u32, String) = (row.get(0), row.get(1));
......@@ -486,6 +492,22 @@ impl MedalConnection for Connection {
grades
}
fn get_taskgroup_user_grade(&self, session_token: &str, taskgroup_id: u32) -> Grade {
let mut stmt = self.prepare("SELECT grade.taskgroup, grade.user, grade.grade, grade.validated
FROM grade
JOIN session_user ON session_user.id = grade.user
WHERE session_user.session_token = ?1 AND grade.taskgroup = ?2")
.unwrap();
let gradeinfo_iter = stmt.query_map(&[&session_token, &taskgroup_id], |row| Grade { taskgroup: row.get(0),
user: row.get(1),
grade: row.get(2),
validated: row.get(3) })
.unwrap();
let grade = gradeinfo_iter.map(|t| t.unwrap_or_default()).next().unwrap_or_default();
grade
}
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")
......@@ -554,6 +576,35 @@ impl MedalConnection for Connection {
contest.taskgroups.push(taskgroup);
contest
}
fn get_contest_by_id_partial(&self, contest_id: u32) -> Contest {
let mut stmt = self.prepare("SELECT contest.location, contest.filename, contest.name, contest.duration, contest.public, contest.start_date, contest.end_date, taskgroup.id, taskgroup.name FROM contest JOIN taskgroup ON contest.id = taskgroup.contest WHERE contest.id = ?1").unwrap();
let mut taskgroupcontest_iter =
stmt.query_map(&[&contest_id], |row| {
(Contest { id: Some(contest_id),
location: row.get(0),
filename: row.get(1),
name: row.get(2),
duration: row.get(3),
public: row.get(4),
start: row.get(5),
end: row.get(6),
taskgroups: Vec::new() },
Taskgroup { id: Some(row.get(7)), contest: contest_id, name: row.get(8), tasks: Vec::new() })
})
.unwrap();
let (mut contest, taskgroup) = taskgroupcontest_iter.next().unwrap().unwrap();
contest.taskgroups.push(taskgroup);
for tgc in taskgroupcontest_iter {
if let Ok((_, tg)) = tgc {
contest.taskgroups.push(tg);
}
}
contest
}
fn get_participation(&self, session: &str, 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 {
......
......@@ -191,10 +191,22 @@ impl SessionUser {
}
}
pub fn ensure_alive(self) -> Option<Self> {
pub fn is_alive(&self) -> bool {
let duration = if self.permanent_login { Duration::days(90) } else { Duration::minutes(90) };
let now = time::get_time();
if now - self.last_activity? < duration {
if let Some(last_activity) = self.last_activity {
now - last_activity < duration
} else {
false
}
}
pub fn is_logged_in(&self) -> bool {
(self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some()) && self.is_alive()
}
pub fn ensure_alive(self) -> Option<Self> {
if self.is_alive() {
Some(self)
} else {
None
......@@ -202,8 +214,8 @@ impl SessionUser {
}
pub fn ensure_logged_in(self) -> Option<Self> {
if self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some() {
self.ensure_alive()
if self.is_logged_in() {
Some(self)
} else {
None
}
......
......@@ -8,7 +8,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
use db_conn::MedalConnection;
use db_objects::{Group, SessionUser, Submission};
use db_objects::{Grade, Group, SessionUser, Submission, Taskgroup};
use self::bcrypt::hash;
......@@ -16,6 +16,8 @@ use self::bcrypt::hash;
pub struct SubTaskInfo {
pub id: u32,
pub linktext: String,
pub active: bool,
pub greyout: bool,
}
#[derive(Serialize, Deserialize)]
......@@ -63,11 +65,13 @@ pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
if let Some(token) = session_token {
if let Some(session) = conn.get_session(&token) {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
if session.is_logged_in() {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
}
}
}
......@@ -90,6 +94,45 @@ pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
("index".to_owned(), data)
}
pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
-> (String, json_val::Map<String, json_val::Value>) {
let mut data = json_val::Map::new();
if let Some(token) = session_token {
if let Some(session) = conn.get_session(&token) {
data.insert("known_session".to_string(), to_json(&true));
data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
if let Some(last_activity) = session.last_activity {
data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
}
if session.is_alive() {
data.insert("alive_session".to_string(), to_json(&true));
if session.is_logged_in() {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
}
}
}
data.insert("session".to_string(), to_json(&token));
println!("etwas session?!");
} else {
data.insert("session".to_string(), to_json(&"No session token given"));
println!("warum nix session?!");
}
("debug".to_owned(), data)
}
pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
if let Some(token) = session_token {
conn.get_session_or_new(&token);
}
}
pub fn show_contests<T: MedalConnection>(conn: &T) -> MedalValue {
let mut data = json_val::Map::new();
......@@ -108,29 +151,38 @@ pub fn show_contests<T: MedalConnection>(conn: &T) -> MedalValue {
("contests".to_owned(), data)
}
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<u32>) -> Vec<SubTaskInfo> {
let mut subtaskinfos = Vec::new();
let mut not_print_yet = true;
for st in &tg.tasks {
let mut blackstars: usize = 0;
if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
blackstars = grade.grade.unwrap_or(0) as usize;
not_print_yet = false;
}
let greyout = not_print_yet && st.stars < grade.grade.unwrap_or(0);
let active = ast.is_some() && st.id == ast;
let linktext = format!("{}{}",
str::repeat("★", blackstars as usize),
str::repeat("☆", st.stars as usize - blackstars as usize));
let si = SubTaskInfo { id: st.id.unwrap(), linktext: linktext, active, greyout };
subtaskinfos.push(si);
}
subtaskinfos
}
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token: String) -> MedalValueResult {
let c = conn.get_contest_by_id_complete(contest_id);
let grades = conn.get_contest_user_grades(session_token.clone(), contest_id);
let grades = conn.get_contest_user_grades(&session_token, contest_id);
// TODO: Clean up star generation
let mut tasks = Vec::new();
for (task, grade) in c.taskgroups.into_iter().zip(grades) {
let mut not_print_yet = true;
let mut stasks = Vec::new();
for st in task.tasks {
let mut blackstars: usize = 0;
if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
blackstars = grade.grade.unwrap_or(0) as usize;
not_print_yet = false;
}
stasks.push(SubTaskInfo { id: st.id.unwrap(),
linktext: format!("{}{}",
str::repeat("★", blackstars as usize),
str::repeat("☆", st.stars as usize - blackstars as usize)) })
}
let mut ti = TaskInfo { name: task.name, subtasks: stasks };
let mut subtaskstars = generate_subtaskstars(&task, &grade, None);
let ti = TaskInfo { name: task.name, subtasks: subtaskstars };
tasks.push(ti);
}
......@@ -146,13 +198,18 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token
data.insert("contest".to_string(), to_json(&ci));
data.insert("logged_in".to_string(), to_json(&false));
data.insert("can_start".to_string(), to_json(&false));
if let Some(session) = conn.get_session(&session_token) {
data.insert("logged_in".to_string(), to_json(&true));
data.insert("can_start".to_string(), to_json(&true));
data.insert("username".to_string(), to_json(&session.username));
data.insert("firstname".to_string(), to_json(&session.firstname));
data.insert("lastname".to_string(), to_json(&session.lastname));
data.insert("teacher".to_string(), to_json(&session.is_teacher));
}
if c.duration == 0 {
data.insert("can_start".to_string(), to_json(&true));
}
match conn.get_participation(&session_token, contest_id) {
None => Ok(("contest".to_owned(), data)),
......@@ -333,21 +390,69 @@ pub fn show_task<T: MedalConnection>(conn: &T, task_id: u32, session_token: Stri
let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
let (t, tg, c) = conn.get_task_by_id_complete(task_id);
let grade = conn.get_taskgroup_user_grade(&session_token, tg.id.unwrap()); // TODO: Unwrap?
let tasklist = conn.get_contest_by_id_complete(c.id.unwrap()); // TODO: Unwrap?
let taskpath = format!("{}{}", c.location, t.location);
let mut prevtaskgroup: Option<Taskgroup> = None;
let mut nexttaskgroup: Option<Taskgroup> = None;
let mut current_found = false;
let mut data = json_val::Map::new();
let mut subtaskstars = Vec::new();
data.insert("name".to_string(), to_json(&tg.name));
data.insert("taskid".to_string(), to_json(&task_id));
data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
data.insert("taskpath".to_string(), to_json(&taskpath));
for taskgroup in tasklist.taskgroups {
if current_found {
nexttaskgroup = Some(taskgroup);
break;
}
if taskgroup.id == tg.id {
current_found = true;
subtaskstars = generate_subtaskstars(&taskgroup, &grade, Some(task_id));
} else {
prevtaskgroup = Some(taskgroup);
}
}
Ok(("task".to_owned(), data))
match conn.get_participation(&session_token, c.id.expect("Value from database")) {
None => Err(MedalError::AccessDenied),
Some(participation) => {
let now = time::get_time();
let passed_secs = now.sec - participation.start.sec;
if passed_secs < 0 {
// behandle inkonsistente Serverzeit
}
let mut data = json_val::Map::new();
data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
data.insert("subtasks".to_string(), to_json(&subtaskstars));
data.insert("prevtask".to_string(), to_json(&prevtaskgroup.map(|tg| tg.tasks[0].id)));
data.insert("nexttask".to_string(), to_json(&nexttaskgroup.map(|tg| tg.tasks[0].id))); // TODO: fail better
let left_secs = i64::from(c.duration) * 60 - passed_secs;
if c.duration > 0 && left_secs < 0 {
Err(MedalError::AccessDenied)
// Contest over
// TODO: Nicer message!
} else {
let (hour, min, sec) = (left_secs / 3600, left_secs / 60 % 60, left_secs % 60);
data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
let taskpath = format!("{}{}", c.location, t.location);
data.insert("name".to_string(), to_json(&tg.name));
data.insert("taskid".to_string(), to_json(&task_id));
data.insert("csrftoken".to_string(), to_json(&session.csrf_token));
data.insert("taskpath".to_string(), to_json(&taskpath));
data.insert("contestid".to_string(), to_json(&c.id));
data.insert("seconds_left".to_string(), to_json(&left_secs));
Ok(("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}
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
......
......@@ -166,7 +166,7 @@ fn add_admin_user(conn: &mut Connection, resetpw: bool) {
let mut admin = match conn.get_user_by_id(1) {
None => {
print!("New Database. Creating new admin user with credentials 'admin':");
conn.new_session()
conn.new_session("")
}
Some(user) => {
if !resetpw {
......@@ -253,7 +253,7 @@ mod tests {
db_apply_migrations::test(&mut conn);
if let Some(user) = set_user {
let mut test_user = conn.new_session();
let mut test_user = conn.new_session("");
test_user.username = Some(user.0);
match test_user.set_password(&user.1) {
None => panic!("Set Password did not work correctly.)"),
......
......@@ -263,6 +263,49 @@ fn greet_personal(req: &mut Request) -> IronResult<Response> {
Ok(resp)
}
fn debug(req: &mut Request) -> IronResult<Response> {
let session_token = req.get_session_token();
let (template, data) = {
let mutex = req.get::<Write<SharedDatabaseConnection>>().unwrap();
let conn = mutex.lock().unwrap_or_else(|e| e.into_inner());
functions::debug(&*conn, session_token)
};
let mut resp = Response::new();
resp.set_mut(Template::new(&template, data)).set_mut(status::Ok);
Ok(resp)
}
fn debug_new_token(req: &mut Request) -> IronResult<Response> {
let session_token = req.get_session_token();
println!("Loggin out session {:?}", session_token);
with_conn![functions::logout, req, session_token];
Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}
fn debug_logout(req: &mut Request) -> IronResult<Response> {
let session_token = req.get_session_token();
println!("Loggin out session {:?}", session_token);
with_conn![functions::logout, req, session_token];
Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}
fn debug_create_session(req: &mut Request) -> IronResult<Response> {
let session_token = req.get_session_token();
with_conn![functions::debug_create_session, req, session_token];
Ok(Response::with((status::Found, Redirect(url_for!(req, "debug")))))
}
fn contests(req: &mut Request) -> IronResult<Response> {
let (template, data) = with_conn![functions::show_contests, req,];
......@@ -794,6 +837,10 @@ pub fn start_server(conn: Connection, config: ::Config) -> iron::error::HttpResu
task: get "/task/:taskid" => task,
oauth: get "/oauth/:oauthid" => oauth,
check_cookie: get "/cookie" => cookie_warning,
debug: get "/debug" => debug,
debug_reset: get "/debug/reset" => debug_new_token,
debug_logout: get "/debug/logout" => debug_logout,
debug_create: get "/debug/create" => debug_create_session,
);
// TODO: how important is this? Should this be in the config?
......
......@@ -31,7 +31,7 @@
{{else}}
{{#if logged_in}}
{{#if can_start}}
<p>
<form action="" method="post">
<input type="hidden" name="csrftoken" value="{{csrftoken}}">
......@@ -39,7 +39,7 @@
</form>
</p>
<p>Der Wettbewerb kann nach dem starten nicht mehr pausiert werden.</p>
<p>Der Wettbewerb kann nach dem Starten nicht mehr pausiert werden.</p>
{{/if}}
{{/if}}
......
{{#if known_session}}<p>Session in Datenbank vorhanden {{/if}}
{{#if alive_session}}<p>Session lebt {{/if}}
<p>Now: {{ now_timestamp }}
<p>Last: {{ session_timestamp }}
<p>Diff: {{ timediff }}
{{#if logged_in}}
<p> Eingeloggt als <em>{{ username }}</em>
{{#if firstname}}{{#if lastname}}
<p>({{firstname}} {{lastname}})
{{/if}}{{/if}}
{{#if teacher}}
<p>[Lehrer]
{{/if}}
{{#if admin}}
<p>[Admin]
{{/if}}
<p><a href="/debug/logout">Logout</a>
{{/if}}
<p>Session-Token: {{session}}
<p><a href="/debug/reset">Neues Token</a>
<p><a href="/debug/create">Erzeuge Session in DB</a>
......@@ -25,7 +25,7 @@ display:block;
padding:0px;
}
#bar>#nav {
#bar>.nav {
float:right;
}
#bar>#time {
......@@ -61,45 +61,85 @@ color:black;
iframe {
width: 100vw;border: 0px;height: calc(100vh - 45px);
}
a {
color: #ddd;
text-decoration: none;
}
</style>
</head><body style="">
<div id="bar">
<div id="menu">
&nbsp; JwInf 2018
<a href="/contest/{{contestid}}">{{contestname}}JwInf 2018</a>
</div>
<div>
{{name}}
</div>
<div>
★★
</div>
<div class="highlight">
☆☆☆
</div>
<div>
☆☆☆☆
</div>
{{#each subtasks}}
{{#if active}}
<div class="highlight">
{{linktext}}
</div>
{{else}}
<div>
<a href="/task/{{id}}">{{linktext}}</a>
</div>
{{/if}}
{{/each}}
{{#if duration}}
<div id="time">
<div id="timetext">
Verbleibende Zeit
</div>
<div id="timetime">
0:28<span id="ttsec">:14</span>