core.rs 41.3 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
192
193
194
195
    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)
            .filter(|c| !c.requires_login.unwrap_or(false) || visibility == ContestVisibility::LoginRequired)
            .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
196
    data.insert("contest".to_string(), to_json(&v));
197
198
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
199
                            ContestVisibility::Open => "Trainingsaufgaben",
200
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
201
                            ContestVisibility::LoginRequired => "Herausforderungen",
202
203
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
204
205
206
207

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

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

218
219
220
221
222
223
        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 };
224
225
226
227
228
229

        subtaskinfos.push(si);
    }
    subtaskinfos
}

230
231
232
233
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
#[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 }
}

262
263
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
264
265
    let session = conn.get_session_or_new(&session_token);

266
    let contest = conn.get_contest_by_id_complete(contest_id);
267
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
268

269
    let mut opt_part = conn.get_participation(&session_token, contest_id);
270

271
272
273
    // 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?
274
    // if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
275
    // TODO: Should autostart only happen in the contest time?
276
    if opt_part.is_none() && contest.duration == 0 {
277
278
        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() });
279
    }
280

281
282
283
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
284
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
285
286
287
288

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

289
    let constraints = check_contest_constraints(&session, &contest);
290

291
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
292
    let has_duration = contest.duration > 0;
293

294
    data.insert("constraints".to_string(), to_json(&constraints));
295
    data.insert("has_duration".to_string(), to_json(&has_duration));
296
    data.insert("can_start".to_string(), to_json(&can_start));
297

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

324
325
326
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
327

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

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

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

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

368
369
370
371
372
373
    // 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));
374
375
    }

376
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
377
378
}

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

384
385
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

386
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
387
388

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

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

395
396
397
            userresults.push(String::new());
            let mut summe = 0;

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

407
408
            userresults[0] = format!("{}", summe);

409
410
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
411
                               user.id,
412
                               grade_to_string(user.grade),
413
                               userresults))
414
        }
415

416
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
417
418
419
420
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
421
422

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

425
    data.insert("contest".to_string(), to_json(&ci));
426
    data.insert("contestname".to_string(), to_json(&c.name));
427

428
429
430
    Ok(("contestresults".to_owned(), data))
}

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

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

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

447
448
449
450
451
452
453
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

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

461
462
463
464
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
465
466
    let (username, password) = login_data;

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

487
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
488
489
490
491
        }
    }
}

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

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

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

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

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

    if session.csrf_token != csrf_token {
547
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
548
549
    }

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

Robert Czechowski's avatar
Robert Czechowski committed
561
562
563
564
565
    conn.submit_submission(submission);

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

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

569
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
570
571
    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?
572

573
574
575
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
576

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

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

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

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

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

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

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

633
634
635
636
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
637
}
Robert Czechowski's avatar
Robert Czechowski committed
638

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

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

Robert Czechowski's avatar
Robert Czechowski committed
650
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
651
652

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

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

666
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
667
668
}

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

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

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

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

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

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

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

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

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

    if session.csrf_token != csrf_token {
720
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
721
722
    }

723
    let group_code = helpers::make_group_code();
724
    // TODO: check for collisions
725

Robert Czechowski's avatar
Robert Czechowski committed
726
727
    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
728

Robert Czechowski's avatar
Robert Czechowski committed
729
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
730
731
732

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

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

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

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

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

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

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

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

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

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

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

792
793
794
    Ok(())
}

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

801
    let data = json_val::Map::new();
802
803
804

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

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

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

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

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

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

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

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

882
883
884
885
886
887
888
889
            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));
                    }
                }
            }
890
891
        }
    }
892

893
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
894
895
}

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

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

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

933
    let mut result = ProfileStatus::NothingChanged;
934

935
    let mut password_and_salt = None;
936

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

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

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

966
967
968
969
970
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;