core.rs 41.4 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
296
297
298
299
300
    // 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) {
    if opt_part.is_none() && contest.duration == 0 && constraints.contest_running && constraints.grade_matching {
        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() });
    }

301
    let now = time::get_time();
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
    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));
325
326
    }

327
328
329
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
330

331
        let mut tasks = Vec::new();
332
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
333
334
335
336
337
338
339
            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);
        }
340
        let relative_points = (totalgrade * 100) / max_totalgrade;
341
342

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

344
345
346
347
348
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
349
350
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
351
352
353
354
        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);
355

356
        data.insert("is_started".to_string(), to_json(&true));
357
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
358
359
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
360
361
362
        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));
363
364
365
366
367
368
        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
369
    }
370

371
372
373
374
375
376
    // 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));
377
378
    }

379
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
380
381
}

382
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
383
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
384
385
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
386

387
388
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

389
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
390
391

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

Daniel Brüning's avatar
Daniel Brüning committed
394
        //TODO: use user
395
        for (user, userdata) in groupdata {
396
397
            let mut userresults: Vec<String> = Vec::new();

398
399
400
            userresults.push(String::new());
            let mut summe = 0;

401
            for grade in userdata {
402
403
404
405
406
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
407
                }
408
409
            }

410
411
            userresults[0] = format!("{}", summe);

412
413
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
414
                               user.id,
415
                               grade_to_string(user.grade),
416
                               userresults))
417
        }
418

419
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
420
421
422
423
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
424
425

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

428
    data.insert("contest".to_string(), to_json(&ci));
429
    data.insert("contestname".to_string(), to_json(&c.name));
430

431
432
433
    Ok(("contestresults".to_owned(), data))
}

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

440
    // Check logged in or open contest
441
    if contest.duration != 0 && !session.is_logged_in() {
442
        return Err(MedalError::AccessDenied);
443
    }
444

445
    // Check CSRF token
446
447
448
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
449

450
451
452
453
454
455
456
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

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

464
465
466
467
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
468
469
    let (username, password) = login_data;

470
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
471
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
472
473
        Err(()) => {
            let mut data = json_val::Map::new();
474
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
475
            data.insert("username".to_string(), to_json(&username));
476
477
478
479
480
481
482
483
484
485
486
487
488
489
            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));

490
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
491
492
493
494
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
495
pub fn login_with_code<T: MedalConnection>(
496
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
497
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
498
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
499
500
501
502
503
504
505
        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));
506
507
508
509
510
511
512
513
514
515
516
517
518
519
                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
520
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
521
            }
Robert Czechowski's avatar
Robert Czechowski committed
522
        },
Robert Czechowski's avatar
Robert Czechowski committed
523
524
525
    }
}

526
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
527
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
528
529
}

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

534
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
535
536
537
              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
538
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
539
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
540
541
542
    }
}

543
544
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
545
546
                                           -> MedalResult<String>
{
547
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
548
549

    if session.csrf_token != csrf_token {
550
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
551
552
    }

Robert Czechowski's avatar
Robert Czechowski committed
553
554
555
556
557
558
559
560
561
562
    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() };
563

Robert Czechowski's avatar
Robert Czechowski committed
564
565
566
567
568
    conn.submit_submission(submission);

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

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

572
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
573
574
    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?
575

576
577
578
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
579

580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
    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);
        }
    }
595

596
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
597
        None => Err(MedalError::AccessDenied),
598
599
600
601
602
603
        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
604

605
            let mut data = json_val::Map::new();
606
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
607
608
609
            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
610

611
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
612
            if c.duration > 0 && left_secs < 0 {
613
                Err(MedalError::AccessDenied)
614
615
616
617
618
            // 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
619
620
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
621
622

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

624
                data.insert("contestname".to_string(), to_json(&c.name));
625
626
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
627
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
628
629
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
630
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
631

Robert Czechowski's avatar
Robert Czechowski committed
632
633
634
635
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

636
637
638
639
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
640
}
Robert Czechowski's avatar
Robert Czechowski committed
641

Robert Czechowski's avatar
Robert Czechowski committed
642
643
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
644
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
645
646
647
648
649
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
653
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
654
655

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

Robert Czechowski's avatar
Robert Czechowski committed
658
659
660
661
662
663
664
665
    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
666
    data.insert("group".to_string(), to_json(&v));
667
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
668

669
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
670
671
}

Robert Czechowski's avatar
Robert Czechowski committed
672
673
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
674
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
675
676
    pub firstname: String,
    pub lastname: String,
677
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
678
679
680
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
688
    if group.admin != session.id {
689
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
690
691
    }

Robert Czechowski's avatar
Robert Czechowski committed
692
693
694
695
696
697
698
699
700
701
702
    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()),
703
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
704
705
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
706
707
708

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
709
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
710
711
712

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

714
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
715
716
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
717

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

    if session.csrf_token != csrf_token {
723
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
724
725
    }

726
    let group_code = helpers::make_group_code();
727
    // TODO: check for collisions
728

Robert Czechowski's avatar
Robert Czechowski committed
729
730
    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
731

Robert Czechowski's avatar
Robert Czechowski committed
732
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
733
734
735

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

737
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
738
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
739
740
741
742
743
744
745

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

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

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

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

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

757
758
759
760
761
    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
762
763
764
765
766
767
768
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

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

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

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

795
796
797
    Ok(())
}

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

804
    let data = json_val::Map::new();
805
806
807

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

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

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

818
819
    match user_id {
        None => {
820
821
822
823
824
            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));
825
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
826
827
828
829
830
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
831

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

844
            if let Some(query) = query_string {
845
                if query.starts_with("status=") {
846
                    let status: &str = &query[7..];
847
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
848
849
                        data.insert((status).to_string(), to_json(&true));
                    }
850
851
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
852
        }
853
854
855
856
857
858
859
860
        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);
            }

861
862
863
864
865
            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));
866
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
867
868
869
870
871
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
872

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

885
886
887
888
889
890
891
892
            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));
                    }
                }
            }
893
894
        }
    }
895

896
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
897
898
}

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

910
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
                                        (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
928
929
                                        -> MedalResult<ProfileStatus>
{
930
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
931
932

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

936
    let mut result = ProfileStatus::NothingChanged;
937

938
    let mut password_and_salt = None;
939

940
941
942
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
943
944
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
945

946
947
948
949
950
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
951
952
953
954
955
956
957
958
959
960
961
962
        }
    }

    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
        {
963
            return Ok(ProfileStatus::NothingChanged);
964
965
        } else {
            result = ProfileStatus::DataChanged;
966
        }
967
968
    }

969
970
971
972
973
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
974
            session.sex = sex;
975

976
977
978
979
980
981