core.rs 35.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
    // 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
255
256
257
258
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
    }

259
260
261
    let mut opt_part = conn.get_participation(&session_token, contest_id);

    // Autostart if appropriate
262
263
264
265
266
    // 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 {
267
        conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
268
        opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
269
    }
Robert Czechowski's avatar
Robert Czechowski committed
270

271
272
273
274
275
276
    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
        }
277

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
        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
294
            } else {
295
                data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
296
297
298
            }
        }
    }
299

300
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
301
302
}

303
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
304
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
305
306
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
307

308
309
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

310
    let mut results: Vec<(String, i32, Vec<(String, i32, Vec<String>)>)> = Vec::new();
311
312

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

Daniel Brüning's avatar
Daniel Brüning committed
315
        //TODO: use user
316
        for (user, userdata) in groupdata {
317
318
            let mut userresults: Vec<String> = Vec::new();

319
320
321
            userresults.push(String::new());
            let mut summe = 0;

322
            for grade in userdata {
323
324
325
326
327
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
328
                }
329
330
            }

331
332
            userresults[0] = format!("{}", summe);

333
334
335
336
337
            groupresults.push((format!("{} {}",
                                       user.firstname.unwrap_or_default(),
                                       user.lastname.unwrap_or_else(|| "–".to_string())),
                               user.id,
                               userresults))
338
        }
339

340
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
341
342
343
344
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
345
346

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
347
348
349
350
351
352
353
    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() };
354
    data.insert("contest".to_string(), to_json(&ci));
355

356
357
358
    Ok(("contestresults".to_owned(), data))
}

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

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

377
378
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
379
        return Err(MedalError::AccessDenied);
380
    }
381

382
    // Check CSRF token
383
384
385
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
386

387
    // Start contest
388
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
389
        Ok(_) => Ok(()),
390
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
391
    }
Robert Czechowski's avatar
Robert Czechowski committed
392
393
}

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

397
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
398
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
399
400
        Err(()) => {
            let mut data = json_val::Map::new();
401
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
402
            data.insert("username".to_string(), to_json(&username));
403
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
404
405
406
407
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
408
pub fn login_with_code<T: MedalConnection>(
Daniel Brüning's avatar
Daniel Brüning committed
409
    conn: &T, code: &str)
Robert Czechowski's avatar
Robert Czechowski committed
410
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
411
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
412
413
414
415
416
417
418
419
        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
420
            }
Robert Czechowski's avatar
Robert Czechowski committed
421
        },
Robert Czechowski's avatar
Robert Czechowski committed
422
423
424
    }
}

425
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
426
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
427
428
}

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

433
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
434
435
436
              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
437
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
438
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
439
440
441
    }
}

442
443
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
444
445
                                           -> MedalResult<String>
{
446
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
447
448

    if session.csrf_token != csrf_token {
449
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
450
451
    }

Robert Czechowski's avatar
Robert Czechowski committed
452
453
454
455
456
457
458
459
460
461
    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() };
462

Robert Czechowski's avatar
Robert Czechowski committed
463
464
465
466
467
    conn.submit_submission(submission);

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

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

471
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
472
473
    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?
474

475
476
477
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
478

479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
    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);
        }
    }
494

495
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
496
        None => Err(MedalError::AccessDenied),
497
498
499
500
501
502
        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
503

504
            let mut data = json_val::Map::new();
505
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
506
507
508
            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
509

510
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
511
            if c.duration > 0 && left_secs < 0 {
512
                Err(MedalError::AccessDenied)
513
514
515
516
517
            // 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
518
519
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
520
521

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

523
                data.insert("contestname".to_string(), to_json(&c.name));
524
525
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
526
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
527
528
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
529
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
530

531
532
533
534
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
535
}
Robert Czechowski's avatar
Robert Czechowski committed
536

Robert Czechowski's avatar
Robert Czechowski committed
537
538
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
539
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
540
541
542
543
544
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
548
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
549
550

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

Robert Czechowski's avatar
Robert Czechowski committed
553
554
555
556
557
558
559
560
    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
561
    data.insert("group".to_string(), to_json(&v));
562
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
563

564
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
565
566
}

Robert Czechowski's avatar
Robert Czechowski committed
567
568
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
569
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
570
571
    pub firstname: String,
    pub lastname: String,
572
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
573
574
575
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
583
    if group.admin != session.id {
584
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
585
586
    }

Robert Czechowski's avatar
Robert Czechowski committed
587
588
589
590
591
592
593
594
595
596
597
    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()),
598
599
600
601
602
603
604
605
606
607
608
609
                                   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
610
611
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
612
613
614

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
615
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
616
617
618

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

620
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
621
622
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
623

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

    if session.csrf_token != csrf_token {
629
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
630
631
    }

632
    let group_code = helpers::make_group_code();
633
    // TODO: check for collisions
634

Robert Czechowski's avatar
Robert Czechowski committed
635
636
    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
637

Robert Czechowski's avatar
Robert Czechowski committed
638
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
639
640
641

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

643
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
644
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
645
646
647
648
649
650
651

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

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

652
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
653
654
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
655
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
656
657
658
659
660

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

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

663
664
665
666
667
    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
668
669
670
671
672
673
674
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

675
676
    for line in v {
        if name != line[0] {
677
            if name != "" {
678
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
679
            }
680
681
682
683
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
684
685
686
687
688
689
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
690
691
692
693
694
695
696
697
698
699
700
        }

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

701
702
703
    Ok(())
}

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

710
    let data = json_val::Map::new();
711
712
713

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

715
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
716
717
718
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
719
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
720
721

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

724
725
    match user_id {
        None => {
726
727
728
729
730
            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));
731
732
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

733
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
734
            if session.password.is_some() {
735
736
                data.insert("profile_username".to_string(), to_json(&session.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
737
738
739
            }
            data.insert("ownprofile".into(), to_json(&true));

740
            if let Some(query) = query_string {
741
                if query.starts_with("status=") {
742
                    let status: &str = &query[7..];
743
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
744
745
                        data.insert((status).to_string(), to_json(&true));
                    }
746
747
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
748
        }
749
750
751
752
753
754
755
756
        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);
            }

757
758
759
760
761
            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));
762
763
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

764
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
765
            if user.password.is_some() {
766
767
                data.insert("profile_username".to_string(), to_json(&user.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
768
            }
769

770
            data.insert("ownprofile".into(), to_json(&false));
771

772
773
774
775
776
777
778
779
            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));
                    }
                }
            }
780
781
        }
    }
782

783
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
784
785
}

786
787
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
788
789
790
791
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
792
793
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
794
    fn into(self) -> String { format!("{:?}", self) }
795
796
}

797
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
798
799
800
801
802
803
804
805
806
807
                                        (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
808
809
                                        -> MedalResult<ProfileStatus>
{
810
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
811
812

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

816
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
817
       && session.lastname.as_ref() == Some(&lastname)
818
819
820
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
821
822
823
824
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
825

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

828
    let mut password_and_salt = None;
829

830
831
832
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
833
834
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
835

836
837
838
839
840
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
841
        } else {
842
            return Ok(ProfileStatus::NothingChanged);
843
        }
844
845
    }

846
847
848
849
850
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
851

852
853
854
855
856
857
858
859
860
861
862
            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 {
863
864
                session.password = Some(password);
                session.salt = Some(salt);
865
            }
Daniel Brüning's avatar
Daniel Brüning committed
866

867
868
869
870
871
872
873
874
875
            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
876

877
878
879
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
880

881
882
883
884
885
886
887
888
889
890
891
            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 {
892
893
                user.password = Some(password);
                user.salt = Some(salt);
894
            }
Daniel Brüning's avatar
Daniel Brüning committed
895

896
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
897
        }
898
    }
899

900
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
901
902
}

903
904
905
906
907
908
909
910
911
912
913
914
915
916
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
917
    pub foreign_id: String,
918
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
919
920
921
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
922
923
}

Robert Czechowski's avatar
Robert Czechowski committed
924
925
926
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,
927
                             &user_data.foreign_id,
928
                             user_data.foreign_type != UserType::User,
Robert Czechowski's avatar
Robert Czechowski committed
929
930
931
932
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
933
934
935
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
936
            Err(("login".to_owned(), data))
937
938
939
        }
    }
}