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

        if let Some(start_date) = c.start {
            if time::get_time() < start_date {
                data.insert("can_start".to_string(), to_json(&false));
            }
        }
        if let Some(end_date) = c.end {
            if time::get_time() > end_date {
                data.insert("can_start".to_string(), to_json(&false));
            }
        }
255
256
257
258
259
260
261
262
263
264
265
266

        let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };

        if c.min_grade.map(|ming| student_grade < ming).unwrap_or(false) {
            data.insert("can_start".to_string(), to_json(&false));
            data.insert("grade_too_low".to_string(), to_json(&true));
        }

        if c.max_grade.map(|maxg| student_grade > maxg).unwrap_or(false) {
            data.insert("can_start".to_string(), to_json(&false));
            data.insert("grade_too_high".to_string(), to_json(&true));
        }
267
    }
268

269
270
271
272
273
274
275
276
277
278
279
    if let Some(start_date) = c.start {
        if time::get_time() < start_date {
            data.insert("can_start".to_string(), to_json(&false));

            let time_until = start_date - time::get_time();
            data.insert("time_until_d".to_string(), to_json(&(time_until.num_days())));
            data.insert("time_until_h".to_string(), to_json(&(time_until.num_hours() % 24)));
            data.insert("time_until_m".to_string(), to_json(&(time_until.num_minutes() % 60)));
        }
    }

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

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

292
293
294
295
296
297
298
299
300
301
302
        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
303

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

310
311
312
313
314
315
316
317
318
319
        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));
320
321
322
            if c.duration > 0 {
                data.insert("is_time_up".to_string(), to_json(&true));
            }
323
324
325
326
327
328
        } 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
329
            } else {
330
                data.insert("time_left".to_string(), to_json(&format!("{}:{}", left_min, left_sec)));
Robert Czechowski's avatar
Robert Czechowski committed
331
332
333
            }
        }
    }
334

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

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

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

347
348
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

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

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

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

358
359
360
            userresults.push(String::new());
            let mut summe = 0;

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

370
371
            userresults[0] = format!("{}", summe);

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

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

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
383
384

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

389
390
391
    Ok(("contestresults".to_owned(), data))
}

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

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

410
411
    // TODO: Check participant is in correct age group (not super important)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

702
    let group_code = helpers::make_group_code();
703
    // TODO: check for collisions
704

Robert Czechowski's avatar
Robert Czechowski committed
705
706
    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
707

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

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

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

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

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

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

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

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

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

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

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

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

771
772
773
    Ok(())
}

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

780
    let data = json_val::Map::new();
781
782
783

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

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

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

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

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

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

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

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

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

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

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

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

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

912
    let mut result = ProfileStatus::NothingChanged;
913

914
    let mut password_and_salt = None;
915

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

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

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

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

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

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

977
978
979
            user.firstname = Some(firstname);
            user.lastname = Some(lastname);
            user.grade = grade;
980
            user.sex = sex;
981

982
983
984
985
986
987
988
989
990
991
992
            if street.is_some() {
                user.street = street;
            }
            if zip.is_some() {
                user.zip = zip;
            }
            if city.is_some() {
                user.city = city;