core.rs 47.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
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
    }

Robert Czechowski's avatar
Robert Czechowski committed
561
562
563
564
565
566
567
568
569
570
    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() };
571

Robert Czechowski's avatar
Robert Czechowski committed
572
573
574
575
576
    conn.submit_submission(submission);

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

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

580
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
581
582
    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?
583

584
585
586
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
587

588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
    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);
        }
    }
603

604
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
605
        None => Err(MedalError::AccessDenied),
606
607
608
609
610
611
        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
612

613
            let mut data = json_val::Map::new();
614
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
615
616
617
            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
618

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

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

632
                data.insert("contestname".to_string(), to_json(&c.name));
633
634
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
635
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
636
637
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
638
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
639

Robert Czechowski's avatar
Robert Czechowski committed
640
641
642
643
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

644
645
646
647
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
648
}
Robert Czechowski's avatar
Robert Czechowski committed
649

Robert Czechowski's avatar
Robert Czechowski committed
650
651
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
652
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
653
654
655
656
657
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
661
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
662
663

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

Robert Czechowski's avatar
Robert Czechowski committed
666
667
668
669
670
671
672
673
    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
674
    data.insert("group".to_string(), to_json(&v));
675
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
676

677
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
678
679
}

Robert Czechowski's avatar
Robert Czechowski committed
680
681
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
682
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
683
684
    pub firstname: String,
    pub lastname: String,
685
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
686
687
688
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
696
    if group.admin != session.id {
697
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
698
699
    }

Robert Czechowski's avatar
Robert Czechowski committed
700
701
702
703
704
705
706
707
708
709
710
    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()),
711
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
712
713
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
714
715
716

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
717
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
718
719
720

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

722
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
723
724
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
725

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

    if session.csrf_token != csrf_token {
731
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
732
733
    }

734
    let group_code = helpers::make_group_code();
735
    // TODO: check for collisions
736

Robert Czechowski's avatar
Robert Czechowski committed
737
738
    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
739

Robert Czechowski's avatar
Robert Czechowski committed
740
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
741
742
743

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

745
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
746
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
747
748
749
750
751
752
753

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

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

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

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

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

765
766
767
768
769
    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
770
771
772
773
774
775
776
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

777
778
    for line in v {
        if name != line[0] {
779
            if name != "" {
780
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
781
            }
782
783
784
785
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
786
787
788
789
790
791
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
792
793
794
795
796
797
798
799
800
801
802
        }

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

803
804
805
    Ok(())
}

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

812
    let data = json_val::Map::new();
813
814
815

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

817
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
818
819
820
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
821
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
822
823

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

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

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

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

869
870
871
872
873
            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));
874
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
875
876
877
878
879
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
880

881
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
882
            if user.username.is_some() {
883
                data.insert("profile_username".to_string(), to_json(&user.username));
884
885
            }
            if user.managed_by.is_none() {
886
                data.insert("profile_not_in_group".into(), to_json(&true));
887
            }
888
889
890
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
891
            data.insert("ownprofile".into(), to_json(&false));
892

893
894
895
896
897
898
899
900
            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));
                    }
                }
            }
901
902
        }
    }
903

904
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
905
906
}

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

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

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

944
    let mut result = ProfileStatus::NothingChanged;
945

946
    let mut password_and_salt = None;
947

948
949
950
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
951
952
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
953

954
955
956
957
958
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
959
960
961
962
963
964
965
966
967
968
969
970
        }
    }

    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
        {
971
            return Ok(ProfileStatus::NothingChanged);
972
973
        } else {
            result = ProfileStatus::DataChanged;
974
        }