core.rs 41.9 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::{Contest, 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
    pub public: bool,
31
    pub login_required: bool,
Robert Czechowski's avatar
Robert Czechowski committed
32
33
}

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

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

49
50
51
52
53
54
55
56
57
58
59
60
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"));
}

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
fn grade_to_string(grade: i32) -> String {
    match 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
76
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
77
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
78
79
                                 -> (String, json_val::Map<String, json_val::Value>)
{
80
81
82
83
84
85
    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) {
86
            fill_user_data(&session, &mut data);
87
88
89
        }
    }

90
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
91
92
93
94
95
96
97
98
    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()));
        }
    }

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

102
    data.insert("parent".to_string(), to_json(&"base"));
103
104
105
    ("index".to_owned(), data)
}

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

108
109
110
111
112
113
114
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));
115
            data.insert("session_id".to_string(), to_json(&session.id));
116
117
118
119
120
121
122
123
124
125
126
127
128
            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));
129
130
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
131
132
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
                }
            }
        }
        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);
    }
}

150
151
152
153
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
154
    Current,
155
    LoginRequired,
156
157
}

158
159
160
161
162
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
163
164
    let mut data = json_val::Map::new();

165
    let session = conn.get_session_or_new(&session_token);
166
    fill_user_data(&session, &mut data);
167

168
169
170
171
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

172
173
174
175
176
177
178
179
180
181
182
183
    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
184
185
186
187
188
189
190
191
192
193
194
195
196
    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,
                                                         login_required: c.requires_login.unwrap_or(false) })
                                  .filter(|ci| ci.public || visibility == ContestVisibility::All)
                                  .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
                                  .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
                                  .filter(|ci| ci.login_required || visibility != ContestVisibility::LoginRequired)
                                  .filter(|ci| !ci.login_required || visibility == ContestVisibility::LoginRequired)
                                  .collect();
Robert Czechowski's avatar
Robert Czechowski committed
197
    data.insert("contest".to_string(), to_json(&v));
198
199
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
200
                            ContestVisibility::Open => "Trainingsaufgaben",
201
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
202
                            ContestVisibility::LoginRequired => "Herausforderungen",
203
204
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
205
206
207
208

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

209
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
210
211
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
212
    for st in &tg.tasks {
213
214
215
216
217
218
        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;
        }

219
220
221
222
223
224
        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 };
225
226
227
228
229
230

        subtaskinfos.push(si);
    }
    subtaskinfos
}

231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#[derive(Serialize, Deserialize)]
pub struct ContestStartConstraints {
    pub contest_not_begun: bool,
    pub contest_over: bool,
    pub contest_running: bool,
    pub grade_too_low: bool,
    pub grade_too_high: bool,
    pub grade_matching: bool,
}

fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> ContestStartConstraints {
    let now = time::get_time();
    let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };

    let contest_not_begun = contest.start.map(|start| now < start).unwrap_or(false);
    let contest_over = contest.end.map(|end| now > end).unwrap_or(false);
    let grade_too_low =
        contest.min_grade.map(|min_grade| student_grade < min_grade && !session.is_teacher).unwrap_or(false);
    let grade_too_high =
        contest.max_grade.map(|max_grade| student_grade > max_grade && !session.is_teacher).unwrap_or(false);

    let contest_running = !contest_not_begun && !contest_over;
    let grade_matching = !grade_too_low && !grade_too_high;

    ContestStartConstraints { contest_not_begun,
                              contest_over,
                              contest_running,
                              grade_too_low,
                              grade_too_high,
                              grade_matching }
}

263
264
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
265
266
    let session = conn.get_session_or_new(&session_token);

267
    let contest = conn.get_contest_by_id_complete(contest_id);
268
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
269

270
    let mut opt_part = conn.get_participation(&session_token, contest_id);
271

272
273
274
    // 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?
275
    // if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
276
    // TODO: Should autostart only happen in the contest time?
277
    if opt_part.is_none() && contest.duration == 0 {
278
279
        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() });
280
    }
281

282
283
284
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
285
                           public: contest.public,
Robert Czechowski's avatar
Robert Czechowski committed
286
                           login_required: contest.requires_login.unwrap_or(false) };
Robert Czechowski's avatar
Robert Czechowski committed
287
288
289
290

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

291
    let constraints = check_contest_constraints(&session, &contest);
292

293
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
294
    let has_duration = contest.duration > 0;
295

296
    data.insert("constraints".to_string(), to_json(&constraints));
297
    data.insert("has_duration".to_string(), to_json(&has_duration));
298
    data.insert("can_start".to_string(), to_json(&can_start));
299

300
    let now = time::get_time();
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
    if let Some(start) = contest.start {
        if now < start {
            let until = start - now;
            data.insert("time_until_start".to_string(),
                        to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
        }
    }

    if let Some(end) = contest.end {
        if now < end {
            let until = end - now;
            data.insert("time_until_end".to_string(),
                        to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
        }
    }

    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));
324
325
    }

326
327
328
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
329

330
        let mut tasks = Vec::new();
331
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
332
333
334
335
336
337
338
            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);
        }
339
        let relative_points = (totalgrade * 100) / max_totalgrade;
340
341

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

343
344
345
346
347
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
348
349
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
350
351
352
353
        let is_time_up = !is_time_left;
        let left_min = left_secs / 60;
        let left_sec = left_secs % 60;
        let time_left = format!("{}:{:02}", left_min, left_sec);
354

355
        data.insert("is_started".to_string(), to_json(&true));
356
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
357
358
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
359
360
361
        data.insert("relative_points".to_string(), to_json(&relative_points));
        data.insert("is_time_left".to_string(), to_json(&is_time_left));
        data.insert("is_time_up".to_string(), to_json(&is_time_up));
362
363
364
365
366
367
        if is_time_left {
            data.insert("left_min".to_string(), to_json(&left_min));
            data.insert("left_sec".to_string(), to_json(&left_sec));
            data.insert("time_left".to_string(), to_json(&time_left));
            data.insert("seconds_left".to_string(), to_json(&left_secs));
        }
Robert Czechowski's avatar
Robert Czechowski committed
368
    }
369

370
371
372
373
374
375
    // This only checks if a query string is existent, so any query string will
    // lead to the assumption that a bare page is requested. This is useful to
    // disable caching (via random token) but should be changed if query string
    // can obtain more than only this meaning in the future
    if query_string.is_none() {
        data.insert("not_bare".to_string(), to_json(&true));
376
377
    }

378
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
379
380
}

381
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
382
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
383
384
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
385

386
387
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

388
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
389
390

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

Daniel Brüning's avatar
Daniel Brüning committed
393
        //TODO: use user
394
        for (user, userdata) in groupdata {
395
396
            let mut userresults: Vec<String> = Vec::new();

397
398
399
            userresults.push(String::new());
            let mut summe = 0;

400
            for grade in userdata {
401
402
403
404
405
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
406
                }
407
408
            }

409
410
            userresults[0] = format!("{}", summe);

411
412
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
413
                               user.id,
414
                               grade_to_string(user.grade),
415
                               userresults))
416
        }
417

418
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
419
420
421
422
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
423
424

    let c = conn.get_contest_by_id(contest_id);
Robert Czechowski's avatar
Robert Czechowski committed
425
426
427
428
429
    let ci = ContestInfo { id: c.id.unwrap(),
                           name: c.name.clone(),
                           duration: c.duration,
                           public: c.public,
                           login_required: c.requires_login.unwrap_or(false) };
430
    data.insert("contest".to_string(), to_json(&ci));
431
    data.insert("contestname".to_string(), to_json(&c.name));
432

433
434
435
    Ok(("contestresults".to_owned(), data))
}

436
pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str)
Robert Czechowski's avatar
Robert Czechowski committed
437
                                         -> MedalResult<()> {
438
    // TODO: Is _or_new the right semantic? We need a CSRF token anyway …
439
    let session = conn.get_session_or_new(&session_token);
440
    let contest = conn.get_contest_by_id(contest_id);
441

442
    // Check logged in or open contest
443
    if contest.duration != 0 && !session.is_logged_in() {
444
        return Err(MedalError::AccessDenied);
445
    }
446

447
    // Check CSRF token
448
449
450
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
451

452
453
454
455
456
457
458
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

    if !(constraints.contest_running && constraints.grade_matching) {
        return Err(MedalError::AccessDenied);
    }

459
    // Start contest
460
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
461
        Ok(_) => Ok(()),
462
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
463
    }
Robert Czechowski's avatar
Robert Czechowski committed
464
465
}

466
467
468
469
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
470
471
    let (username, password) = login_data;

472
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
473
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
474
475
        Err(()) => {
            let mut data = json_val::Map::new();
476
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
477
            data.insert("username".to_string(), to_json(&username));
478
479
480
481
482
483
484
485
486
487
488
489
490
491
            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));

492
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
493
494
495
496
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
497
pub fn login_with_code<T: MedalConnection>(
498
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
499
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
500
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
501
502
503
504
505
506
507
        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));
508
509
510
511
512
513
514
515
516
517
518
519
520
521
                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
522
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
523
            }
Robert Czechowski's avatar
Robert Czechowski committed
524
        },
Robert Czechowski's avatar
Robert Czechowski committed
525
526
527
    }
}

528
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
529
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
530
531
}

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

536
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
537
538
539
              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
540
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
541
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
542
543
544
    }
}

545
546
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
547
548
                                           -> MedalResult<String>
{
549
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
550
551

    if session.csrf_token != csrf_token {
552
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
553
554
    }

Robert Czechowski's avatar
Robert Czechowski committed
555
556
557
558
559
560
561
562
563
564
    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() };
565

Robert Czechowski's avatar
Robert Czechowski committed
566
567
568
569
570
    conn.submit_submission(submission);

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

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

574
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
575
576
    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?
577

578
579
580
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
581

582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
    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);
        }
    }
597

598
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
599
        None => Err(MedalError::AccessDenied),
600
601
602
603
604
605
        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
606

607
            let mut data = json_val::Map::new();
608
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
609
610
611
            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
612

613
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
614
            if c.duration > 0 && left_secs < 0 {
615
                Err(MedalError::AccessDenied)
616
617
618
619
620
            // 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
621
622
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
623
624

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

626
                data.insert("contestname".to_string(), to_json(&c.name));
627
628
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
629
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
630
631
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
632
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
633

Robert Czechowski's avatar
Robert Czechowski committed
634
635
636
637
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

638
639
640
641
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
642
}
Robert Czechowski's avatar
Robert Czechowski committed
643

Robert Czechowski's avatar
Robert Czechowski committed
644
645
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
646
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
647
648
649
650
651
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
655
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
656
657

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

Robert Czechowski's avatar
Robert Czechowski committed
660
661
662
663
664
665
666
667
    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
668
    data.insert("group".to_string(), to_json(&v));
669
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
670

671
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
672
673
}

Robert Czechowski's avatar
Robert Czechowski committed
674
675
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
676
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
677
678
    pub firstname: String,
    pub lastname: String,
679
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
680
681
682
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
690
    if group.admin != session.id {
691
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
692
693
    }

Robert Czechowski's avatar
Robert Czechowski committed
694
695
696
697
698
699
700
701
702
703
704
    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()),
705
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
706
707
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
708
709
710

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
711
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
712
713
714

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

716
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
717
718
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
719

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

    if session.csrf_token != csrf_token {
725
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
726
727
    }

728
    let group_code = helpers::make_group_code();
729
    // TODO: check for collisions
730

Robert Czechowski's avatar
Robert Czechowski committed
731
732
    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
733

Robert Czechowski's avatar
Robert Czechowski committed
734
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
735
736
737

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

739
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
740
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
741
742
743
744
745
746
747

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

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

748
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
749
750
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
751
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
752
753
754
755
756

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

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

759
760
761
762
763
    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
764
765
766
767
768
769
770
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

771
772
    for line in v {
        if name != line[0] {
773
            if name != "" {
774
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
775
            }
776
777
778
779
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
780
781
782
783
784
785
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
786
787
788
789
790
791
792
793
794
795
796
        }

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

797
798
799
    Ok(())
}

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

806
    let data = json_val::Map::new();
807
808
809

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

811
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
812
813
814
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
815
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
816
817

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

820
821
    match user_id {
        None => {
822
823
824
825
826
            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));
827
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
828
829
830
831
832
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
833

834
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
835
            if session.password.is_some() {
836
                data.insert("profile_username".to_string(), to_json(&session.username));
837
838
            }
            if session.managed_by.is_none() {
839
                data.insert("profile_not_in_group".into(), to_json(&true));
840
            }
841
842
843
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
844
845
            data.insert("ownprofile".into(), to_json(&true));

846
            if let Some(query) = query_string {
847
                if query.starts_with("status=") {
848
                    let status: &str = &query[7..];
849
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
850
851
                        data.insert((status).to_string(), to_json(&true));
                    }
852
853
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
854
        }
855
856
857
858
859
860
861
862
        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);
            }

863
864
865
866
867
            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));
868
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
869
870
871
872
873
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
874

875
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
876
            if user.username.is_some() {
877
                data.insert("profile_username".to_string(), to_json(&user.username));
878
879
            }
            if user.managed_by.is_none() {
880
                data.insert("profile_not_in_group".into(), to_json(&true));
881
            }
882
883
884
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
885
            data.insert("ownprofile".into(), to_json(&false));
886

887
888
889
890
891
892
893
894
            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));
                    }
                }
            }
895
896
        }
    }
897

898
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
899
900
}

901
#[derive(Debug, PartialEq)]
902
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
903
904
905
906
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
907
908
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
909
    fn into(self) -> String { format!("{:?}", self) }
910
911
}

912
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
                                        (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
930
931
                                        -> MedalResult<ProfileStatus>
{
932
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
933
934

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

938
    let mut result = ProfileStatus::NothingChanged;
939

940
    let mut password_and_salt = None;
941

942
943
944
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
945
946
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
947

948
949
950
951
952
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
953
954
955
956
957
958
959
960
961
962
963
964
        }
    }

    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
        {
965
            return Ok(ProfileStatus::NothingChanged);
966
967
        } else {
            result = ProfileStatus::DataChanged;
968
        }