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

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
use db_objects::OptionSession;
5
use db_objects::SessionUser;
6
use db_objects::{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
261
    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)));
        }
    }

262
263
264
265
    // This only checks if a query string is existent, so any query string will
    // lead to the assumption that a base page is requested. This is usefull to
    // disable caching (via random token) but should be changed if query string
    // can obtain more than only this meaning in the future
266
267
268
269
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
    }

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

    // Autostart if appropriate
273
274
275
276
277
    // 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 {
278
        conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
279
        opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
280
    }
Robert Czechowski's avatar
Robert Czechowski committed
281

282
283
284
285
286
287
    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
        }
288

289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
        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));
        } 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
305
            } else {
306
                data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
307
308
309
            }
        }
    }
310

311
312
313
314
    if c.duration > 0 {
        data.insert("duration".to_string(), to_json(&true));
    }

315
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
316
317
}

318
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
319
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
320
321
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
322

323
324
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

325
    let mut results: Vec<(String, i32, Vec<(String, i32, Vec<String>)>)> = Vec::new();
326
327

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

Daniel Brüning's avatar
Daniel Brüning committed
330
        //TODO: use user
331
        for (user, userdata) in groupdata {
332
333
            let mut userresults: Vec<String> = Vec::new();

334
335
336
            userresults.push(String::new());
            let mut summe = 0;

337
            for grade in userdata {
338
339
340
341
342
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
343
                }
344
345
            }

346
347
            userresults[0] = format!("{}", summe);

348
349
350
351
352
            groupresults.push((format!("{} {}",
                                       user.firstname.unwrap_or_default(),
                                       user.lastname.unwrap_or_else(|| "–".to_string())),
                               user.id,
                               userresults))
353
        }
354

355
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
356
357
358
359
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
360
361

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
362
363
364
365
366
367
368
    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() };
369
    data.insert("contest".to_string(), to_json(&ci));
370

371
372
373
    Ok(("contestresults".to_owned(), data))
}

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

380
381
382
383
384
385
386
387
388
389
390
    // 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);
        }
    }
391

392
393
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
394
        return Err(MedalError::AccessDenied);
395
    }
396

397
    // Check CSRF token
398
399
400
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
401

402
    // Start contest
403
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
404
        Ok(_) => Ok(()),
405
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
406
    }
Robert Czechowski's avatar
Robert Czechowski committed
407
408
}

409
pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String)) -> Result<String, MedalValue> {
Robert Czechowski's avatar
Robert Czechowski committed
410
411
    let (username, password) = login_data;

412
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
413
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
414
415
        Err(()) => {
            let mut data = json_val::Map::new();
416
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
417
            data.insert("username".to_string(), to_json(&username));
418
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
419
420
421
422
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
423
pub fn login_with_code<T: MedalConnection>(
Daniel Brüning's avatar
Daniel Brüning committed
424
    conn: &T, code: &str)
Robert Czechowski's avatar
Robert Czechowski committed
425
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
426
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
427
428
429
430
431
432
433
434
        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));
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
435
            }
Robert Czechowski's avatar
Robert Czechowski committed
436
        },
Robert Czechowski's avatar
Robert Czechowski committed
437
438
439
    }
}

440
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
441
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
442
443
}

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

448
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
449
450
451
              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
452
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
453
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
454
455
456
    }
}

457
458
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
459
460
                                           -> MedalResult<String>
{
461
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
462
463

    if session.csrf_token != csrf_token {
464
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
465
466
    }

Robert Czechowski's avatar
Robert Czechowski committed
467
468
469
470
471
472
473
474
475
476
    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() };
477

Robert Czechowski's avatar
Robert Czechowski committed
478
479
480
481
482
    conn.submit_submission(submission);

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

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

486
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
487
488
    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?
489

490
491
492
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
493

494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    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);
        }
    }
509

510
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
511
        None => Err(MedalError::AccessDenied),
512
513
514
515
516
517
        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
518

519
            let mut data = json_val::Map::new();
520
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
521
522
523
            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
524

525
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
526
            if c.duration > 0 && left_secs < 0 {
527
                Err(MedalError::AccessDenied)
528
529
530
531
532
            // 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
533
534
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
535
536

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

538
                data.insert("contestname".to_string(), to_json(&c.name));
539
540
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
541
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
542
543
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
544
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
545

Robert Czechowski's avatar
Robert Czechowski committed
546
547
548
549
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

550
551
552
553
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
554
}
Robert Czechowski's avatar
Robert Czechowski committed
555

Robert Czechowski's avatar
Robert Czechowski committed
556
557
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
558
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
559
560
561
562
563
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
567
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
568
569

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

Robert Czechowski's avatar
Robert Czechowski committed
572
573
574
575
576
577
578
579
    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
580
    data.insert("group".to_string(), to_json(&v));
581
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
582

583
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
584
585
}

Robert Czechowski's avatar
Robert Czechowski committed
586
587
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
588
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
589
590
    pub firstname: String,
    pub lastname: String,
591
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
592
593
594
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
602
    if group.admin != session.id {
603
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
604
605
    }

Robert Czechowski's avatar
Robert Czechowski committed
606
607
608
609
610
611
612
613
614
615
616
    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()),
617
618
619
620
621
622
623
624
625
626
627
628
                                   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
629
630
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
631
632
633

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
634
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
635
636
637

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

639
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
640
641
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
642

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

    if session.csrf_token != csrf_token {
648
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
649
650
    }

651
    let group_code = helpers::make_group_code();
652
    // TODO: check for collisions
653

Robert Czechowski's avatar
Robert Czechowski committed
654
655
    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
656

Robert Czechowski's avatar
Robert Czechowski committed
657
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
658
659
660

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

662
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
663
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
664
665
666
667
668
669
670

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

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

671
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
672
673
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
674
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
675
676
677
678
679

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

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

682
683
684
685
686
    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
687
688
689
690
691
692
693
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

694
695
    for line in v {
        if name != line[0] {
696
            if name != "" {
697
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
698
            }
699
700
701
702
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
703
704
705
706
707
708
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
709
710
711
712
713
714
715
716
717
718
719
        }

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

720
721
722
    Ok(())
}

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

729
    let data = json_val::Map::new();
730
731
732

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

734
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
735
736
737
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
738
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
739
740

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

743
744
    match user_id {
        None => {
745
746
747
748
749
            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));
750
751
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

752
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
753
            if session.password.is_some() {
754
755
                data.insert("profile_username".to_string(), to_json(&session.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
756
757
758
            }
            data.insert("ownprofile".into(), to_json(&true));

759
            if let Some(query) = query_string {
760
                if query.starts_with("status=") {
761
                    let status: &str = &query[7..];
762
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
763
764
                        data.insert((status).to_string(), to_json(&true));
                    }
765
766
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
767
        }
768
769
770
771
772
773
774
775
        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);
            }

776
777
778
779
780
            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));
781
782
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

783
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
784
            if user.password.is_some() {
785
786
                data.insert("profile_username".to_string(), to_json(&user.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
787
            }
788

789
            data.insert("ownprofile".into(), to_json(&false));
790

791
792
793
794
795
796
797
798
            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));
                    }
                }
            }
799
800
        }
    }
801

802
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
803
804
}

805
806
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
807
808
809
810
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
811
812
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
813
    fn into(self) -> String { format!("{:?}", self) }
814
815
}

816
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
817
818
819
820
821
822
823
824
825
826
                                        (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
827
828
                                        -> MedalResult<ProfileStatus>
{
829
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
830
831

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

835
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
836
       && session.lastname.as_ref() == Some(&lastname)
837
838
839
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
840
841
842
843
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
844

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

847
    let mut password_and_salt = None;
848

849
850
851
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
852
853
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
854

855
856
857
858
859
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
860
        } else {
861
            return Ok(ProfileStatus::NothingChanged);
862
        }
863
864
    }

865
866
867
868
869
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
870

871
872
873
874
875
876
877
878
879
880
881
            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 {
882
883
                session.password = Some(password);
                session.salt = Some(salt);
884
            }
Daniel Brüning's avatar
Daniel Brüning committed
885

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

896
897
898
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
899

900
901
902
903
904
905
906
907
908
909
910
            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 {
911
912
                user.password = Some(password);
                user.salt = Some(salt);
913
            }
Daniel Brüning's avatar
Daniel Brüning committed
914

915
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
916
        }
917
    }
918

919
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
920
921
}

922
923
924
925
926
927
928
929
930
931
932
933
934
935
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
936
    pub foreign_id: String,
937
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
938
939
940
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
941
942
}

Robert Czechowski's avatar
Robert Czechowski committed
943
944
945
pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData)
                                       -> Result<String, (String, json_val::Map<String, json_val::Value>)> {
    match conn.login_foreign(None,
946
                             &user_data.foreign_id,
947
                             user_data.foreign_type != UserType::User,
Robert Czechowski's avatar
Robert Czechowski committed
948
949
950
951
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
952
953
954
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
955
            Err(("login".to_owned(), data))
956
957
958
        }
    }
}