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

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
5
6
7
use db_objects::{Grade, Group, Submission, Taskgroup};
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
8

9
10
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
11
    pub id: i32,
12
    pub linktext: String,
13
14
    pub active: bool,
    pub greyout: bool,
15
16
17
18
19
20
21
22
}

#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
    pub name: String,
    pub subtasks: Vec<SubTaskInfo>,
}

Robert Czechowski's avatar
Robert Czechowski committed
23
24
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
25
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
26
27
28
    pub location: String,
    pub filename: String,
    pub name: String,
29
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
30
    pub public: bool,
31
    pub tasks: Vec<TaskInfo>,
Robert Czechowski's avatar
Robert Czechowski committed
32
33
}

34
35
#[derive(Clone)]
pub enum MedalError {
36
37
    NotLoggedIn,
    AccessDenied,
38
39
40
    CsrfCheckFailed,
    SessionTimeout,
    DatabaseError,
41
    PasswordHashingError,
42
    UnmatchedPasswords,
43
}
44

45
46
47
48
type MedalValue = (String, json_val::Map<String, json_val::Value>);
type MedalResult<T> = Result<T, MedalError>;
type MedalValueResult = MedalResult<MedalValue>;

Robert Czechowski's avatar
Robert Czechowski committed
49
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
50
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
51
52
                                 -> (String, json_val::Map<String, json_val::Value>)
{
53
54
55
56
57
58
    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) {
59
60
61
62
63
64
65
            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));
            }
66
67
68
        }
    }

69
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
70
71
72
73
74
75
76
77
    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()));
        }
    }

78
    data.insert("self_url".to_string(), to_json(&self_url));
79
    data.insert("oauth_links".to_string(), to_json(&oauth_links));
80
81
82
83

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

84
85
86
87
88
89
90
91
92
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
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);
    }
}

123
pub fn show_contests<T: MedalConnection>(conn: &T) -> MedalValue {
Robert Czechowski's avatar
Robert Czechowski committed
124
125
    let mut data = json_val::Map::new();

Robert Czechowski's avatar
Robert Czechowski committed
126
127
128
129
130
131
132
133
134
135
    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() })
                                  .collect();
Robert Czechowski's avatar
Robert Czechowski committed
136
137
138
139
140
    data.insert("contest".to_string(), to_json(&v));

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

141
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
142
143
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
144
    for st in &tg.tasks {
145
146
147
148
149
150
        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;
        }

151
152
153
154
155
156
        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 };
157
158
159
160
161
162

        subtaskinfos.push(si);
    }
    subtaskinfos
}

163
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
164
    let c = conn.get_contest_by_id_complete(contest_id);
165
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
166

167
168
    let mut totalgrade = 0;
    let mut max_totalgrade = 0;
169

170
    let mut tasks = Vec::new();
171
    for (taskgroup, grade) in c.taskgroups.into_iter().zip(grades) {
172
        let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
173
        let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
174
        tasks.push(ti);
175

176
177
        totalgrade += grade.grade.unwrap_or(0);
        max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
178
    }
179

Robert Czechowski's avatar
Robert Czechowski committed
180
181
182
183
184
185
186
    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
187
188
189
190

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

191
    data.insert("logged_in".to_string(), to_json(&false));
192
    data.insert("can_start".to_string(), to_json(&false));
193
    if let Some(session) = conn.get_session(&session_token) {
194
        data.insert("logged_in".to_string(), to_json(&true));
195
        data.insert("can_start".to_string(), to_json(&true));
196
197
198
199
        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));
200
        data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
201
    }
202
203
204
    if c.duration == 0 {
        data.insert("can_start".to_string(), to_json(&true));
    }
205

206
    match conn.get_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
207
        None => Ok(("contest".to_owned(), data)),
Robert Czechowski's avatar
Robert Czechowski committed
208
209
210
211
212
213
214
        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
215
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
216
217
218
            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)));
219

220
            let left_secs = i64::from(ci.duration) * 60 - passed_secs;
Robert Czechowski's avatar
Robert Czechowski committed
221
222
            if left_secs < 0 {
                // Contest over
223
                data.insert("is_time_left".to_string(), to_json(&false));
Robert Czechowski's avatar
Robert Czechowski committed
224
            } else {
225
                data.insert("is_time_left".to_string(), to_json(&true));
Robert Czechowski's avatar
Robert Czechowski committed
226
227
                let left_min = left_secs / 60;
                let left_sec = left_secs % 60;
228
                if left_sec < 10 {
Robert Czechowski's avatar
Robert Czechowski committed
229
230
231
                    data.insert("time_left".to_string(), to_json(&format!("{}:0{}", left_min, left_sec)));
                } else {
                    data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
232
233
                }
            }
234

235
            Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
236
237
238
239
        }
    }
}

240
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
Robert Czechowski's avatar
Robert Czechowski committed
241
242
243
244
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout?
245
246
247
248
249
250
251
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

    let mut results: Vec<(String, Vec<(String, Vec<String>)>)> = Vec::new();

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

Daniel Brüning's avatar
Daniel Brüning committed
252
253
        //TODO: use user
        for (_user, userdata) in groupdata {
254
255
            let mut userresults: Vec<String> = Vec::new();

256
257
258
            userresults.push(String::new());
            let mut summe = 0;

259
            for grade in userdata {
260
261
262
263
264
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
265
                }
266
267
            }

268
269
            userresults[0] = format!("{}", summe);

270
271
            groupresults.push((format!("Name"), userresults))
        }
272

Robert Czechowski's avatar
Robert Czechowski committed
273
        results.push((format!("{}", group.name), groupresults));
274
275
276
277
278
    }

    let mut data = json_val::Map::new();
    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
279
280

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
281
282
283
284
285
286
287
    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() };
288
    data.insert("contest".to_string(), to_json(&ci));
289

290
291
292
    Ok(("contestresults".to_owned(), data))
}

Daniel Brüning's avatar
Daniel Brüning committed
293
//TODO: use csrf_token
294
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
Robert Czechowski's avatar
Robert Czechowski committed
295
                                         -> MedalResult<()> {
296
297
298
299
300
301
302
303
304
    // TODO: Should check if logged in or anonymous!
    // TODO: Is _or_new the right semantic?
    let session = conn.get_session_or_new(&session_token);
    //.ensure_logged_in()
    //.ok_or(MedalError::AccessDenied)?;

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

306
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
307
        Ok(_) => Ok(()),
308
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
309
    }
Robert Czechowski's avatar
Robert Czechowski committed
310
311
}

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

315
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
316
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
317
318
        Err(()) => {
            let mut data = json_val::Map::new();
319
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
320
            data.insert("username".to_string(), to_json(&username));
321
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
322
323
324
325
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
326
pub fn login_with_code<T: MedalConnection>(
Daniel Brüning's avatar
Daniel Brüning committed
327
    conn: &T, code: &str)
Robert Czechowski's avatar
Robert Czechowski committed
328
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
329
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
330
331
332
333
334
335
336
337
        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
338
            }
Robert Czechowski's avatar
Robert Czechowski committed
339
        },
Robert Czechowski's avatar
Robert Czechowski committed
340
341
342
    }
}

343
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
344
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
345
346
}

347
pub fn load_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, subtask: Option<String>)
Robert Czechowski's avatar
Robert Czechowski committed
348
349
350
351
352
                                           -> MedalResult<String> {
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
353

354
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
355
356
357
              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
358
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
359
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
360
361
362
    }
}

363
364
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
365
366
367
368
369
370
                                           -> MedalResult<String>
{
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
371
372

    if session.csrf_token != csrf_token {
373
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
374
375
    }

Robert Czechowski's avatar
Robert Czechowski committed
376
377
378
379
380
381
382
383
384
385
    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() };
386

Robert Czechowski's avatar
Robert Czechowski committed
387
388
389
390
391
    conn.submit_submission(submission);

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

392
pub fn show_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str) -> MedalValueResult {
393
    let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
394

395
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
396
397
    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?
398

399
400
401
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
402

403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
    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);
        }
    }
418

419
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
420
        None => Err(MedalError::AccessDenied),
421
422
423
424
425
426
        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
427

428
            let mut data = json_val::Map::new();
429
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
430
431
432
            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
433

434
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
435
            if c.duration > 0 && left_secs < 0 {
436
                Err(MedalError::AccessDenied)
437
438
439
440
441
            // 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
442
443
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
444
445

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

447
                data.insert("contestname".to_string(), to_json(&c.name));
448
449
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
450
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
451
452
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
453
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
454

455
456
457
458
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
459
}
Robert Czechowski's avatar
Robert Czechowski committed
460

Robert Czechowski's avatar
Robert Czechowski committed
461
462
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
463
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
464
465
466
467
468
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
472
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
473
474
475

    let mut data = json_val::Map::new();

Robert Czechowski's avatar
Robert Czechowski committed
476
477
478
479
480
481
482
483
    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
484
    data.insert("group".to_string(), to_json(&v));
485
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
486

487
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
488
489
}

Robert Czechowski's avatar
Robert Czechowski committed
490
491
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
492
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
493
494
    pub firstname: String,
    pub lastname: String,
495
    pub grade: i32,
Robert Czechowski's avatar
Robert Czechowski committed
496
497
498
    pub logincode: String,
}

499
pub fn show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
500
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
501
502
503
    let group = conn.get_group_complete(group_id).unwrap(); // TODO handle error

    let mut data = json_val::Map::new();
504

Robert Czechowski's avatar
Robert Czechowski committed
505
    if group.admin != session.id {
506
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
507
508
    }

Robert Czechowski's avatar
Robert Czechowski committed
509
510
511
512
513
514
515
516
517
518
519
520
521
522
    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
523
524
525

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
Robert Czechowski's avatar
Robert Czechowski committed
526
527
528

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

530
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
531
532
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
533

Daniel Brüning's avatar
Daniel Brüning committed
534
pub fn add_group<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
535
                                     -> MedalResult<i32> {
Robert Czechowski's avatar
Robert Czechowski committed
536
537
538
539
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_logged_in()
                      .ok_or(MedalError::AccessDenied)?;
Robert Czechowski's avatar
Robert Czechowski committed
540
541

    if session.csrf_token != csrf_token {
542
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
543
544
    }

545
    let group_code = helpers::make_group_code();
546
    // TODO: check for collisions
547

Robert Czechowski's avatar
Robert Czechowski committed
548
549
    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
550

Robert Czechowski's avatar
Robert Czechowski committed
551
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
552
553
554

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

Daniel Brüning's avatar
Daniel Brüning committed
556
#[allow(dead_code)]
557
pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
558
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Daniel Brüning's avatar
Daniel Brüning committed
559
560
    //TODO: use g
    let _g = conn.get_contest_groups_grades(session.id, contest_id);
561

562
    let data = json_val::Map::new();
563
564
565

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

567
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
568
569
570
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
571
    let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
572
573
574

    let mut data = json_val::Map::new();

575
576
577
578
    match user_id {
        None => {
            data.insert("firstname".to_string(), to_json(&session.firstname));
            data.insert("lastname".to_string(), to_json(&session.lastname));
579
580
581
            data.insert("street".to_string(), to_json(&session.street));
            data.insert("zip".to_string(), to_json(&session.zip));
            data.insert("city".to_string(), to_json(&session.city));
582
583
584
585
586
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));

            data.insert("logincode".to_string(), to_json(&session.logincode));
            if session.password.is_some() {
                data.insert("username".to_string(), to_json(&session.username));
587
                data.insert("not_in_group".into(), to_json(&true));
588
589
590
            }
            data.insert("ownprofile".into(), to_json(&true));

591
            data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
592

593
            if let Some(query) = query_string {
594
                if query.starts_with("status=") {
595
                    let status: &str = &query[7..];
Robert Czechowski's avatar
Robert Czechowski committed
596
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
597
598
                        data.insert((status).to_string(), to_json(&true));
                    }
599
600
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
601
        }
602
603
604
605
606
607
608
609
610
611
        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);
            }

            data.insert("firstname".to_string(), to_json(&user.firstname));
            data.insert("lastname".to_string(), to_json(&user.lastname));
612
613
614
            data.insert("street".to_string(), to_json(&session.street));
            data.insert("zip".to_string(), to_json(&session.zip));
            data.insert("city".to_string(), to_json(&session.city));
615
616
617
618
619
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));

            data.insert("logincode".to_string(), to_json(&user.logincode));
            if user.password.is_some() {
                data.insert("username".to_string(), to_json(&user.username));
620
                data.insert("not_in_group".into(), to_json(&true));
621
            }
622

623
            data.insert("ownprofile".into(), to_json(&false));
624

625
            data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
626

627
628
629
630
631
632
633
634
            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));
                    }
                }
            }
635
636
        }
    }
637

638
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
639
640
}

641
642
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
643
644
645
646
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
647
648
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
649
    fn into(self) -> String { format!("{:?}", self) }
650
651
}

652
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
653
                                        (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
654
655
656
657
658
659
                                        -> MedalResult<ProfileStatus>
{
    let mut session = conn.get_session(&session_token)
                          .ok_or(MedalError::AccessDenied)?
                          .ensure_alive()
                          .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
660
661

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

665
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
666
       && session.lastname.as_ref() == Some(&lastname)
667
668
669
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
670
671
672
673
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
674

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

677
    let mut password_and_salt = None;
678

679
680
681
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
682
683
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
684

685
686
687
688
689
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
690
        } else {
691
            return Ok(ProfileStatus::NothingChanged);
692
        }
693
694
    }

695
696
697
698
699
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
700

701
702
703
704
705
706
707
708
709
710
711
            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 {
712
713
                session.password = Some(password);
                session.salt = Some(salt);
714
            }
Daniel Brüning's avatar
Daniel Brüning committed
715

716
717
718
719
720
721
722
723
724
            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
725

726
727
728
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
729

730
731
732
733
734
735
736
737
738
739
740
            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 {
741
742
                user.password = Some(password);
                user.salt = Some(salt);
743
            }
Daniel Brüning's avatar
Daniel Brüning committed
744

745
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
746
        }
747
    }
748

749
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
750
751
}

752
753
754
755
756
757
758
759
760
761
762
763
764
765
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
766
    pub foreign_id: String,
767
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
768
769
770
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
771
772
}

Robert Czechowski's avatar
Robert Czechowski committed
773
774
775
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,
776
                             &user_data.foreign_id,
777
                             user_data.foreign_type != UserType::User,
Robert Czechowski's avatar
Robert Czechowski committed
778
779
780
781
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
782
783
784
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
785
            Err(("login".to_owned(), data))
786
787
788
        }
    }
}