core.rs 41.5 KB
Newer Older
1
2
use time;

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
use db_objects::OptionSession;
5
use db_objects::SessionUser;
6
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
7
8
9
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
10

11
12
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
13
    pub id: i32,
14
    pub linktext: String,
15
16
    pub active: bool,
    pub greyout: bool,
17
18
19
20
21
22
23
24
}

#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
    pub name: String,
    pub subtasks: Vec<SubTaskInfo>,
}

Robert Czechowski's avatar
Robert Czechowski committed
25
26
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
27
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
28
    pub name: String,
29
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
30
31
32
    pub public: bool,
}

33
34
#[derive(Clone)]
pub enum MedalError {
35
36
    NotLoggedIn,
    AccessDenied,
37
38
39
    CsrfCheckFailed,
    SessionTimeout,
    DatabaseError,
40
    PasswordHashingError,
41
    UnmatchedPasswords,
42
}
43

44
45
46
47
type MedalValue = (String, json_val::Map<String, json_val::Value>);
type MedalResult<T> = Result<T, MedalError>;
type MedalValueResult = MedalResult<MedalValue>;

48
49
50
51
52
53
54
55
56
57
58
59
fn fill_user_data(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>) {
    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("csrf_token".to_string(), to_json(&session.csrf_token));
    }
    data.insert("parent".to_string(), to_json(&"base"));
}

60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
fn grade_to_string(grade: i32) -> String {
    match grade {
        0 => "Noch kein Schüler".to_string(),
        n @ 1..=10 => format!("{}", n),
        11 => "11 (G8)".to_string(),
        12 => "12 (G8)".to_string(),
        111 => "11 (G9)".to_string(),
        112 => "12 (G9)".to_string(),
        113 => "13 (G9)".to_string(),
        114 => "Berufsschule".to_string(),
        255 => "Kein Schüler mehr".to_string(),
        _ => "?".to_string(),
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
75
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
76
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
77
78
                                 -> (String, json_val::Map<String, json_val::Value>)
{
79
80
81
82
83
84
    let mut data = json_val::Map::new();

    //let mut contests = Vec::new();

    if let Some(token) = session_token {
        if let Some(session) = conn.get_session(&token) {
85
            fill_user_data(&session, &mut data);
86
87
88
        }
    }

89
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
90
91
92
93
94
95
96
97
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

98
    data.insert("self_url".to_string(), to_json(&self_url));
99
    data.insert("oauth_links".to_string(), to_json(&oauth_links));
100

101
    data.insert("parent".to_string(), to_json(&"base"));
102
103
104
    ("index".to_owned(), data)
}

105
106
pub fn status<T: MedalConnection>(conn: &T) -> String { conn.get_debug_information() }

107
108
109
110
111
112
113
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));
114
            data.insert("session_id".to_string(), to_json(&session.id));
115
116
117
118
119
120
121
122
123
124
125
126
127
            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));
128
129
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
130
131
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
                }
            }
        }
        data.insert("session".to_string(), to_json(&token));
    } else {
        data.insert("session".to_string(), to_json(&"No session token given"));
    }

    ("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);
    }
}

149
150
151
152
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
153
    Current,
154
    LoginRequired,
155
156
}

157
158
159
160
161
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str,
                                         (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>),
                                         visibility: ContestVisibility)
                                         -> MedalValue
{
Robert Czechowski's avatar
Robert Czechowski committed
162
163
    let mut data = json_val::Map::new();

164
    let session = conn.get_session_or_new(&session_token);
165
    fill_user_data(&session, &mut data);
166

167
168
169
170
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

171
172
173
174
175
176
177
178
179
180
181
182
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

    data.insert("self_url".to_string(), to_json(&self_url));
    data.insert("oauth_links".to_string(), to_json(&oauth_links));

183
184
185
186
187
188
189
190
191
    let now = time::get_time();
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
            .filter(|c| c.public)
            .filter(|c| c.end.map(|end| now <= end).unwrap_or(true) || visibility == ContestVisibility::All)
            .filter(|c| c.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|c| c.duration != 0 || visibility != ContestVisibility::Current)
            .filter(|c| c.requires_login.unwrap_or(false) || visibility != ContestVisibility::LoginRequired)
192
193
194
195
196
            .filter(|c| {
                !c.requires_login.unwrap_or(false)
                || visibility == ContestVisibility::LoginRequired
                || visibility == ContestVisibility::All
            })
197
198
199
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
            .collect();

Robert Czechowski's avatar
Robert Czechowski committed
200
    data.insert("contest".to_string(), to_json(&v));
201
202
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
203
                            ContestVisibility::Open => "Trainingsaufgaben",
204
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
205
                            ContestVisibility::LoginRequired => "Herausforderungen",
206
207
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
208
209
210
211

    ("contests".to_owned(), data)
}

212
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
213
214
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
215
    for st in &tg.tasks {
216
217
218
219
220
221
        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;
        }

222
223
224
225
226
227
        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 };
228
229
230
231
232
233

        subtaskinfos.push(si);
    }
    subtaskinfos
}

234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
#[derive(Serialize, Deserialize)]
pub struct ContestStartConstraints {
    pub contest_not_begun: bool,
    pub contest_over: bool,
    pub contest_running: bool,
    pub grade_too_low: bool,
    pub grade_too_high: bool,
    pub grade_matching: bool,
}

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 };

    let contest_not_begun = contest.start.map(|start| now < start).unwrap_or(false);
    let contest_over = contest.end.map(|end| now > end).unwrap_or(false);
    let grade_too_low =
        contest.min_grade.map(|min_grade| student_grade < min_grade && !session.is_teacher).unwrap_or(false);
    let grade_too_high =
        contest.max_grade.map(|max_grade| student_grade > max_grade && !session.is_teacher).unwrap_or(false);

    let contest_running = !contest_not_begun && !contest_over;
    let grade_matching = !grade_too_low && !grade_too_high;

    ContestStartConstraints { contest_not_begun,
                              contest_over,
                              contest_running,
                              grade_too_low,
                              grade_too_high,
                              grade_matching }
}

266
267
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
268
269
    let session = conn.get_session_or_new(&session_token);

270
    let contest = conn.get_contest_by_id_complete(contest_id);
271
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
272

273
    let mut opt_part = conn.get_participation(&session_token, contest_id);
274

275
276
277
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
278
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
279
280
281
282

    let mut data = json_val::Map::new();
    data.insert("contest".to_string(), to_json(&ci));

283
    let constraints = check_contest_constraints(&session, &contest);
284

285
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
286
    let has_duration = contest.duration > 0;
287

288
    data.insert("constraints".to_string(), to_json(&constraints));
289
    data.insert("has_duration".to_string(), to_json(&has_duration));
290
    data.insert("can_start".to_string(), to_json(&can_start));
291

292
293
294
295
    // Autostart if appropriate
    // TODO: Should participation start automatically for teacher? Even before the contest start?
    // Should teachers have all time access or only the same limited amount of time?
    // if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
296
297
298
299
300
301
    if opt_part.is_none()
       && contest.duration == 0
       && constraints.contest_running
       && constraints.grade_matching
       && contest.requires_login != Some(true)
    {
302
303
304
305
        conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
        opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
    }

306
    let now = time::get_time();
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
    if let Some(start) = contest.start {
        if now < start {
            let until = start - now;
            data.insert("time_until_start".to_string(),
                        to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
        }
    }

    if let Some(end) = contest.end {
        if now < end {
            let until = end - now;
            data.insert("time_until_end".to_string(),
                        to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
        }
    }

    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("csrf_token".to_string(), to_json(&session.csrf_token));
330
331
    }

332
333
334
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
335

336
        let mut tasks = Vec::new();
337
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
338
339
340
341
342
343
344
            let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
            let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
            tasks.push(ti);

            totalgrade += grade.grade.unwrap_or(0);
            max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
        }
345
        let relative_points = (totalgrade * 100) / max_totalgrade;
346
347

        data.insert("tasks".to_string(), to_json(&tasks));
Robert Czechowski's avatar
Robert Czechowski committed
348

349
350
351
352
353
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
354
355
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
356
357
358
359
        let is_time_up = !is_time_left;
        let left_min = left_secs / 60;
        let left_sec = left_secs % 60;
        let time_left = format!("{}:{:02}", left_min, left_sec);
360

361
        data.insert("is_started".to_string(), to_json(&true));
362
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
363
364
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
365
366
367
        data.insert("relative_points".to_string(), to_json(&relative_points));
        data.insert("is_time_left".to_string(), to_json(&is_time_left));
        data.insert("is_time_up".to_string(), to_json(&is_time_up));
368
369
370
371
372
373
        if is_time_left {
            data.insert("left_min".to_string(), to_json(&left_min));
            data.insert("left_sec".to_string(), to_json(&left_sec));
            data.insert("time_left".to_string(), to_json(&time_left));
            data.insert("seconds_left".to_string(), to_json(&left_secs));
        }
Robert Czechowski's avatar
Robert Czechowski committed
374
    }
375

376
377
378
379
380
381
    // This only checks if a query string is existent, so any query string will
    // lead to the assumption that a bare page is requested. This is useful to
    // disable caching (via random token) but should be changed if query string
    // can obtain more than only this meaning in the future
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
382
383
    }

384
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
385
386
}

387
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
388
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
389
390
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
391

392
393
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

394
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
395
396

    for (group, groupdata) in resultdata {
397
        let mut groupresults: Vec<(String, String, i32, String, Vec<String>)> = Vec::new();
398

Daniel Brüning's avatar
Daniel Brüning committed
399
        //TODO: use user
400
        for (user, userdata) in groupdata {
401
402
            let mut userresults: Vec<String> = Vec::new();

403
404
405
            userresults.push(String::new());
            let mut summe = 0;

406
            for grade in userdata {
407
408
409
410
411
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
412
                }
413
414
            }

415
416
            userresults[0] = format!("{}", summe);

417
418
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
419
                               user.id,
420
                               grade_to_string(user.grade),
421
                               userresults))
422
        }
423

424
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
425
426
427
428
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
429
430

    let c = conn.get_contest_by_id(contest_id);
431
432
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };

433
    data.insert("contest".to_string(), to_json(&ci));
434
    data.insert("contestname".to_string(), to_json(&c.name));
435

436
437
438
    Ok(("contestresults".to_owned(), data))
}

439
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
Robert Czechowski's avatar
Robert Czechowski committed
440
                                         -> MedalResult<()> {
441
    // TODO: Is _or_new the right semantic? We need a CSRF token anyway …
442
    let session = conn.get_session_or_new(&session_token);
443
    let contest = conn.get_contest_by_id(contest_id);
444

445
    // Check logged in or open contest
446
    if contest.duration != 0 && !session.is_logged_in() {
447
        return Err(MedalError::AccessDenied);
448
    }
449

450
    // Check CSRF token
451
452
453
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
454

455
456
457
458
459
460
461
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

    if !(constraints.contest_running && constraints.grade_matching) {
        return Err(MedalError::AccessDenied);
    }

462
    // Start contest
463
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
464
        Ok(_) => Ok(()),
465
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
466
    }
Robert Czechowski's avatar
Robert Czechowski committed
467
468
}

469
470
471
472
pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String),
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
                                 -> Result<String, MedalValue>
{
Robert Czechowski's avatar
Robert Czechowski committed
473
474
    let (username, password) = login_data;

475
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
476
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
477
478
        Err(()) => {
            let mut data = json_val::Map::new();
479
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
480
            data.insert("username".to_string(), to_json(&username));
481
482
483
484
485
486
487
488
489
490
491
492
493
494
            data.insert("parent".to_string(), to_json(&"base"));

            let mut oauth_links: Vec<(String, String, String)> = Vec::new();
            if let Some(oauth_providers) = oauth_providers {
                for oauth_provider in oauth_providers {
                    oauth_links.push((oauth_provider.provider_id.to_owned(),
                                      oauth_provider.login_link_text.to_owned(),
                                      oauth_provider.url.to_owned()));
                }
            }

            data.insert("self_url".to_string(), to_json(&self_url));
            data.insert("oauth_links".to_string(), to_json(&oauth_links));

495
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
496
497
498
499
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
500
pub fn login_with_code<T: MedalConnection>(
501
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
502
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
503
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
504
505
506
507
508
509
510
        Ok(session_token) => Ok(Ok(session_token)),
        Err(()) => match conn.create_user_with_groupcode(None, &code) {
            Ok(session_token) => Ok(Err(session_token)),
            Err(()) => {
                let mut data = json_val::Map::new();
                data.insert("reason".to_string(), to_json(&"Kein gültiger Code. Bitte erneut versuchen.".to_string()));
                data.insert("code".to_string(), to_json(&code));
511
512
513
514
515
516
517
518
519
520
521
522
523
524
                data.insert("parent".to_string(), to_json(&"base"));

                let mut oauth_links: Vec<(String, String, String)> = Vec::new();
                if let Some(oauth_providers) = oauth_providers {
                    for oauth_provider in oauth_providers {
                        oauth_links.push((oauth_provider.provider_id.to_owned(),
                                          oauth_provider.login_link_text.to_owned(),
                                          oauth_provider.url.to_owned()));
                    }
                }

                data.insert("self_url".to_string(), to_json(&self_url));
                data.insert("oauth_links".to_string(), to_json(&oauth_links));

Robert Czechowski's avatar
Robert Czechowski committed
525
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
526
            }
Robert Czechowski's avatar
Robert Czechowski committed
527
        },
Robert Czechowski's avatar
Robert Czechowski committed
528
529
530
    }
}

531
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
532
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
533
534
}

535
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, subtask: Option<String>)
Robert Czechowski's avatar
Robert Czechowski committed
536
                                           -> MedalResult<String> {
537
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
538

539
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
540
541
542
              Some(s) => conn.load_submission(&session, task_id, Some(&s)),
              None => conn.load_submission(&session, task_id, None),
          } {
Robert Czechowski's avatar
Robert Czechowski committed
543
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
544
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
545
546
547
    }
}

548
549
pub fn save_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, csrf_token: &str,
                                           data: String, grade: i32, subtask: Option<String>)
Robert Czechowski's avatar
Robert Czechowski committed
550
551
                                           -> MedalResult<String>
{
552
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
553
554

    if session.csrf_token != csrf_token {
555
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
556
557
    }

Robert Czechowski's avatar
Robert Czechowski committed
558
559
560
561
562
563
564
565
566
567
    let submission = Submission { id: None,
                                  session_user: session.id,
                                  task: task_id,
                                  grade: grade,
                                  validated: false,
                                  nonvalidated_grade: grade,
                                  needs_validation: true,
                                  subtask_identifier: subtask,
                                  value: data,
                                  date: time::get_time() };
568

Robert Czechowski's avatar
Robert Czechowski committed
569
570
571
572
573
    conn.submit_submission(submission);

    Ok("{}".to_string())
}

574
pub fn show_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str) -> MedalValueResult {
575
    let session = conn.get_session_or_new(&session_token);
Robert Czechowski's avatar
Robert Czechowski committed
576

577
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
578
579
    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?
580

581
582
583
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
584

585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
    let mut subtaskstars = Vec::new();

    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);
        }
    }
600

601
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
602
        None => Err(MedalError::AccessDenied),
603
604
605
606
607
608
        Some(participation) => {
            let now = time::get_time();
            let passed_secs = now.sec - participation.start.sec;
            if passed_secs < 0 {
                // behandle inkonsistente Serverzeit
            }
Robert Czechowski's avatar
Robert Czechowski committed
609

610
            let mut data = json_val::Map::new();
611
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
612
613
614
            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
Robert Czechowski's avatar
Robert Czechowski committed
615

616
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
617
            if c.duration > 0 && left_secs < 0 {
618
                Err(MedalError::AccessDenied)
619
620
621
622
623
            // Contest over
            // TODO: Nicer message!
            } else {
                let (hour, min, sec) = (left_secs / 3600, left_secs / 60 % 60, left_secs % 60);

Robert Czechowski's avatar
Robert Czechowski committed
624
625
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
626
627

                let taskpath = format!("{}{}", c.location, t.location);
Robert Czechowski's avatar
Robert Czechowski committed
628

629
                data.insert("contestname".to_string(), to_json(&c.name));
630
631
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
632
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
633
634
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
635
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
636

Robert Czechowski's avatar
Robert Czechowski committed
637
638
639
640
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

641
642
643
644
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
645
}
Robert Czechowski's avatar
Robert Czechowski committed
646

Robert Czechowski's avatar
Robert Czechowski committed
647
648
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
649
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
650
651
652
653
654
    pub name: String,
    pub tag: String,
    pub code: String,
}

Daniel Brüning's avatar
Daniel Brüning committed
655
pub fn show_groups<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
656
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
657

Robert Czechowski's avatar
Robert Czechowski committed
658
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
659
660

    let mut data = json_val::Map::new();
661
    fill_user_data(&session, &mut data);
Robert Czechowski's avatar
Robert Czechowski committed
662

Robert Czechowski's avatar
Robert Czechowski committed
663
664
665
666
667
668
669
670
    let v: Vec<GroupInfo> =
        conn.get_groups(session.id)
            .iter()
            .map(|g| GroupInfo { id: g.id.unwrap(),
                                 name: g.name.clone(),
                                 tag: g.tag.clone(),
                                 code: g.groupcode.clone() })
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
671
    data.insert("group".to_string(), to_json(&v));
672
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
673

674
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
675
676
}

Robert Czechowski's avatar
Robert Czechowski committed
677
678
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
679
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
680
681
    pub firstname: String,
    pub lastname: String,
682
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
683
684
685
    pub logincode: String,
}

686
pub fn show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
687
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
688
    let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error
689

Robert Czechowski's avatar
Robert Czechowski committed
690
    let mut data = json_val::Map::new();
691
    fill_user_data(&session, &mut data);
692

Robert Czechowski's avatar
Robert Czechowski committed
693
    if group.admin != session.id {
694
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
695
696
    }

Robert Czechowski's avatar
Robert Czechowski committed
697
698
699
700
701
702
703
704
705
706
707
    let gi = GroupInfo { id: group.id.unwrap(),
                         name: group.name.clone(),
                         tag: group.tag.clone(),
                         code: group.groupcode.clone() };

    let v: Vec<MemberInfo> =
        group.members
             .iter()
             .map(|m| MemberInfo { id: m.id,
                                   firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
                                   lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
708
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
709
710
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
711
712
713

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
714
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
715
716
717

    Ok(("group".to_string(), data))
}
Robert Czechowski's avatar
Robert Czechowski committed
718

719
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
720
721
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
722

Daniel Brüning's avatar
Daniel Brüning committed
723
pub fn add_group<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
724
                                     -> MedalResult<i32> {
725
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::AccessDenied)?;
Robert Czechowski's avatar
Robert Czechowski committed
726
727

    if session.csrf_token != csrf_token {
728
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
729
730
    }

731
    let group_code = helpers::make_group_code();
732
    // TODO: check for collisions
733

Robert Czechowski's avatar
Robert Czechowski committed
734
735
    let mut group =
        Group { id: None, name: name, groupcode: group_code, tag: tag, admin: session.id, members: Vec::new() };
Robert Czechowski's avatar
Robert Czechowski committed
736

Robert Czechowski's avatar
Robert Czechowski committed
737
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
738
739
740

    Ok(group.id.unwrap())
}
741

742
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
743
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
744
745
746
747
748
749
750

    let mut data = json_val::Map::new();
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));

    Ok(("groupcsv".to_string(), data))
}

751
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
752
753
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
754
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
755
756
757
758
759

    if session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
760
761
    println!("{}", group_data);

762
763
764
765
766
    let mut v: Vec<Vec<String>> = serde_json::from_str(group_data).or(Err(MedalError::AccessDenied))?; // TODO: Change error type
    v.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());

    let mut group_code = "".to_string();
    let mut name = "".to_string();
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
767
768
769
770
771
772
773
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

774
775
    for line in v {
        if name != line[0] {
776
            if name != "" {
777
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
778
            }
779
780
781
782
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
783
784
785
786
787
788
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
789
790
791
792
793
794
795
796
797
798
799
        }

        let mut user = SessionUser::group_user_stub();
        user.grade = line[1].parse::<i32>().unwrap_or(0);
        user.firstname = Some(line[2].clone());
        user.lastname = Some(line[3].clone());

        group.members.push(user);
    }
    conn.create_group_with_users(group);

800
801
802
    Ok(())
}

Daniel Brüning's avatar
Daniel Brüning committed
803
#[allow(dead_code)]
804
pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
805
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Daniel Brüning's avatar
Daniel Brüning committed
806
807
    //TODO: use g
    let _g = conn.get_contest_groups_grades(session.id, contest_id);
808

809
    let data = json_val::Map::new();
810
811
812

    Ok(("groupresults".into(), data))
}
Robert Czechowski's avatar
Robert Czechowski committed
813

814
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
815
816
817
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
818
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
819
820

    let mut data = json_val::Map::new();
821
    fill_user_data(&session, &mut data);
Robert Czechowski's avatar
Robert Czechowski committed
822

823
824
    match user_id {
        None => {
825
826
827
828
829
            data.insert("profile_firstname".to_string(), to_json(&session.firstname));
            data.insert("profile_lastname".to_string(), to_json(&session.lastname));
            data.insert("profile_street".to_string(), to_json(&session.street));
            data.insert("profile_zip".to_string(), to_json(&session.zip));
            data.insert("profile_city".to_string(), to_json(&session.city));
830
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
831
832
833
834
835
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
836

837
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
838
            if session.password.is_some() {
839
                data.insert("profile_username".to_string(), to_json(&session.username));
840
841
            }
            if session.managed_by.is_none() {
842
                data.insert("profile_not_in_group".into(), to_json(&true));
843
            }
844
845
846
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
847
848
            data.insert("ownprofile".into(), to_json(&true));

849
            if let Some(query) = query_string {
850
                if query.starts_with("status=") {
851
                    let status: &str = &query[7..];
852
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
853
854
                        data.insert((status).to_string(), to_json(&true));
                    }
855
856
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
857
        }
858
859
860
861
862
863
864
865
        Some(user_id) => {
            // TODO: Add test to check if this access restriction works
            let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
            let group = opt_group.ok_or(MedalError::AccessDenied)?;
            if group.admin != session.id {
                return Err(MedalError::AccessDenied);
            }

866
867
868
869
870
            data.insert("profile_firstname".to_string(), to_json(&user.firstname));
            data.insert("profile_lastname".to_string(), to_json(&user.lastname));
            data.insert("profile_street".to_string(), to_json(&session.street));
            data.insert("profile_zip".to_string(), to_json(&session.zip));
            data.insert("profile_city".to_string(), to_json(&session.city));
871
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
872
873
874
875
876
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
877

878
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
879
            if user.username.is_some() {
880
                data.insert("profile_username".to_string(), to_json(&user.username));
881
882
            }
            if user.managed_by.is_none() {
883
                data.insert("profile_not_in_group".into(), to_json(&true));
884
            }
885
886
887
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
888
            data.insert("ownprofile".into(), to_json(&false));
889

890
891
892
893
894
895
896
897
            if let Some(query) = query_string {
                if query.starts_with("status=") {
                    let status: &str = &query[7..];
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
                        data.insert((status).to_string(), to_json(&true));
                    }
                }
            }
898
899
        }
    }
900

901
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
902
903
}

904
#[derive(Debug, PartialEq)]
905
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
906
907
908
909
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
910
911
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
912
    fn into(self) -> String { format!("{:?}", self) }
913
914
}

915
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
                                        (firstname,
                                         lastname,
                                         street,
                                         zip,
                                         city,
                                         password,
                                         password_repeat,
                                         grade,
                                         sex): (String,
                                         String,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         i32,
                                         Option<i32>))
Robert Czechowski's avatar
Robert Czechowski committed
933
934
                                        -> MedalResult<ProfileStatus>
{
935
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
936
937

    if session.csrf_token != csrf_token {
938
        return Err(MedalError::AccessDenied); // CsrfError
Robert Czechowski's avatar
Robert Czechowski committed
939
940
    }

941
    let mut result = ProfileStatus::NothingChanged;
942

943
    let mut password_and_salt = None;
944

945
946
947
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
948
949
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
950

951
952
953
954
955
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
956
957
958
959
960
961
962
963
964
965
966
967
        }
    }

    if result == ProfileStatus::NothingChanged {
        if session.firstname.as_ref() == Some(&firstname)
           && session.lastname.as_ref() == Some(&lastname)
           && session.street == street
           && session.zip == zip
           && session.city == city
           && session.grade == grade
           && session.sex == sex
        {
968
            return Ok(ProfileStatus::NothingChanged);
969
970
        } else {
            result = ProfileStatus::DataChanged;
971
        }
972
973
    }

974
975
976
977
978
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
979