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
144
    fill_user_data(&session, &mut data);
    
145
146
147
148
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

149
150
151

    

Robert Czechowski's avatar
Robert Czechowski committed
152
153
154
155
156
157
158
159
160
    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() })
161
                                  .filter(|ci| ci.public)
162
163
                                  .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
                                  .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
Robert Czechowski's avatar
Robert Czechowski committed
164
                                  .collect();
Robert Czechowski's avatar
Robert Czechowski committed
165
    data.insert("contest".to_string(), to_json(&v));
166
167
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
168
                            ContestVisibility::Open => "Trainingsaufgaben",
169
170
171
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
172
173
174
175

    ("contests".to_owned(), data)
}

176
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
177
178
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
179
    for st in &tg.tasks {
180
181
182
183
184
185
        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;
        }

186
187
188
189
190
191
        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 };
192
193
194
195
196
197

        subtaskinfos.push(si);
    }
    subtaskinfos
}

198
199
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
200
201
    let session = conn.get_session_or_new(&session_token);

202
    let c = conn.get_contest_by_id_complete(contest_id);
203
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
204

205
206
    let mut totalgrade = 0;
    let mut max_totalgrade = 0;
207

208
    let mut tasks = Vec::new();
209
    for (taskgroup, grade) in c.taskgroups.into_iter().zip(grades) {
210
        let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
211
        let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
212
        tasks.push(ti);
213

214
215
        totalgrade += grade.grade.unwrap_or(0);
        max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
216
    }
217

Robert Czechowski's avatar
Robert Czechowski committed
218
219
220
221
222
223
224
    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
225
226
227
228

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

229
    data.insert("logged_in".to_string(), to_json(&false)); // TODO: cant we just drop these two?
230
    data.insert("can_start".to_string(), to_json(&false));
231
232
233
234
235
236
237
    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));
238
    }
239
    if c.duration == 0 || session.is_logged_in() {
240
        data.insert("can_start".to_string(), to_json(&true));
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256

        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));
            }
        }
257
    }
258

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

267
268
269
    let mut opt_part = conn.get_participation(&session_token, contest_id);

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

279
280
281
282
283
284
    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
        }
285

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

308
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
309
310
}

311
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
312
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
313
314
315
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
    
316
317
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

318
    let mut results: Vec<(String, i32, Vec<(String, i32, Vec<String>)>)> = Vec::new();
319
320

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

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

327
328
329
            userresults.push(String::new());
            let mut summe = 0;

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

339
340
            userresults[0] = format!("{}", summe);

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

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

351
    
352
353
    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
354
355

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

365
366
367
    Ok(("contestresults".to_owned(), data))
}

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

374
375
376
377
378
379
380
381
382
383
384
385
    // 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);
        }
    }
    
386
387
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
388
        return Err(MedalError::AccessDenied);
389
    }
390

391
    // Check CSRF token
392
393
394
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
395

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

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

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

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

434
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
435
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
436
437
}

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

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

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

    if session.csrf_token != csrf_token {
458
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
459
460
    }

Robert Czechowski's avatar
Robert Czechowski committed
461
462
463
464
465
466
467
468
469
470
    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() };
471

Robert Czechowski's avatar
Robert Czechowski committed
472
473
474
475
476
    conn.submit_submission(submission);

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

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

480
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
481
482
    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?
483

484
485
486
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
487

488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
    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);
        }
    }
503

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Robert Czechowski's avatar
Robert Czechowski committed
596
597
598
599
600
601
602
603
604
605
606
607
608
609
    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
610
611
612

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
Robert Czechowski's avatar
Robert Czechowski committed
613
614
615

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

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

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

    if session.csrf_token != csrf_token {
626
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
627
628
    }

629
    let group_code = helpers::make_group_code();
630
    // TODO: check for collisions
631

Robert Czechowski's avatar
Robert Czechowski committed
632
633
    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
634

Robert Czechowski's avatar
Robert Czechowski committed
635
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
636
637
638

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

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

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

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

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

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

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

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

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

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

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

698
699
700
    Ok(())
}

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

707
    let data = json_val::Map::new();
708
709
710

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

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

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

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

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

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

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

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

767
            data.insert("ownprofile".into(), to_json(&false));
768

769
770
771
772
773
774
775
776
            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));
                    }
                }
            }
777
778
        }
    }
779

780
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
781
782
}

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

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

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

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

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

825
    let mut password_and_salt = None;
826

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

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

843
844
845
846
847
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
848

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

864
865
866
867
868
869
870
871
872
            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
873

874
875
876
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
877

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

893
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
894
        }
895
    }
896

897
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
898
899
}

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

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

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

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