core.rs 40.7 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
    pub name: String,
29
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
30
31
32
    pub public: bool,
}

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

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

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

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

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

86
    data.insert("parent".to_string(), to_json(&"base"));
87
88
89
    ("index".to_owned(), data)
}

90
91
pub fn status<T: MedalConnection>(conn: &T) -> String { conn.get_debug_information() }

92
93
94
95
96
97
98
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));
99
            data.insert("session_id".to_string(), to_json(&session.id));
100
101
102
103
104
105
106
107
108
109
110
111
112
            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));
113
114
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
115
116
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
                }
            }
        }
        data.insert("session".to_string(), to_json(&token));
    } else {
        data.insert("session".to_string(), to_json(&"No session token given"));
    }

    ("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);
    }
}

134
135
136
137
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
138
    Current,
139
140
}

141
142
143
144
145
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str,
                                         (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>),
                                         visibility: ContestVisibility)
                                         -> MedalValue
{
Robert Czechowski's avatar
Robert Czechowski committed
146
147
    let mut data = json_val::Map::new();

148
    let session = conn.get_session_or_new(&session_token);
149
    fill_user_data(&session, &mut data);
150

151
152
153
154
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

155
156
157
158
159
160
161
162
163
164
165
166
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
    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()));
        }
    }

    data.insert("self_url".to_string(), to_json(&self_url));
    data.insert("oauth_links".to_string(), to_json(&oauth_links));

167
168
169
170
171
172
173
174
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
            .filter(|ci| ci.public)
            .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
175
    data.insert("contest".to_string(), to_json(&v));
176
177
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
178
                            ContestVisibility::Open => "Trainingsaufgaben",
179
180
181
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
182
183
184
185

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

186
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
187
188
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
189
    for st in &tg.tasks {
190
191
192
193
194
195
        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;
        }

196
197
198
199
200
201
        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 };
202
203
204
205
206
207

        subtaskinfos.push(si);
    }
    subtaskinfos
}

208
209
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
210
211
    let session = conn.get_session_or_new(&session_token);

212
    let c = conn.get_contest_by_id_complete(contest_id);
213
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
214

215
    let mut opt_part = conn.get_participation(&session_token, contest_id);
216

217
218
219
220
221
222
223
224
    // Autostart if appropriate
    // 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 {
        conn.new_participation(&session_token, contest_id).map_err(|_| MedalError::AccessDenied)?;
        opt_part = Some(Participation { contest: contest_id, user: session.id, start: time::get_time() });
225
    }
226

227
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
Robert Czechowski's avatar
Robert Czechowski committed
228
229
230
231

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

232
233
234
235
236
237
238
    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));
239
    }
240

241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
    let now = time::get_time();
    let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };

    let contest_not_yet = c.start.map(|start| now < start).unwrap_or(false);
    let contest_no_more = c.end.map(|end| now > end).unwrap_or(false);
    let grade_too_low = c.min_grade.map(|min_grade| student_grade < min_grade).unwrap_or(false);
    let grade_too_high = c.max_grade.map(|max_grade| student_grade > max_grade).unwrap_or(false);

    let contest_running = !contest_not_yet && !contest_no_more;
    let matching_grade = !grade_too_low && !grade_too_high;

    let can_start = session.is_logged_in() && contest_running && matching_grade;

    c.start.map(|start| {
               if now < start {
                   let time_until = start - now;
                   data.insert("time_until_start_d".to_string(), to_json(&(time_until.num_days())));
                   data.insert("time_until_start_h".to_string(), to_json(&(time_until.num_hours() % 24)));
                   data.insert("time_until_start_m".to_string(), to_json(&(time_until.num_minutes() % 60)));
               }
           });

    c.end.map(|end| {
               if now < end {
                   let time_until = end - now;
                   data.insert("time_until_end_d".to_string(), to_json(&(time_until.num_days())));
                   data.insert("time_until_end_h".to_string(), to_json(&(time_until.num_hours() % 24)));
                   data.insert("time_until_end_m".to_string(), to_json(&(time_until.num_minutes() % 60)));
               }
           });

    data.insert("can_start".to_string(), to_json(&can_start));
    data.insert("grade_too_high".to_string(), to_json(&grade_too_high));
    data.insert("grade_too_low".to_string(), to_json(&grade_too_low));
    data.insert("contest_not_yet".to_string(), to_json(&contest_not_yet));
    data.insert("contest_no_more".to_string(), to_json(&contest_no_more));
277

278
    // This only checks if a query string is existent, so any query string will
279
    // lead to the assumption that a bare page is requested. This is useful to
280
281
    // disable caching (via random token) but should be changed if query string
    // can obtain more than only this meaning in the future
282
283
284
285
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
    }

286
287
288
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
289

290
291
292
293
294
295
296
297
298
        let mut tasks = Vec::new();
        for (taskgroup, grade) in c.taskgroups.into_iter().zip(grades) {
            let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
            let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
            tasks.push(ti);

            totalgrade += grade.grade.unwrap_or(0);
            max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
        }
299
        let relative_points = (totalgrade * 100) / max_totalgrade;
300
301

        data.insert("tasks".to_string(), to_json(&tasks));
Robert Czechowski's avatar
Robert Czechowski committed
302

303
304
305
306
307
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
308
309
310
311
312
313
        let left_secs = i64::from(c.duration) * 60 - passed_secs;
        let is_time_left = c.duration == 0 || left_secs >= 0;
        let is_time_up = !is_time_left;
        let left_min = left_secs / 60;
        let left_sec = left_secs % 60;
        let time_left = format!("{}:{:02}", left_min, left_sec);
314

315
        data.insert("started".to_string(), to_json(&true));
316
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
317
318
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
319
320
321
322
323
324
325
        data.insert("relative_points".to_string(), to_json(&relative_points));
        data.insert("is_time_left".to_string(), to_json(&is_time_left));
        data.insert("is_time_up".to_string(), to_json(&is_time_up));
        data.insert("left_min".to_string(), to_json(&left_min));
        data.insert("left_sec".to_string(), to_json(&left_sec));
        data.insert("time_left".to_string(), to_json(&time_left));
        data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
326
    }
327

328
329
330
331
    if c.duration > 0 {
        data.insert("duration".to_string(), to_json(&true));
    }

332
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
333
334
}

335
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
336
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
337
338
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
339

340
341
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

342
    let mut results: Vec<(String, i32, Vec<(String, String, i32, Vec<String>)>)> = Vec::new();
343
344

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

Daniel Brüning's avatar
Daniel Brüning committed
347
        //TODO: use user
348
        for (user, userdata) in groupdata {
349
350
            let mut userresults: Vec<String> = Vec::new();

351
352
353
            userresults.push(String::new());
            let mut summe = 0;

354
            for grade in userdata {
355
356
357
358
359
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
360
                }
361
362
            }

363
364
            userresults[0] = format!("{}", summe);

365
366
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
367
368
                               user.id,
                               userresults))
369
        }
370

371
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
372
373
374
375
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
376
377

    let c = conn.get_contest_by_id(contest_id);
378
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
379
    data.insert("contest".to_string(), to_json(&ci));
380
    data.insert("contestname".to_string(), to_json(&c.name));
381

382
383
384
    Ok(("contestresults".to_owned(), data))
}

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

391
392
393
394
395
396
397
398
399
400
401
    // 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);
        }
    }
402

403
404
    // TODO: Check participant is in correct age group (not super important)

405
406
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
407
        return Err(MedalError::AccessDenied);
408
    }
409

410
    // Check CSRF token
411
412
413
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
414

415
    // Start contest
416
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
417
        Ok(_) => Ok(()),
418
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
419
    }
Robert Czechowski's avatar
Robert Czechowski committed
420
421
}

422
423
424
425
pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String),
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
                                 -> Result<String, MedalValue>
{
Robert Czechowski's avatar
Robert Czechowski committed
426
427
    let (username, password) = login_data;

428
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
429
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
430
431
        Err(()) => {
            let mut data = json_val::Map::new();
432
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
433
            data.insert("username".to_string(), to_json(&username));
434
435
436
437
438
439
440
441
442
443
444
445
446
447
            data.insert("parent".to_string(), to_json(&"base"));

            let mut oauth_links: Vec<(String, String, String)> = Vec::new();
            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()));
                }
            }

            data.insert("self_url".to_string(), to_json(&self_url));
            data.insert("oauth_links".to_string(), to_json(&oauth_links));

448
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
449
450
451
452
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
453
pub fn login_with_code<T: MedalConnection>(
454
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
455
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
456
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
457
458
459
460
461
462
463
        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));
464
465
466
467
468
469
470
471
472
473
474
475
476
477
                data.insert("parent".to_string(), to_json(&"base"));

                let mut oauth_links: Vec<(String, String, String)> = Vec::new();
                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()));
                    }
                }

                data.insert("self_url".to_string(), to_json(&self_url));
                data.insert("oauth_links".to_string(), to_json(&oauth_links));

Robert Czechowski's avatar
Robert Czechowski committed
478
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
479
            }
Robert Czechowski's avatar
Robert Czechowski committed
480
        },
Robert Czechowski's avatar
Robert Czechowski committed
481
482
483
    }
}

484
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
485
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
486
487
}

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

492
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
493
494
495
              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
496
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
497
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
498
499
500
    }
}

501
502
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
503
504
                                           -> MedalResult<String>
{
505
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
506
507

    if session.csrf_token != csrf_token {
508
        return Err(MedalError::CsrfCheckFailed);
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
    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() };
521

Robert Czechowski's avatar
Robert Czechowski committed
522
523
524
525
526
    conn.submit_submission(submission);

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

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

530
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
531
532
    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?
533

534
535
536
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
537

538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
    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);
        }
    }
553

554
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
555
        None => Err(MedalError::AccessDenied),
556
557
558
559
560
561
        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
562

563
            let mut data = json_val::Map::new();
564
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
565
566
567
            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
568

569
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
570
            if c.duration > 0 && left_secs < 0 {
571
                Err(MedalError::AccessDenied)
572
573
574
575
576
            // 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
577
578
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
579
580

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

582
                data.insert("contestname".to_string(), to_json(&c.name));
583
584
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
585
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
586
587
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
588
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
589

Robert Czechowski's avatar
Robert Czechowski committed
590
591
592
593
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

594
595
596
597
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
598
}
Robert Czechowski's avatar
Robert Czechowski committed
599

Robert Czechowski's avatar
Robert Czechowski committed
600
601
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
602
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
603
604
605
606
607
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
611
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
612
613

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

Robert Czechowski's avatar
Robert Czechowski committed
616
617
618
619
620
621
622
623
    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
624
    data.insert("group".to_string(), to_json(&v));
625
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
626

627
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
628
629
}

Robert Czechowski's avatar
Robert Czechowski committed
630
631
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
632
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
633
634
    pub firstname: String,
    pub lastname: String,
635
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
636
637
638
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
646
    if group.admin != session.id {
647
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
648
649
    }

Robert Czechowski's avatar
Robert Czechowski committed
650
651
652
653
654
655
656
657
658
659
660
    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()),
661
662
663
664
665
666
667
668
669
670
671
672
                                   grade: match m.grade {
                                       0 => "Noch kein Schüler".to_string(),
                                       n @ 1..=10 => format!("{}", n),
                                       11 => "11 (G8)".to_string(),
                                       12 => "12 (G8)".to_string(),
                                       111 => "11 (G9)".to_string(),
                                       112 => "12 (G9)".to_string(),
                                       113 => "13 (G9)".to_string(),
                                       114 => "Berufsschule".to_string(),
                                       255 => "Kein Schüler mehr".to_string(),
                                       _ => "?".to_string(),
                                   },
Robert Czechowski's avatar
Robert Czechowski committed
673
674
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
675
676
677

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
678
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
679
680
681

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

683
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
684
685
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
686

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

    if session.csrf_token != csrf_token {
692
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
693
694
    }

695
    let group_code = helpers::make_group_code();
696
    // TODO: check for collisions
697

Robert Czechowski's avatar
Robert Czechowski committed
698
699
    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
700

Robert Czechowski's avatar
Robert Czechowski committed
701
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
702
703
704

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

706
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
707
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
708
709
710
711
712
713
714

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

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

715
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
716
717
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
718
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
719
720
721
722
723

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

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

726
727
728
729
730
    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
731
732
733
734
735
736
737
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

738
739
    for line in v {
        if name != line[0] {
740
            if name != "" {
741
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
742
            }
743
744
745
746
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
747
748
749
750
751
752
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
753
754
755
756
757
758
759
760
761
762
763
        }

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

764
765
766
    Ok(())
}

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

773
    let data = json_val::Map::new();
774
775
776

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

778
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
779
780
781
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
782
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
783
784

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

787
788
    match user_id {
        None => {
789
790
791
792
793
            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));
794
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
795
796
797
798
799
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
800

801
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
802
            if session.password.is_some() {
803
                data.insert("profile_username".to_string(), to_json(&session.username));
804
805
            }
            if session.managed_by.is_none() {
806
                data.insert("profile_not_in_group".into(), to_json(&true));
807
            }
808
809
810
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
811
812
            data.insert("ownprofile".into(), to_json(&true));

813
            if let Some(query) = query_string {
814
                if query.starts_with("status=") {
815
                    let status: &str = &query[7..];
816
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
817
818
                        data.insert((status).to_string(), to_json(&true));
                    }
819
820
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
821
        }
822
823
824
825
826
827
828
829
        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);
            }

830
831
832
833
834
            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));
835
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
836
837
838
839
840
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
841

842
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
843
            if user.username.is_some() {
844
                data.insert("profile_username".to_string(), to_json(&user.username));
845
846
            }
            if user.managed_by.is_none() {
847
                data.insert("profile_not_in_group".into(), to_json(&true));
848
            }
849
850
851
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
852
            data.insert("ownprofile".into(), to_json(&false));
853

854
855
856
857
858
859
860
861
            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));
                    }
                }
            }
862
863
        }
    }
864

865
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
866
867
}

868
#[derive(Debug, PartialEq)]
869
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
870
871
872
873
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
874
875
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
876
    fn into(self) -> String { format!("{:?}", self) }
877
878
}

879
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
                                        (firstname,
                                         lastname,
                                         street,
                                         zip,
                                         city,
                                         password,
                                         password_repeat,
                                         grade,
                                         sex): (String,
                                         String,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         Option<String>,
                                         i32,
                                         Option<i32>))
Robert Czechowski's avatar
Robert Czechowski committed
897
898
                                        -> MedalResult<ProfileStatus>
{
899
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
900
901

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

905
    let mut result = ProfileStatus::NothingChanged;
906

907
    let mut password_and_salt = None;
908

909
910
911
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
912
913
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
914

915
916
917
918
919
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
920
921
922
923
924
925
926
927
928
929
930
931
        }
    }

    if result == ProfileStatus::NothingChanged {
        if session.firstname.as_ref() == Some(&firstname)
           && session.lastname.as_ref() == Some(&lastname)
           && session.street == street
           && session.zip == zip
           && session.city == city
           && session.grade == grade
           && session.sex == sex
        {
932
            return Ok(ProfileStatus::NothingChanged);
933
934
        } else {
            result = ProfileStatus::DataChanged;
935
        }
936
937
    }

938
939
940
941
942
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
943
            session.sex = sex;
944

945
946
947
948
949
950
951
952
953
954
955
            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 {
956
957
                session.password = Some(password);
                session.salt = Some(salt);
958
            }
Daniel Brüning's avatar
Daniel Brüning committed
959

960
961
962
963
964
965
966
967
968
            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
969

970
971
972
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
973
            user.sex = sex;
974

975
976
977
978
979
980
981
982
983
984
985
            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 {
986
987
                user.password = Some(password);
                user.salt = Some(salt);
988
            }
Daniel Brüning's avatar
Daniel Brüning committed
989