core.rs 38.6 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::{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
29
30
    pub location: String,
    pub filename: String,
    pub name: String,
31
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
32
    pub public: bool,
33
    pub tasks: Vec<TaskInfo>,
Robert Czechowski's avatar
Robert Czechowski committed
34
35
}

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

47
48
49
50
type MedalValue = (String, json_val::Map<String, json_val::Value>);
type MedalResult<T> = Result<T, MedalError>;
type MedalValueResult = MedalResult<MedalValue>;

51
52
53
54
55
56
57
58
59
60
61
62
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"));
}

Robert Czechowski's avatar
Robert Czechowski committed
63
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
64
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
65
66
                                 -> (String, json_val::Map<String, json_val::Value>)
{
67
68
69
70
71
72
    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) {
73
            fill_user_data(&session, &mut data);
74
75
76
        }
    }

77
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
78
79
80
81
82
83
84
85
    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()));
        }
    }

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

89
    data.insert("parent".to_string(), to_json(&"base"));
90
91
92
    ("index".to_owned(), data)
}

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
                                 -> (String, json_val::Map<String, json_val::Value>) {
    let mut data = json_val::Map::new();

    if let Some(token) = session_token {
        if let Some(session) = conn.get_session(&token) {
            data.insert("known_session".to_string(), to_json(&true));
            data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
            if let Some(last_activity) = session.last_activity {
                data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
                data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
            }
            if session.is_alive() {
                data.insert("alive_session".to_string(), to_json(&true));
                if session.is_logged_in() {
                    data.insert("logged_in".to_string(), to_json(&true));
                    data.insert("username".to_string(), to_json(&session.username));
                    data.insert("firstname".to_string(), to_json(&session.firstname));
                    data.insert("lastname".to_string(), to_json(&session.lastname));
                    data.insert("teacher".to_string(), to_json(&session.is_teacher));
                }
            }
        }
        data.insert("session".to_string(), to_json(&token));
        println!("etwas session?!");
    } else {
        data.insert("session".to_string(), to_json(&"No session token given"));
        println!("warum nix session?!");
    }

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

pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
    if let Some(token) = session_token {
        conn.get_session_or_new(&token);
    }
}

132
133
134
135
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
136
    Current,
137
138
}

139
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str, visibility: ContestVisibility) -> MedalValue {
Robert Czechowski's avatar
Robert Czechowski committed
140
141
    let mut data = json_val::Map::new();

142
    let session = conn.get_session_or_new(&session_token);
143
    fill_user_data(&session, &mut data);
144

145
146
147
148
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

Robert Czechowski's avatar
Robert Czechowski committed
149
150
151
152
153
154
155
156
157
    let v: Vec<ContestInfo> = conn.get_contest_list()
                                  .iter()
                                  .map(|c| ContestInfo { id: c.id.unwrap(),
                                                         location: c.location.clone(),
                                                         filename: c.filename.clone(),
                                                         name: c.name.clone(),
                                                         duration: c.duration,
                                                         public: c.public,
                                                         tasks: Vec::new() })
158
                                  .filter(|ci| ci.public)
159
160
                                  .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
                                  .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
Robert Czechowski's avatar
Robert Czechowski committed
161
                                  .collect();
Robert Czechowski's avatar
Robert Czechowski committed
162
    data.insert("contest".to_string(), to_json(&v));
163
164
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
165
                            ContestVisibility::Open => "Trainingsaufgaben",
166
167
168
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
169
170
171
172

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

173
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
174
175
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
176
    for st in &tg.tasks {
177
178
179
180
181
182
        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;
        }

183
184
185
186
187
188
        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 };
189
190
191
192
193
194

        subtaskinfos.push(si);
    }
    subtaskinfos
}

195
196
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
197
198
    let session = conn.get_session_or_new(&session_token);

199
    let c = conn.get_contest_by_id_complete(contest_id);
200
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
201

202
203
    let mut totalgrade = 0;
    let mut max_totalgrade = 0;
204

205
    let mut tasks = Vec::new();
206
    for (taskgroup, grade) in c.taskgroups.into_iter().zip(grades) {
207
        let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
208
        let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
209
        tasks.push(ti);
210

211
212
        totalgrade += grade.grade.unwrap_or(0);
        max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
213
    }
214

Robert Czechowski's avatar
Robert Czechowski committed
215
216
217
218
219
220
221
    let ci = ContestInfo { id: c.id.unwrap(),
                           location: c.location.clone(),
                           filename: c.filename.clone(),
                           name: c.name.clone(),
                           duration: c.duration,
                           public: c.public,
                           tasks: tasks };
Robert Czechowski's avatar
Robert Czechowski committed
222
223
224
225

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

226
    data.insert("logged_in".to_string(), to_json(&false)); // TODO: cant we just drop these two?
227
    data.insert("can_start".to_string(), to_json(&false));
228
229
230
231
232
233
234
    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));
235
    }
236
    if c.duration == 0 || session.is_logged_in() {
237
        data.insert("can_start".to_string(), to_json(&true));
238
239
240
241
242
243
244
245
246
247
248

        if let Some(start_date) = c.start {
            if time::get_time() < start_date {
                data.insert("can_start".to_string(), to_json(&false));
            }
        }
        if let Some(end_date) = c.end {
            if time::get_time() > end_date {
                data.insert("can_start".to_string(), to_json(&false));
            }
        }
249
250
251
252
253
254
255
256
257
258
259
260

        let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };

        if c.min_grade.map(|ming| student_grade < ming).unwrap_or(false) {
            data.insert("can_start".to_string(), to_json(&false));
            data.insert("grade_too_low".to_string(), to_json(&true));
        }

        if c.max_grade.map(|maxg| student_grade > maxg).unwrap_or(false) {
            data.insert("can_start".to_string(), to_json(&false));
            data.insert("grade_too_high".to_string(), to_json(&true));
        }
261
    }
262

263
264
265
266
267
268
269
270
271
272
273
    if let Some(start_date) = c.start {
        if time::get_time() < start_date {
            data.insert("can_start".to_string(), to_json(&false));

            let time_until = start_date - time::get_time();
            data.insert("time_until_d".to_string(), to_json(&(time_until.num_days())));
            data.insert("time_until_h".to_string(), to_json(&(time_until.num_hours() % 24)));
            data.insert("time_until_m".to_string(), to_json(&(time_until.num_minutes() % 60)));
        }
    }

274
    // This only checks if a query string is existent, so any query string will
275
    // lead to the assumption that a bare page is requested. This is useful to
276
277
    // disable caching (via random token) but should be changed if query string
    // can obtain more than only this meaning in the future
278
279
280
281
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
    }

282
283
284
    let mut opt_part = conn.get_participation(&session_token, contest_id);

    // Autostart if appropriate
285
286
287
288
289
    // TODO: Should participation start automatically for teacher? Even before the contest start?
    // Should teachers have all time access or only the same limited amount of time?
    // if opt_part.is_none() && (c.duration == 0 || session.is_teacher) {
    // TODO: Should autostart only happen in the contest time?
    if opt_part.is_none() && c.duration == 0 {
290
        conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
291
        opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
292
    }
Robert Czechowski's avatar
Robert Czechowski committed
293

294
295
296
297
298
299
    if let Some(participation) = opt_part {
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
300

301
302
303
304
305
306
307
308
309
310
        data.insert("started".to_string(), to_json(&true));
        data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
        data.insert("relative_points".to_string(), to_json(&((totalgrade * 100) / max_totalgrade)));

        let left_secs = i64::from(ci.duration) * 60 - passed_secs;
        if left_secs < 0 {
            // Contest over
            data.insert("is_time_left".to_string(), to_json(&false));
311
312
313
            if c.duration > 0 {
                data.insert("is_time_up".to_string(), to_json(&true));
            }
314
315
316
317
318
319
        } else {
            data.insert("is_time_left".to_string(), to_json(&true));
            let left_min = left_secs / 60;
            let left_sec = left_secs % 60;
            if left_sec < 10 {
                data.insert("time_left".to_string(), to_json(&format!("{}:0{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
320
            } else {
321
                data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
322
323
324
            }
        }
    }
325

326
327
328
329
    if c.duration > 0 {
        data.insert("duration".to_string(), to_json(&true));
    }

330
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
331
332
}

333
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
334
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
335
336
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
337

338
339
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

340
    let mut results: Vec<(String, i32, Vec<(String, i32, Vec<String>)>)> = Vec::new();
341
342

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

Daniel Brüning's avatar
Daniel Brüning committed
345
        //TODO: use user
346
        for (user, userdata) in groupdata {
347
348
            let mut userresults: Vec<String> = Vec::new();

349
350
351
            userresults.push(String::new());
            let mut summe = 0;

352
            for grade in userdata {
353
354
355
356
357
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
358
                }
359
360
            }

361
362
            userresults[0] = format!("{}", summe);

363
364
365
366
367
            groupresults.push((format!("{} {}",
                                       user.firstname.unwrap_or_default(),
                                       user.lastname.unwrap_or_else(|| "–".to_string())),
                               user.id,
                               userresults))
368
        }
369

370
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
371
372
373
374
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
375
376

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
377
378
379
380
381
382
383
    let ci = ContestInfo { id: c.id.unwrap(),
                           location: c.location.clone(),
                           filename: c.filename.clone(),
                           name: c.name.clone(),
                           duration: c.duration,
                           public: c.public,
                           tasks: Vec::new() };
384
    data.insert("contest".to_string(), to_json(&ci));
385

386
387
388
    Ok(("contestresults".to_owned(), data))
}

389
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
Robert Czechowski's avatar
Robert Czechowski committed
390
                                         -> MedalResult<()> {
391
    // TODO: Is _or_new the right semantic? We need a CSRF token anyway …
392
    let session = conn.get_session_or_new(&session_token);
393
394
    let c = conn.get_contest_by_id(contest_id);

395
396
397
398
399
400
401
402
403
404
405
    // Check contest currently available:
    if let Some(start_date) = c.start {
        if time::get_time() < start_date {
            return Err(MedalError::AccessDenied);
        }
    }
    if let Some(end_date) = c.end {
        if time::get_time() > end_date {
            return Err(MedalError::AccessDenied);
        }
    }
406

407
408
    // TODO: Check participant is in correct age group (not super important)

409
410
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
411
        return Err(MedalError::AccessDenied);
412
    }
413

414
    // Check CSRF token
415
416
417
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
418

419
    // Start contest
420
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
421
        Ok(_) => Ok(()),
422
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
423
    }
Robert Czechowski's avatar
Robert Czechowski committed
424
425
}

426
427
428
429
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
430
431
    let (username, password) = login_data;

432
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
433
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
434
435
        Err(()) => {
            let mut data = json_val::Map::new();
436
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
437
            data.insert("username".to_string(), to_json(&username));
438
439
440
441
442
443
444
445
446
447
448
449
450
451
            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));

452
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
453
454
455
456
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
457
pub fn login_with_code<T: MedalConnection>(
458
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
459
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
460
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
461
462
463
464
465
466
467
        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));
468
469
470
471
472
473
474
475
476
477
478
479
480
481
                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
482
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
483
            }
Robert Czechowski's avatar
Robert Czechowski committed
484
        },
Robert Czechowski's avatar
Robert Czechowski committed
485
486
487
    }
}

488
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
489
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
490
491
}

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

496
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
497
498
499
              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
500
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
501
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
502
503
504
    }
}

505
506
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
507
508
                                           -> MedalResult<String>
{
509
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
510
511

    if session.csrf_token != csrf_token {
512
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
513
514
    }

Robert Czechowski's avatar
Robert Czechowski committed
515
516
517
518
519
520
521
522
523
524
    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() };
525

Robert Czechowski's avatar
Robert Czechowski committed
526
527
528
529
530
    conn.submit_submission(submission);

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

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

534
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
535
536
    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?
537

538
539
540
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
541

542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
    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);
        }
    }
557

558
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
559
        None => Err(MedalError::AccessDenied),
560
561
562
563
564
565
        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
566

567
            let mut data = json_val::Map::new();
568
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
569
570
571
            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
572

573
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
574
            if c.duration > 0 && left_secs < 0 {
575
                Err(MedalError::AccessDenied)
576
577
578
579
580
            // 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
581
582
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
583
584

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

586
                data.insert("contestname".to_string(), to_json(&c.name));
587
588
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
589
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
590
591
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
592
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
593

Robert Czechowski's avatar
Robert Czechowski committed
594
595
596
597
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

598
599
600
601
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
602
}
Robert Czechowski's avatar
Robert Czechowski committed
603

Robert Czechowski's avatar
Robert Czechowski committed
604
605
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
606
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
607
608
609
610
611
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
615
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
616
617

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

Robert Czechowski's avatar
Robert Czechowski committed
620
621
622
623
624
625
626
627
    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
628
    data.insert("group".to_string(), to_json(&v));
629
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
630

631
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
632
633
}

Robert Czechowski's avatar
Robert Czechowski committed
634
635
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
636
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
637
638
    pub firstname: String,
    pub lastname: String,
639
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
640
641
642
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
650
    if group.admin != session.id {
651
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
652
653
    }

Robert Czechowski's avatar
Robert Czechowski committed
654
655
656
657
658
659
660
661
662
663
664
    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()),
665
666
667
668
669
670
671
672
673
674
675
676
                                   grade: match m.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
677
678
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
679
680
681

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
682
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
683
684
685

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

687
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
688
689
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
690

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

    if session.csrf_token != csrf_token {
696
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
697
698
    }

699
    let group_code = helpers::make_group_code();
700
    // TODO: check for collisions
701

Robert Czechowski's avatar
Robert Czechowski committed
702
703
    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
704

Robert Czechowski's avatar
Robert Czechowski committed
705
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
706
707
708

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

710
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
711
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
712
713
714
715
716
717
718

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

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

719
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
720
721
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
722
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
723
724
725
726
727

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

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

730
731
732
733
734
    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
735
736
737
738
739
740
741
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

742
743
    for line in v {
        if name != line[0] {
744
            if name != "" {
745
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
746
            }
747
748
749
750
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
751
752
753
754
755
756
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
757
758
759
760
761
762
763
764
765
766
767
        }

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

768
769
770
    Ok(())
}

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

777
    let data = json_val::Map::new();
778
779
780

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

782
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
783
784
785
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
786
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
787
788

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

791
792
    match user_id {
        None => {
793
794
795
796
797
            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));
798
799
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

800
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
801
            if session.password.is_some() {
802
803
                data.insert("profile_username".to_string(), to_json(&session.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
804
805
806
            }
            data.insert("ownprofile".into(), to_json(&true));

807
            if let Some(query) = query_string {
808
                if query.starts_with("status=") {
809
                    let status: &str = &query[7..];
810
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
811
812
                        data.insert((status).to_string(), to_json(&true));
                    }
813
814
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
815
        }
816
817
818
819
820
821
822
823
        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);
            }

824
825
826
827
828
            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));
829
830
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

831
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
832
            if user.password.is_some() {
833
834
                data.insert("profile_username".to_string(), to_json(&user.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
835
            }
836

837
            data.insert("ownprofile".into(), to_json(&false));
838

839
840
841
842
843
844
845
846
            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));
                    }
                }
            }
847
848
        }
    }
849

850
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
851
852
}

853
854
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
855
856
857
858
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
859
860
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
861
    fn into(self) -> String { format!("{:?}", self) }
862
863
}

864
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
865
866
867
868
869
870
871
872
873
874
                                        (firstname, lastname, street, zip, city, password, password_repeat, grade): (
        String,
        String,
        Option<String>,
        Option<String>,
        Option<String>,
        Option<String>,
        Option<String>,
        i32,
    ))
Robert Czechowski's avatar
Robert Czechowski committed
875
876
                                        -> MedalResult<ProfileStatus>
{
877
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
878
879

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

883
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
884
       && session.lastname.as_ref() == Some(&lastname)
885
886
887
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
888
889
890
891
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
892

Daniel Brüning's avatar
Daniel Brüning committed
893
    let mut result = ProfileStatus::DataChanged;
894

895
    let mut password_and_salt = None;
896

897
898
899
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
900
901
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
902

903
904
905
906
907
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
908
        } else {
909
            return Ok(ProfileStatus::NothingChanged);
910
        }
911
912
    }

913
914
915
916
917
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
918

919
920
921
922
923
924
925
926
927
928
929
            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 {
930
931
                session.password = Some(password);
                session.salt = Some(salt);
932
            }
Daniel Brüning's avatar
Daniel Brüning committed
933

934
935
936
937
938
939
940
941
942
            conn.save_session(session);
        }
        Some(user_id) => {
            // TODO: Add test to check if this access restriction works
            let (mut 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);
            }
Robert Czechowski's avatar
Robert Czechowski committed
943

944
945
946
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
947

948
949
950
951
952
953
954
955
956
957
958
            if street.is_some() {
                user.street = street;
            }
            if zip.is_some() {
                user.zip = zip;
            }
            if city.is_some() {
                user.city = city;
            }

            if let Some((password, salt)) = password_and_salt {
959
960
                user.password = Some(password);
                user.salt = Some(salt);
961
            }
Daniel Brüning's avatar
Daniel Brüning committed
962

963
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
964
        }
965
    }
966

967
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
968
969
}

970
971
972
973
974
975
976
977
978
979
980
981
982
983
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
984
    pub foreign_id: String,