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
299
300
        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);
        }

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

302
303
304
305
306
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
307

308
309
310
311
312
313
314
315
316
317
        data.insert("started".to_string(), to_json(&true));
        data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
        data.insert("relative_points".to_string(), to_json(&((totalgrade * 100) / max_totalgrade)));

        let left_secs = i64::from(ci.duration) * 60 - passed_secs;
        if left_secs < 0 {
            // Contest over
            data.insert("is_time_left".to_string(), to_json(&false));
318
319
320
            if c.duration > 0 {
                data.insert("is_time_up".to_string(), to_json(&true));
            }
321
322
323
324
325
326
        } else {
            data.insert("is_time_left".to_string(), to_json(&true));
            let left_min = left_secs / 60;
            let left_sec = left_secs % 60;
            if left_sec < 10 {
                data.insert("time_left".to_string(), to_json(&format!("{}:0{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
327
            } else {
328
                data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
329
330
331
            }
        }
    }
332

333
334
335
336
    if c.duration > 0 {
        data.insert("duration".to_string(), to_json(&true));
    }

337
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
338
339
}

340
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
341
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
342
343
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
344

345
346
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

347
    let mut results: Vec<(String, i32, Vec<(String, String, i32, Vec<String>)>)> = Vec::new();
348
349

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

Daniel Brüning's avatar
Daniel Brüning committed
352
        //TODO: use user
353
        for (user, userdata) in groupdata {
354
355
            let mut userresults: Vec<String> = Vec::new();

356
357
358
            userresults.push(String::new());
            let mut summe = 0;

359
            for grade in userdata {
360
361
362
363
364
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
365
                }
366
367
            }

368
369
            userresults[0] = format!("{}", summe);

370
371
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
372
373
                               user.id,
                               userresults))
374
        }
375

376
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
377
378
379
380
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
381
382

    let c = conn.get_contest_by_id(contest_id);
383
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
384
    data.insert("contest".to_string(), to_json(&ci));
385
    data.insert("contestname".to_string(), to_json(&c.name));
386

387
388
389
    Ok(("contestresults".to_owned(), data))
}

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

396
397
398
399
400
401
402
403
404
405
406
    // 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);
        }
    }
407

408
409
    // TODO: Check participant is in correct age group (not super important)

410
411
    // Check logged in or open contest
    if c.duration != 0 && !session.is_logged_in() {
412
        return Err(MedalError::AccessDenied);
413
    }
414

415
    // Check CSRF token
416
417
418
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
419

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

427
428
429
430
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
431
432
    let (username, password) = login_data;

433
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
434
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
435
436
        Err(()) => {
            let mut data = json_val::Map::new();
437
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
438
            data.insert("username".to_string(), to_json(&username));
439
440
441
442
443
444
445
446
447
448
449
450
451
452
            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));

453
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
454
455
456
457
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
458
pub fn login_with_code<T: MedalConnection>(
459
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
460
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
461
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
462
463
464
465
466
467
468
        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));
469
470
471
472
473
474
475
476
477
478
479
480
481
482
                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
483
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
484
            }
Robert Czechowski's avatar
Robert Czechowski committed
485
        },
Robert Czechowski's avatar
Robert Czechowski committed
486
487
488
    }
}

489
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
490
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
491
492
}

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

497
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
498
499
500
              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
501
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
502
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
503
504
505
    }
}

506
507
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
508
509
                                           -> MedalResult<String>
{
510
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
511
512

    if session.csrf_token != csrf_token {
513
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
514
515
    }

Robert Czechowski's avatar
Robert Czechowski committed
516
517
518
519
520
521
522
523
524
525
    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() };
526

Robert Czechowski's avatar
Robert Czechowski committed
527
528
529
530
531
    conn.submit_submission(submission);

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

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

535
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
536
537
    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?
538

539
540
541
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
542

543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
    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);
        }
    }
558

559
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
560
        None => Err(MedalError::AccessDenied),
561
562
563
564
565
566
        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
567

568
            let mut data = json_val::Map::new();
569
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
570
571
572
            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
573

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

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

587
                data.insert("contestname".to_string(), to_json(&c.name));
588
589
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
590
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
591
592
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
593
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
594

Robert Czechowski's avatar
Robert Czechowski committed
595
596
597
598
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

599
600
601
602
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
603
}
Robert Czechowski's avatar
Robert Czechowski committed
604

Robert Czechowski's avatar
Robert Czechowski committed
605
606
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
607
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
608
609
610
611
612
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
616
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
617
618

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

Robert Czechowski's avatar
Robert Czechowski committed
621
622
623
624
625
626
627
628
    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
629
    data.insert("group".to_string(), to_json(&v));
630
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
631

632
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
633
634
}

Robert Czechowski's avatar
Robert Czechowski committed
635
636
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
637
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
638
639
    pub firstname: String,
    pub lastname: String,
640
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
641
642
643
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
651
    if group.admin != session.id {
652
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
653
654
    }

Robert Czechowski's avatar
Robert Czechowski committed
655
656
657
658
659
660
661
662
663
664
665
    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()),
666
667
668
669
670
671
672
673
674
675
676
677
                                   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
678
679
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
680
681
682

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
683
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
684
685
686

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

688
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
689
690
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
691

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

    if session.csrf_token != csrf_token {
697
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
698
699
    }

700
    let group_code = helpers::make_group_code();
701
    // TODO: check for collisions
702

Robert Czechowski's avatar
Robert Czechowski committed
703
704
    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
705

Robert Czechowski's avatar
Robert Czechowski committed
706
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
707
708
709

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

711
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
712
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
713
714
715
716
717
718
719

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

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

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

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

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

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

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

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

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

769
770
771
    Ok(())
}

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

778
    let data = json_val::Map::new();
779
780
781

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

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

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

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

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

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

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

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

859
860
861
862
863
864
865
866
            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));
                    }
                }
            }
867
868
        }
    }
869

870
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
871
872
}

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

884
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
                                        (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
902
903
                                        -> MedalResult<ProfileStatus>
{
904
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
905
906

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

910
    let mut result = ProfileStatus::NothingChanged;
911

912
    let mut password_and_salt = None;
913

914
915
916
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
917
918
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
919

920
921
922
923
924
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
925
926
927
928
929
930
931
932
933
934
935
936
        }
    }

    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
        {
937
            return Ok(ProfileStatus::NothingChanged);
938
939
        } else {
            result = ProfileStatus::DataChanged;
940
        }
941
942
    }

943
944
945
946
947
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
948
            session.sex = sex;
949

950
951
952
953
954
955
956
957
958
959
960
            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 {
961
962
                session.password = Some(password);
                session.salt = Some(salt);
963
            }
Daniel Brüning's avatar
Daniel Brüning committed
964

965
966
967
968
969
970
971
972
973
            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
974

975
976
977
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
978
            user.sex = sex;
979

980
981
982
983
984
985
986
987
988
989
990
            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 {
991
992
                user.password = Some(password);
                user.salt = Some(salt);
993
            }
Daniel Brüning's avatar
Daniel Brüning committed
994

995
            conn.save_session(user);
Robert Czechowski's avatar