core.rs 35 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: i32,
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
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()),
                                   grade: m.grade,
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
606
607
608

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
609
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
610
611
612

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

614
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
615
616
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
617

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

    if session.csrf_token != csrf_token {
623
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
624
625
    }

626
    let group_code = helpers::make_group_code();
627
    // TODO: check for collisions
628

Robert Czechowski's avatar
Robert Czechowski committed
629
630
    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
631

Robert Czechowski's avatar
Robert Czechowski committed
632
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
633
634
635

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

637
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
638
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
639
640
641
642
643
644
645

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

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

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

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

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

657
658
659
660
661
    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
662
663
664
665
666
667
668
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

669
670
    for line in v {
        if name != line[0] {
671
            if name != "" {
672
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
673
            }
674
675
676
677
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
678
679
680
681
682
683
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
684
685
686
687
688
689
690
691
692
693
694
        }

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

695
696
697
    Ok(())
}

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

704
    let data = json_val::Map::new();
705
706
707

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

709
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
710
711
712
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
713
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
714
715

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

718
719
    match user_id {
        None => {
720
721
722
723
724
            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));
725
726
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

727
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
728
            if session.password.is_some() {
729
730
                data.insert("profile_username".to_string(), to_json(&session.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
731
732
733
            }
            data.insert("ownprofile".into(), to_json(&true));

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

751
752
753
754
755
            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));
756
757
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

758
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
759
            if user.password.is_some() {
760
761
                data.insert("profile_username".to_string(), to_json(&user.username));
                data.insert("profile_not_in_group".into(), to_json(&true));
762
            }
763

764
            data.insert("ownprofile".into(), to_json(&false));
765

766
767
768
769
770
771
772
773
            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));
                    }
                }
            }
774
775
        }
    }
776

777
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
778
779
}

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

791
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
792
793
794
795
796
797
798
799
800
801
                                        (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
802
803
                                        -> MedalResult<ProfileStatus>
{
804
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
805
806

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

810
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
811
       && session.lastname.as_ref() == Some(&lastname)
812
813
814
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
815
816
817
818
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
819

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

822
    let mut password_and_salt = None;
823

824
825
826
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
827
828
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
829

830
831
832
833
834
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
835
        } else {
836
            return Ok(ProfileStatus::NothingChanged);
837
        }
838
839
    }

840
841
842
843
844
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
845

846
847
848
849
850
851
852
853
854
855
856
            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 {
857
858
                session.password = Some(password);
                session.salt = Some(salt);
859
            }
Daniel Brüning's avatar
Daniel Brüning committed
860

861
862
863
864
865
866
867
868
869
            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
870

871
872
873
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
874

875
876
877
878
879
880
881
882
883
884
885
            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 {
886
887
                user.password = Some(password);
                user.salt = Some(salt);
888
            }
Daniel Brüning's avatar
Daniel Brüning committed
889

890
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
891
        }
892
    }
893

894
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
895
896
}

897
898
899
900
901
902
903
904
905
906
907
908
909
910
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
911
    pub foreign_id: String,
912
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
913
914
915
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
916
917
}

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