core.rs 40.8 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
155
}

156
157
158
159
160
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
161
162
    let mut data = json_val::Map::new();

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

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

170
171
172
173
174
175
176
177
178
179
180
181
    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));

182
183
184
185
186
187
188
189
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
            .filter(|ci| ci.public)
            .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
190
    data.insert("contest".to_string(), to_json(&v));
191
192
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
193
                            ContestVisibility::Open => "Trainingsaufgaben",
194
195
196
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
197
198
199
200

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

201
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
202
203
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
204
    for st in &tg.tasks {
205
206
207
208
209
210
        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;
        }

211
212
213
214
215
216
        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 };
217
218
219
220
221
222

        subtaskinfos.push(si);
    }
    subtaskinfos
}

223
224
225
226
227
228
229
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
#[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 }
}

255
256
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
257
258
    let session = conn.get_session_or_new(&session_token);

259
    let contest = conn.get_contest_by_id_complete(contest_id);
260
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
261

262
    let mut opt_part = conn.get_participation(&session_token, contest_id);
263

264
265
266
    // 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?
267
    // if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
268
    // TODO: Should autostart only happen in the contest time?
269
    if opt_part.is_none() && contest.duration == 0 {
270
271
        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() });
272
    }
273

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

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

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

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

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

291
    let now = time::get_time();
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    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));
315
316
    }

317
318
319
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
320

321
        let mut tasks = Vec::new();
322
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
323
324
325
326
327
328
329
            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);
        }
330
        let relative_points = (totalgrade * 100) / max_totalgrade;
331
332

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

334
335
336
337
338
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
339
340
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
341
342
343
344
        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);
345

346
        data.insert("is_started".to_string(), to_json(&true));
347
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
348
349
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
350
351
352
        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));
353
354
355
356
357
358
        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
359
    }
360

361
362
363
364
365
366
    // 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));
367
368
    }

369
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
370
371
}

372
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
373
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
374
375
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
376

377
378
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

379
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
380
381

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

Daniel Brüning's avatar
Daniel Brüning committed
384
        //TODO: use user
385
        for (user, userdata) in groupdata {
386
387
            let mut userresults: Vec<String> = Vec::new();

388
389
390
            userresults.push(String::new());
            let mut summe = 0;

391
            for grade in userdata {
392
393
394
395
396
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
397
                }
398
399
            }

400
401
            userresults[0] = format!("{}", summe);

402
403
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
404
                               user.id,
405
                               grade_to_string(user.grade),
406
                               userresults))
407
        }
408

409
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
410
411
412
413
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
414
415

    let c = conn.get_contest_by_id(contest_id);
416
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
417
    data.insert("contest".to_string(), to_json(&ci));
418
    data.insert("contestname".to_string(), to_json(&c.name));
419

420
421
422
    Ok(("contestresults".to_owned(), data))
}

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

429
    // Check logged in or open contest
430
    if contest.duration != 0 && !session.is_logged_in() {
431
        return Err(MedalError::AccessDenied);
432
    }
433

434
    // Check CSRF token
435
436
437
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
438

439
440
441
442
443
444
445
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

446
    // Start contest
447
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
448
        Ok(_) => Ok(()),
449
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
450
    }
Robert Czechowski's avatar
Robert Czechowski committed
451
452
}

453
454
455
456
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
457
458
    let (username, password) = login_data;

459
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
460
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
461
462
        Err(()) => {
            let mut data = json_val::Map::new();
463
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
464
            data.insert("username".to_string(), to_json(&username));
465
466
467
468
469
470
471
472
473
474
475
476
477
478
            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));

479
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
480
481
482
483
        }
    }
}

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

515
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
516
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
517
518
}

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

523
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
524
525
526
              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
527
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
528
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
529
530
531
    }
}

532
533
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
534
535
                                           -> MedalResult<String>
{
536
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
537
538

    if session.csrf_token != csrf_token {
539
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
540
541
    }

Robert Czechowski's avatar
Robert Czechowski committed
542
543
544
545
546
547
548
549
550
551
    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() };
552

Robert Czechowski's avatar
Robert Czechowski committed
553
554
555
556
557
    conn.submit_submission(submission);

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

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

561
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
562
563
    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?
564

565
566
567
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
568

569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
    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);
        }
    }
584

585
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
586
        None => Err(MedalError::AccessDenied),
587
588
589
590
591
592
        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
593

594
            let mut data = json_val::Map::new();
595
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
596
597
598
            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
599

600
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
601
            if c.duration > 0 && left_secs < 0 {
602
                Err(MedalError::AccessDenied)
603
604
605
606
607
            // 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
608
609
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
610
611

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

613
                data.insert("contestname".to_string(), to_json(&c.name));
614
615
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
616
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
617
618
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
619
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
620

Robert Czechowski's avatar
Robert Czechowski committed
621
622
623
624
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

625
626
627
628
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
629
}
Robert Czechowski's avatar
Robert Czechowski committed
630

Robert Czechowski's avatar
Robert Czechowski committed
631
632
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
633
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
634
635
636
637
638
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
642
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
643
644

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

Robert Czechowski's avatar
Robert Czechowski committed
647
648
649
650
651
652
653
654
    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
655
    data.insert("group".to_string(), to_json(&v));
656
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
657

658
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
659
660
}

Robert Czechowski's avatar
Robert Czechowski committed
661
662
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
663
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
664
665
    pub firstname: String,
    pub lastname: String,
666
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
667
668
669
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
677
    if group.admin != session.id {
678
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
679
680
    }

Robert Czechowski's avatar
Robert Czechowski committed
681
682
683
684
685
686
687
688
689
690
691
    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()),
692
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
693
694
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
695
696
697

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
698
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
699
700
701

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

703
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
704
705
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
706

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

    if session.csrf_token != csrf_token {
712
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
713
714
    }

715
    let group_code = helpers::make_group_code();
716
    // TODO: check for collisions
717

Robert Czechowski's avatar
Robert Czechowski committed
718
719
    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
720

Robert Czechowski's avatar
Robert Czechowski committed
721
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
722
723
724

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

726
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
727
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
728
729
730
731
732
733
734

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

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

735
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
736
737
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
738
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
739
740
741
742
743

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

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

746
747
748
749
750
    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
751
752
753
754
755
756
757
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

758
759
    for line in v {
        if name != line[0] {
760
            if name != "" {
761
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
762
            }
763
764
765
766
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
767
768
769
770
771
772
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
773
774
775
776
777
778
779
780
781
782
783
        }

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

784
785
786
    Ok(())
}

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

793
    let data = json_val::Map::new();
794
795
796

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

798
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
799
800
801
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
802
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
803
804

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

807
808
    match user_id {
        None => {
809
810
811
812
813
            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));
814
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
815
816
817
818
819
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
820

821
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
822
            if session.password.is_some() {
823
                data.insert("profile_username".to_string(), to_json(&session.username));
824
825
            }
            if session.managed_by.is_none() {
826
                data.insert("profile_not_in_group".into(), to_json(&true));
827
            }
828
829
830
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
831
832
            data.insert("ownprofile".into(), to_json(&true));

833
            if let Some(query) = query_string {
834
                if query.starts_with("status=") {
835
                    let status: &str = &query[7..];
836
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
837
838
                        data.insert((status).to_string(), to_json(&true));
                    }
839
840
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
841
        }
842
843
844
845
846
847
848
849
        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);
            }

850
851
852
853
854
            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));
855
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
856
857
858
859
860
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
861

862
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
863
            if user.username.is_some() {
864
                data.insert("profile_username".to_string(), to_json(&user.username));
865
866
            }
            if user.managed_by.is_none() {
867
                data.insert("profile_not_in_group".into(), to_json(&true));
868
            }
869
870
871
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
872
            data.insert("ownprofile".into(), to_json(&false));
873

874
875
876
877
878
879
880
881
            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));
                    }
                }
            }
882
883
        }
    }
884

885
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
886
887
}

888
#[derive(Debug, PartialEq)]
889
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
890
891
892
893
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
894
895
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
896
    fn into(self) -> String { format!("{:?}", self) }
897
898
}

899
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
                                        (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
917
918
                                        -> MedalResult<ProfileStatus>
{
919
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
920
921

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

925
    let mut result = ProfileStatus::NothingChanged;
926

927
    let mut password_and_salt = None;
928

929
930
931
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
932
933
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
934

935
936
937
938
939
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
940
941
942
943
944
945
946
947
948
949
950
951
        }
    }

    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
        {
952
            return Ok(ProfileStatus::NothingChanged);
953
954
        } else {
            result = ProfileStatus::DataChanged;
955
        }
956
957
    }

958
959
960
961
962
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
963
            session.sex = sex;
964

965
966
967
968
969
970
971
972
973
974
975
            if street.is_some() {
                session.street = street;
            }
            if zip.is_some() {
                session.zip = zip;
            }
            if city.is_some() {
                session.city = city;
            }

            if let Some((password, salt)) = password_and_salt {
976
977
                session.password = Some(password);
                session.salt = Some(salt);