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

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
use db_objects::OptionSession;
5
use db_objects::SessionUser;
6
use db_objects::{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
301
302
303
    if c.duration > 0 {
        data.insert("duration".to_string(), to_json(&true));
    }

304
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
305
306
}

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

312
313
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

314
    let mut results: Vec<(String, i32, Vec<(String, i32, Vec<String>)>)> = Vec::new();
315
316

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

Daniel Brüning's avatar
Daniel Brüning committed
319
        //TODO: use user
320
        for (user, userdata) in groupdata {
321
322
            let mut userresults: Vec<String> = Vec::new();

323
324
325
            userresults.push(String::new());
            let mut summe = 0;

326
            for grade in userdata {
327
328
329
330
331
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
332
                }
333
334
            }

335
336
            userresults[0] = format!("{}", summe);

337
338
339
340
341
            groupresults.push((format!("{} {}",
                                       user.firstname.unwrap_or_default(),
                                       user.lastname.unwrap_or_else(|| "–".to_string())),
                               user.id,
                               userresults))
342
        }
343

344
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
345
346
347
348
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
349
350

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

360
361
362
    Ok(("contestresults".to_owned(), data))
}

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

369
370
371
372
373
374
375
376
377
378
379
    // 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);
        }
    }
380

381
382
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
383
        return Err(MedalError::AccessDenied);
384
    }
385

386
    // Check CSRF token
387
388
389
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
390

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

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

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

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

429
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
430
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
431
432
}

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

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

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

    if session.csrf_token != csrf_token {
453
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
454
455
    }

Robert Czechowski's avatar
Robert Czechowski committed
456
457
458
459
460
461
462
463
464
465
    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() };
466

Robert Czechowski's avatar
Robert Czechowski committed
467
468
469
470
471
    conn.submit_submission(submission);

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

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

475
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
476
477
    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?
478

479
480
481
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
482

483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
    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);
        }
    }
498

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

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

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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
535
536
537
538
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

539
540
541
542
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
543
}
Robert Czechowski's avatar
Robert Czechowski committed
544

Robert Czechowski's avatar
Robert Czechowski committed
545
546
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
547
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
548
549
550
551
552
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
556
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
557
558

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

Robert Czechowski's avatar
Robert Czechowski committed
561
562
563
564
565
566
567
568
    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
569
    data.insert("group".to_string(), to_json(&v));
570
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
571

572
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
573
574
}

Robert Czechowski's avatar
Robert Czechowski committed
575
576
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
577
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
578
579
    pub firstname: String,
    pub lastname: String,
580
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
581
582
583
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
591
    if group.admin != session.id {
592
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
593
594
    }

Robert Czechowski's avatar
Robert Czechowski committed
595
596
597
598
599
600
601
602
603
604
605
    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()),
606
607
608
609
610
611
612
613
614
615
616
617
                                   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
618
619
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
620
621
622

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
623
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
624
625
626

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

628
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
629
630
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
631

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

    if session.csrf_token != csrf_token {
637
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
638
639
    }

640
    let group_code = helpers::make_group_code();
641
    // TODO: check for collisions
642

Robert Czechowski's avatar
Robert Czechowski committed
643
644
    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
645

Robert Czechowski's avatar
Robert Czechowski committed
646
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
647
648
649

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

651
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
652
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
653
654
655
656
657
658
659

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

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

660
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
661
662
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
663
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
664
665
666
667
668

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

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

671
672
673
674
675
    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
676
677
678
679
680
681
682
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

683
684
    for line in v {
        if name != line[0] {
685
            if name != "" {
686
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
687
            }
688
689
690
691
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
692
693
694
695
696
697
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
698
699
700
701
702
703
704
705
706
707
708
        }

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

709
710
711
    Ok(())
}

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

718
    let data = json_val::Map::new();
719
720
721

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

723
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
724
725
726
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
727
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
728
729

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

732
733
    match user_id {
        None => {
734
735
736
737
738
            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));
739
740
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

741
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
742
            if session.password.is_some() {
743
744
                data.insert("profile_username".to_string(), to_json(&session.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
745
746
747
            }
            data.insert("ownprofile".into(), to_json(&true));

748
            if let Some(query) = query_string {
749
                if query.starts_with("status=") {
750
                    let status: &str = &query[7..];
751
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
752
753
                        data.insert((status).to_string(), to_json(&true));
                    }
754
755
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
756
        }
757
758
759
760
761
762
763
764
        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);
            }

765
766
767
768
769
            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));
770
771
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

772
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
773
            if user.password.is_some() {
774
775
                data.insert("profile_username".to_string(), to_json(&user.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
776
            }
777

778
            data.insert("ownprofile".into(), to_json(&false));
779

780
781
782
783
784
785
786
787
            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));
                    }
                }
            }
788
789
        }
    }
790

791
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
792
793
}

794
795
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
796
797
798
799
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
800
801
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
802
    fn into(self) -> String { format!("{:?}", self) }
803
804
}

805
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
806
807
808
809
810
811
812
813
814
815
                                        (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
816
817
                                        -> MedalResult<ProfileStatus>
{
818
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
819
820

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

824
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
825
       && session.lastname.as_ref() == Some(&lastname)
826
827
828
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
829
830
831
832
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
833

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

836
    let mut password_and_salt = None;
837

838
839
840
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
841
842
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
843

844
845
846
847
848
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
849
        } else {
850
            return Ok(ProfileStatus::NothingChanged);
851
        }
852
853
    }

854
855
856
857
858
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
859

860
861
862
863
864
865
866
867
868
869
870
            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 {
871
872
                session.password = Some(password);
                session.salt = Some(salt);
873
            }
Daniel Brüning's avatar
Daniel Brüning committed
874

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

885
886
887
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
888

889
890
891
892
893
894
895
896
897
898
899
            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 {
900
901
                user.password = Some(password);
                user.salt = Some(salt);
902
            }
Daniel Brüning's avatar
Daniel Brüning committed
903

904
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
905
        }
906
    }
907

908
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
909
910
}

911
912
913
914
915
916
917
918
919
920
921
922
923
924
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
925
    pub foreign_id: String,
926
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
927
928
929
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
930
931
}

Robert Czechowski's avatar
Robert Czechowski committed
932
933
934
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,
935
                             &user_data.foreign_id,
936
                             user_data.foreign_type != UserType::User,
Robert Czechowski's avatar
Robert Czechowski committed
937
938
939
940
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
941
942
943
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
944
            Err(("login".to_owned(), data))
945
946
947
        }
    }
}