core.rs 48.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::{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
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
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));
    }
52
53
54
    if session.is_admin() {
        data.insert("admin".to_string(), to_json(&true));
    }
55
56
57
58
59
    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));
60
61
62
    data.insert("parent".to_string(), to_json(&"base"));
}

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

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

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

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

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

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

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

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

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

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

174
175
176
177
178
179
180
181
182
183
184
185
    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));

186
187
188
189
190
191
192
193
194
    let now = time::get_time();
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
            .filter(|c| c.public)
            .filter(|c| c.end.map(|end| now <= end).unwrap_or(true) || visibility == ContestVisibility::All)
            .filter(|c| c.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|c| c.duration != 0 || visibility != ContestVisibility::Current)
            .filter(|c| c.requires_login.unwrap_or(false) || visibility != ContestVisibility::LoginRequired)
195
196
197
198
199
            .filter(|c| {
                !c.requires_login.unwrap_or(false)
                || visibility == ContestVisibility::LoginRequired
                || visibility == ContestVisibility::All
            })
200
201
202
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
            .collect();

Robert Czechowski's avatar
Robert Czechowski committed
203
    data.insert("contest".to_string(), to_json(&v));
204
205
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
206
                            ContestVisibility::Open => "Trainingsaufgaben",
207
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
208
                            ContestVisibility::LoginRequired => "Herausforderungen",
209
210
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
211
212
213
214

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

215
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
216
217
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
218
    for st in &tg.tasks {
219
220
221
222
223
224
        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;
        }

225
226
227
228
229
230
        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 };
231
232
233
234
235
236

        subtaskinfos.push(si);
    }
    subtaskinfos
}

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
263
264
265
266
267
268
#[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 }
}

269
270
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
271
272
    let session = conn.get_session_or_new(&session_token);

273
    let contest = conn.get_contest_by_id_complete(contest_id);
274
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
275

276
    let mut opt_part = conn.get_participation(&session_token, contest_id);
277

278
279
280
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
281
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
282
283
284
285

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

286
    let constraints = check_contest_constraints(&session, &contest);
287

288
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
289
    let has_duration = contest.duration > 0;
290

291
    data.insert("constraints".to_string(), to_json(&constraints));
292
    data.insert("has_duration".to_string(), to_json(&has_duration));
293
    data.insert("can_start".to_string(), to_json(&can_start));
294

295
296
297
298
    // 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() && (contest.duration == 0 || session.is_teacher) {
299
300
301
302
303
304
    if opt_part.is_none()
       && contest.duration == 0
       && constraints.contest_running
       && constraints.grade_matching
       && contest.requires_login != Some(true)
    {
305
306
307
308
        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() });
    }

309
    let now = time::get_time();
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
    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));
333
334
    }

335
336
337
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
338

339
        let mut tasks = Vec::new();
340
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
341
342
343
344
345
346
347
            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);
        }
348
        let relative_points = (totalgrade * 100) / max_totalgrade;
349
350

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

352
353
354
355
356
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
357
358
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
359
360
361
362
        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);
363

364
        data.insert("is_started".to_string(), to_json(&true));
365
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
366
367
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
368
369
370
        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));
371
372
373
374
375
376
        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
377
    }
378

379
380
381
382
383
384
    // 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));
385
386
    }

387
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
388
389
}

390
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
391
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
392
393
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
394

395
396
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

397
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
398
399

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

Daniel Brüning's avatar
Daniel Brüning committed
402
        //TODO: use user
403
        for (user, userdata) in groupdata {
404
405
            let mut userresults: Vec<String> = Vec::new();

406
407
408
            userresults.push(String::new());
            let mut summe = 0;

409
            for grade in userdata {
410
411
412
413
414
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
415
                }
416
417
            }

418
419
            userresults[0] = format!("{}", summe);

420
421
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
422
                               user.id,
423
                               grade_to_string(user.grade),
424
                               userresults))
425
        }
426

427
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
428
429
430
431
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
432
433

    let c = conn.get_contest_by_id(contest_id);
434
435
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };

436
    data.insert("contest".to_string(), to_json(&ci));
437
    data.insert("contestname".to_string(), to_json(&c.name));
438

439
440
441
    Ok(("contestresults".to_owned(), data))
}

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

448
    // Check logged in or open contest
449
    if contest.duration != 0 && !session.is_logged_in() {
450
        return Err(MedalError::AccessDenied);
451
    }
452

453
    // Check CSRF token
454
455
456
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
457

458
459
460
461
462
463
464
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

465
    // Start contest
466
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
467
        Ok(_) => Ok(()),
468
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
469
    }
Robert Czechowski's avatar
Robert Czechowski committed
470
471
}

472
473
474
475
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
476
477
    let (username, password) = login_data;

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

498
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
499
500
501
502
        }
    }
}

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

534
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
535
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
536
537
}

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

542
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
543
544
545
              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
546
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
547
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
548
549
550
    }
}

551
552
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
553
554
                                           -> MedalResult<String>
{
555
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
556
557

    if session.csrf_token != csrf_token {
558
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
559
560
    }

561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
    let (_, _, c) = conn.get_task_by_id_complete(task_id);

    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
        None => return Err(MedalError::AccessDenied),
        Some(participation) => {
            let now = time::get_time();
            let passed_secs = now.sec - participation.start.sec;
            if passed_secs < 0 {
                // behandle inkonsistente Serverzeit
            }

            let left_secs = i64::from(c.duration) * 60 - passed_secs;
            if c.duration > 0 && left_secs < 0 {
                return Err(MedalError::AccessDenied)
            // Contest over
            // TODO: Nicer message!
            }
        }
    }

Robert Czechowski's avatar
Robert Czechowski committed
581
582
583
584
585
586
587
588
589
590
    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() };
591

Robert Czechowski's avatar
Robert Czechowski committed
592
593
594
595
596
    conn.submit_submission(submission);

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

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

600
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
601
602
    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?
603

604
605
606
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
607

608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
    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);
        }
    }
623

624
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
625
        None => Err(MedalError::AccessDenied),
626
627
628
629
630
631
        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
632

633
            let mut data = json_val::Map::new();
634
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
635
636
637
            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
638

639
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
640
            if c.duration > 0 && left_secs < 0 {
641
                Err(MedalError::AccessDenied)
642
643
644
645
646
            // 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
647
648
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
649
650

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

652
                data.insert("contestname".to_string(), to_json(&c.name));
653
654
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
655
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
656
657
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
658
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
659

Robert Czechowski's avatar
Robert Czechowski committed
660
661
662
663
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

664
665
666
667
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
668
}
Robert Czechowski's avatar
Robert Czechowski committed
669

Robert Czechowski's avatar
Robert Czechowski committed
670
671
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
672
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
673
674
675
676
677
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
681
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
682
683

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

Robert Czechowski's avatar
Robert Czechowski committed
686
687
688
689
690
691
692
693
    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
694
    data.insert("group".to_string(), to_json(&v));
695
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
696

697
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
698
699
}

Robert Czechowski's avatar
Robert Czechowski committed
700
701
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
702
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
703
704
    pub firstname: String,
    pub lastname: String,
705
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
706
707
708
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
716
    if group.admin != session.id {
717
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
718
719
    }

Robert Czechowski's avatar
Robert Czechowski committed
720
721
722
723
724
725
726
727
728
729
730
    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()),
731
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
732
733
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
734
735
736

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
737
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
738
739
740

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

742
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
743
744
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
745

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

    if session.csrf_token != csrf_token {
751
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
752
753
    }

754
    let group_code = helpers::make_group_code();
755
    // TODO: check for collisions
756

Robert Czechowski's avatar
Robert Czechowski committed
757
758
    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
759

Robert Czechowski's avatar
Robert Czechowski committed
760
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
761
762
763

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

765
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
766
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
767
768
769
770
771
772
773

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

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

774
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
775
776
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
777
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
778
779
780
781
782

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

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

785
786
787
788
789
    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
790
791
792
793
794
795
796
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

797
798
    for line in v {
        if name != line[0] {
799
            if name != "" {
800
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
801
            }
802
803
804
805
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
806
807
808
809
810
811
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
812
813
814
815
816
817
818
819
820
821
822
        }

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

823
824
825
    Ok(())
}

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

832
    let data = json_val::Map::new();
833
834
835

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

837
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
838
839
840
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
841
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
842
843

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

846
847
    match user_id {
        None => {
848
849
850
851
852
            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));
853
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
854
855
856
857
858
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
859

860
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
861
            if session.password.is_some() {
862
                data.insert("profile_username".to_string(), to_json(&session.username));
863
864
            }
            if session.managed_by.is_none() {
865
                data.insert("profile_not_in_group".into(), to_json(&true));
866
            }
867
868
869
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
870
871
            data.insert("ownprofile".into(), to_json(&true));

872
            if let Some(query) = query_string {
873
                if query.starts_with("status=") {
874
                    let status: &str = &query[7..];
875
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
876
877
                        data.insert((status).to_string(), to_json(&true));
                    }
878
879
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
880
        }
881
882
883
884
885
886
887
888
        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);
            }

889
890
891
892
893
            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));
894
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
895
896
897
898
899
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
900

901
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
902
            if user.username.is_some() {
903
                data.insert("profile_username".to_string(), to_json(&user.username));
904
905
            }
            if user.managed_by.is_none() {
906
                data.insert("profile_not_in_group".into(), to_json(&true));
907
            }
908
909
910
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
911
            data.insert("ownprofile".into(), to_json(&false));
912

913
914
915
916
917
918
919
920
            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));
                    }
                }
            }
921
922
        }
    }
923

924
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
925
926
}

927
#[derive(Debug, PartialEq)]
928
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
929
930
931
932
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
933
934
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
935
    fn into(self) -> String { format!("{:?}", self) }
936
937
}

938
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
                                        (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
956
957
                                        -> MedalResult<ProfileStatus>
{
958
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
959
960

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

964
    let mut result = ProfileStatus::NothingChanged;
965

966
    let mut password_and_salt = None;
967

968
969
970
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
971
972
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
973

974
975
976
977
978
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
979
980
981
982
983
984
985
986
987
988
989
990
        }
    }

    if result == ProfileStatus::NothingChanged {
        if session.firstname.as_ref() == Some(&firstname)