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
249
250
251
252
253

        println!("jop, hier");
        if let Some(start_date) = c.start {
            println!("jop, hier");
            if time::get_time() < start_date {
                data.insert("can_start".to_string(), to_json(&false));
                println!("jop, hier");
            }
        }
        if let Some(end_date) = c.end {
            println!("jop, hier");
            if time::get_time() > end_date {
                println!("jop, hier");
                data.insert("can_start".to_string(), to_json(&false));
            }
        }
254
    }
255

256
257
258
259
    // 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
260
261
262
263
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
    }

264
265
266
    let mut opt_part = conn.get_participation(&session_token, contest_id);

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

276
277
278
279
280
281
    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
        }
282

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

536
537
538
539
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
540
}
Robert Czechowski's avatar
Robert Czechowski committed
541

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

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

Robert Czechowski's avatar
Robert Czechowski committed
553
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
554
555

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

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

569
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
570
571
}

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

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

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

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

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

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

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

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

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

    if session.csrf_token != csrf_token {
634
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
635
636
    }

637
    let group_code = helpers::make_group_code();
638
    // TODO: check for collisions
639

Robert Czechowski's avatar
Robert Czechowski committed
640
641
    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
642

Robert Czechowski's avatar
Robert Czechowski committed
643
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
644
645
646

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

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

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

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

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

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

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

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

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

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

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

706
707
708
    Ok(())
}

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

715
    let data = json_val::Map::new();
716
717
718

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

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

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

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

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

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

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

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

775
            data.insert("ownprofile".into(), to_json(&false));
776

777
778
779
780
781
782
783
784
            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));
                    }
                }
            }
785
786
        }
    }
787

788
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
789
790
}

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

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

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

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

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

833
    let mut password_and_salt = None;
834

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

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

851
852
853
854
855
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
856

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

872
873
874
875
876
877
878
879
880
            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
881

882
883
884
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
885

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

901
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
902
        }
903
    }
904

905
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
906
907
}

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

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

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

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