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

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
use db_objects::{Grade, Group, Submission, Taskgroup};
5
use db_objects::SessionUser;
6
7
8
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
9
use serde_json::from_str;
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>;

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

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

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

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

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
123
124
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);
    }
}

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

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

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

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

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

        subtaskinfos.push(si);
    }
    subtaskinfos
}

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

169
170
    let mut totalgrade = 0;
    let mut max_totalgrade = 0;
171

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

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

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

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

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

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

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

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

242
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
Robert Czechowski's avatar
Robert Czechowski committed
243
244
245
246
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_alive()
                      .ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout?
247
248
249
250
251
252
253
    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
254
255
        //TODO: use user
        for (_user, userdata) in groupdata {
256
257
            let mut userresults: Vec<String> = Vec::new();

258
259
260
            userresults.push(String::new());
            let mut summe = 0;

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

270
271
            userresults[0] = format!("{}", summe);

272
273
            groupresults.push((format!("Name"), userresults))
        }
274

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

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

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

292
293
294
    Ok(("contestresults".to_owned(), data))
}

Daniel Brüning's avatar
Daniel Brüning committed
295
//TODO: use csrf_token
296
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
Robert Czechowski's avatar
Robert Czechowski committed
297
                                         -> MedalResult<()> {
298
299
300
301
302
303
304
305
306
    // 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);
    }
307

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

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

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

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

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

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

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

365
366
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
367
368
369
370
371
372
                                           -> 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
373
374

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Daniel Brüning's avatar
Daniel Brüning committed
536
pub fn add_group<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
537
                                     -> MedalResult<i32> {
Robert Czechowski's avatar
Robert Czechowski committed
538
539
540
541
    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
542
543

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

547
    let group_code = helpers::make_group_code();
548
    // TODO: check for collisions
549

Robert Czechowski's avatar
Robert Czechowski committed
550
551
    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
552

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

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

558
559
560
561
562
563
564
565
566
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
    let session = conn.get_session_or_new(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;

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

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

567
568

// TODO: Should creating the users and groups happen in a batch operation to speed things up?
569
570
571
572
573
574
575
576
577
578
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str) -> MedalResult<()> {
    let session = conn.get_session(&session_token)
                      .ok_or(MedalError::AccessDenied)?
                      .ensure_logged_in()
                      .ok_or(MedalError::AccessDenied)?;

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

579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
    println!("{}",group_data);
        
    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();
    let mut group =
        Group { id: None, name: "".to_string(), groupcode: "".to_string(), tag: "".to_string(), admin: session.id, members: Vec::new() };
    
    for line in v {
        if name != line[0] {
            if (name != "") {
                conn.create_group_with_users(group);
            }            
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

            group = Group { id: None, name: name.clone(), groupcode: group_code, tag: name.clone(), admin: session.id, members: Vec::new() };
        }

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

610
611
612
    Ok(())
}

Daniel Brüning's avatar
Daniel Brüning committed
613
#[allow(dead_code)]
614
pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
615
    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
616
617
    //TODO: use g
    let _g = conn.get_contest_groups_grades(session.id, contest_id);
618

619
    let data = json_val::Map::new();
620
621
622

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

624
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
625
626
627
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
628
    let session = conn.get_session_or_new(&session_token).ensure_alive().ok_or(MedalError::AccessDenied)?; // TODO SessionTimeout
Robert Czechowski's avatar
Robert Czechowski committed
629
630
631

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

632
633
634
635
    match user_id {
        None => {
            data.insert("firstname".to_string(), to_json(&session.firstname));
            data.insert("lastname".to_string(), to_json(&session.lastname));
636
637
638
            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));
639
640
641
642
643
            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));
644
                data.insert("not_in_group".into(), to_json(&true));
645
646
647
            }
            data.insert("ownprofile".into(), to_json(&true));

648
            data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
649

650
            if let Some(query) = query_string {
651
                if query.starts_with("status=") {
652
                    let status: &str = &query[7..];
Robert Czechowski's avatar
Robert Czechowski committed
653
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
654
655
                        data.insert((status).to_string(), to_json(&true));
                    }
656
657
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
658
        }
659
660
661
662
663
664
665
666
667
668
        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));
669
670
671
            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));
672
673
674
675
676
            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));
677
                data.insert("not_in_group".into(), to_json(&true));
678
            }
679

680
            data.insert("ownprofile".into(), to_json(&false));
681

682
            data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
683

684
685
686
687
688
689
690
691
            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));
                    }
                }
            }
692
693
        }
    }
694

695
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
696
697
}

698
699
#[derive(Debug)]
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
700
701
702
703
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
704
705
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
706
    fn into(self) -> String { format!("{:?}", self) }
707
708
}

709
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
710
                                        (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
711
712
713
714
715
716
                                        -> 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
717
718

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

722
    if session.firstname.as_ref() == Some(&firstname)
Robert Czechowski's avatar
Robert Czechowski committed
723
       && session.lastname.as_ref() == Some(&lastname)
724
725
726
       && session.street == street
       && session.zip == zip
       && session.city == city
Robert Czechowski's avatar
Robert Czechowski committed
727
728
729
730
       && session.grade == grade
    {
        return Ok(ProfileStatus::NothingChanged);
    }
731

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

734
    let mut password_and_salt = None;
735

736
737
738
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
739
740
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
741

742
743
744
745
746
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
747
        } else {
748
            return Ok(ProfileStatus::NothingChanged);
749
        }
750
751
    }

752
753
754
755
756
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
757

758
759
760
761
762
763
764
765
766
767
768
            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 {
769
770
                session.password = Some(password);
                session.salt = Some(salt);
771
            }
Daniel Brüning's avatar
Daniel Brüning committed
772

773
774
775
776
777
778
779
780
781
            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
782

783
784
785
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
786

787
788
789
790
791
792
793
794
795
796
797
            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 {
798
799
                user.password = Some(password);
                user.salt = Some(salt);
800
            }
Daniel Brüning's avatar
Daniel Brüning committed
801

802
            conn.save_session(user);
Robert Czechowski's avatar
Robert Czechowski committed
803
        }
804
    }
805

806
    Ok(result)
Robert Czechowski's avatar
Robert Czechowski committed
807
808
}

809
810
811
812
813
814
815
816
817
818
819
820
821
822
#[derive(PartialEq)]
pub enum UserType {
    User,
    Teacher,
    Admin,
}

pub enum UserGender {
    Female,
    Male,
    Unknown,
}

pub struct ForeignUserData {
823
    pub foreign_id: String,
824
    pub foreign_type: UserType,
Robert Czechowski's avatar
Robert Czechowski committed
825
826
827
    pub gender: UserGender,
    pub firstname: String,
    pub lastname: String,
828
829
}

Robert Czechowski's avatar
Robert Czechowski committed
830
831
832
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,
833
                             &user_data.foreign_id,
834
                             user_data.foreign_type != UserType::User,
Robert Czechowski's avatar
Robert Czechowski committed
835
836
837
838
                             &user_data.firstname,
                             &user_data.lastname)
    {
        Ok(session_token) => Ok(session_token),
839
840
841
        Err(()) => {
            let mut data = json_val::Map::new();
            data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
842
            Err(("login".to_owned(), data))
843
844
845
        }
    }
}