core.rs 41.3 KB
Newer Older
1
2
use time;

Robert Czechowski's avatar
Robert Czechowski committed
3
use db_conn::MedalConnection;
4
use db_objects::OptionSession;
5
use db_objects::SessionUser;
6
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
7
8
9
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
10

11
12
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
13
    pub id: i32,
14
    pub linktext: String,
15
16
    pub active: bool,
    pub greyout: bool,
17
18
19
20
21
22
23
24
}

#[derive(Serialize, Deserialize)]
pub struct TaskInfo {
    pub name: String,
    pub subtasks: Vec<SubTaskInfo>,
}

Robert Czechowski's avatar
Robert Czechowski committed
25
26
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
27
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
28
    pub name: String,
29
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
30
    pub public: bool,
31
    pub login_required: bool,
Robert Czechowski's avatar
Robert Czechowski committed
32
33
}

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

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

49
50
51
52
53
54
55
56
57
58
59
60
fn fill_user_data(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>) {
    if session.is_logged_in() {
        data.insert("logged_in".to_string(), to_json(&true));
        data.insert("username".to_string(), to_json(&session.username));
        data.insert("firstname".to_string(), to_json(&session.firstname));
        data.insert("lastname".to_string(), to_json(&session.lastname));
        data.insert("teacher".to_string(), to_json(&session.is_teacher));
        data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
    }
    data.insert("parent".to_string(), to_json(&"base"));
}

61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
fn grade_to_string(grade: i32) -> String {
    match grade {
        0 => "Noch kein Schüler".to_string(),
        n @ 1..=10 => format!("{}", n),
        11 => "11 (G8)".to_string(),
        12 => "12 (G8)".to_string(),
        111 => "11 (G9)".to_string(),
        112 => "12 (G9)".to_string(),
        113 => "13 (G9)".to_string(),
        114 => "Berufsschule".to_string(),
        255 => "Kein Schüler mehr".to_string(),
        _ => "?".to_string(),
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
76
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
77
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
78
79
                                 -> (String, json_val::Map<String, json_val::Value>)
{
80
81
82
83
84
85
    let mut data = json_val::Map::new();

    //let mut contests = Vec::new();

    if let Some(token) = session_token {
        if let Some(session) = conn.get_session(&token) {
86
            fill_user_data(&session, &mut data);
87
88
89
        }
    }

90
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
91
92
93
94
95
96
97
98
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

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

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

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

108
109
110
111
112
113
114
pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
                                 -> (String, json_val::Map<String, json_val::Value>) {
    let mut data = json_val::Map::new();

    if let Some(token) = session_token {
        if let Some(session) = conn.get_session(&token) {
            data.insert("known_session".to_string(), to_json(&true));
115
            data.insert("session_id".to_string(), to_json(&session.id));
116
117
118
119
120
121
122
123
124
125
126
127
128
            data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
            if let Some(last_activity) = session.last_activity {
                data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
                data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
            }
            if session.is_alive() {
                data.insert("alive_session".to_string(), to_json(&true));
                if session.is_logged_in() {
                    data.insert("logged_in".to_string(), to_json(&true));
                    data.insert("username".to_string(), to_json(&session.username));
                    data.insert("firstname".to_string(), to_json(&session.firstname));
                    data.insert("lastname".to_string(), to_json(&session.lastname));
                    data.insert("teacher".to_string(), to_json(&session.is_teacher));
129
130
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
131
132
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
                }
            }
        }
        data.insert("session".to_string(), to_json(&token));
    } else {
        data.insert("session".to_string(), to_json(&"No session token given"));
    }

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

pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
    if let Some(token) = session_token {
        conn.get_session_or_new(&token);
    }
}

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

158
159
160
161
162
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str,
                                         (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>),
                                         visibility: ContestVisibility)
                                         -> MedalValue
{
Robert Czechowski's avatar
Robert Czechowski committed
163
164
    let mut data = json_val::Map::new();

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

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

172
173
174
175
176
177
178
179
180
181
182
183
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
    if let Some(oauth_providers) = oauth_providers {
        for oauth_provider in oauth_providers {
            oauth_links.push((oauth_provider.provider_id.to_owned(),
                              oauth_provider.login_link_text.to_owned(),
                              oauth_provider.url.to_owned()));
        }
    }

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

184
185
186
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
187
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public, login_required: c.requires_login.unwrap_or(false) })
188
189
190
            .filter(|ci| ci.public)
            .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
191
192
            .filter(|ci| ci.login_required || visibility != ContestVisibility::LoginRequired)
            .filter(|ci| !ci.login_required || visibility == ContestVisibility::LoginRequired)
193
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
194
    data.insert("contest".to_string(), to_json(&v));
195
196
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
197
                            ContestVisibility::Open => "Trainingsaufgaben",
198
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
199
                            ContestVisibility::LoginRequired => "Herausforderungen",
200
201
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
202
203
204
205

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

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

216
217
218
219
220
221
        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 };
222
223
224
225
226
227

        subtaskinfos.push(si);
    }
    subtaskinfos
}

228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#[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 }
}

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

264
    let contest = conn.get_contest_by_id_complete(contest_id);
265
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
266

267
    let mut opt_part = conn.get_participation(&session_token, contest_id);
268

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

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

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

288
    let constraints = check_contest_constraints(&session, &contest);
289

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

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

297
    let now = time::get_time();
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    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));
321
322
    }

323
324
325
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
326

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

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

340
341
342
343
344
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
345
346
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
347
348
349
350
        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);
351

352
        data.insert("is_started".to_string(), to_json(&true));
353
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
354
355
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
356
357
358
        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));
359
360
361
362
363
364
        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
365
    }
366

367
368
369
370
371
372
    // 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));
373
374
    }

375
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
376
377
}

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

383
384
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

385
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
386
387

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

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

394
395
396
            userresults.push(String::new());
            let mut summe = 0;

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

406
407
            userresults[0] = format!("{}", summe);

408
409
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
410
                               user.id,
411
                               grade_to_string(user.grade),
412
                               userresults))
413
        }
414

415
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
416
417
418
419
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
420
421

    let c = conn.get_contest_by_id(contest_id);
422
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public,  login_required: c.requires_login.unwrap_or(false)};
423
    data.insert("contest".to_string(), to_json(&ci));
424
    data.insert("contestname".to_string(), to_json(&c.name));
425

426
427
428
    Ok(("contestresults".to_owned(), data))
}

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

435
    // Check logged in or open contest
436
    if contest.duration != 0 && !session.is_logged_in() {
437
        return Err(MedalError::AccessDenied);
438
    }
439

440
    // Check CSRF token
441
442
443
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
444

445
446
447
448
449
450
451
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

452
    // Start contest
453
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
454
        Ok(_) => Ok(()),
455
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
456
    }
Robert Czechowski's avatar
Robert Czechowski committed
457
458
}

459
460
461
462
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
463
464
    let (username, password) = login_data;

465
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
466
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
467
468
        Err(()) => {
            let mut data = json_val::Map::new();
469
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
470
            data.insert("username".to_string(), to_json(&username));
471
472
473
474
475
476
477
478
479
480
481
482
483
484
            data.insert("parent".to_string(), to_json(&"base"));

            let mut oauth_links: Vec<(String, String, String)> = Vec::new();
            if let Some(oauth_providers) = oauth_providers {
                for oauth_provider in oauth_providers {
                    oauth_links.push((oauth_provider.provider_id.to_owned(),
                                      oauth_provider.login_link_text.to_owned(),
                                      oauth_provider.url.to_owned()));
                }
            }

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

485
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
486
487
488
489
        }
    }
}

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

521
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
522
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
523
524
}

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

529
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
530
531
532
              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
533
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
534
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
535
536
537
    }
}

538
539
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
540
541
                                           -> MedalResult<String>
{
542
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
543
544

    if session.csrf_token != csrf_token {
545
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
546
547
    }

Robert Czechowski's avatar
Robert Czechowski committed
548
549
550
551
552
553
554
555
556
557
    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() };
558

Robert Czechowski's avatar
Robert Czechowski committed
559
560
561
562
563
    conn.submit_submission(submission);

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

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

567
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
568
569
    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?
570

571
572
573
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
574

575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
    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);
        }
    }
590

591
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
592
        None => Err(MedalError::AccessDenied),
593
594
595
596
597
598
        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
599

600
            let mut data = json_val::Map::new();
601
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
602
603
604
            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
605

606
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
607
            if c.duration > 0 && left_secs < 0 {
608
                Err(MedalError::AccessDenied)
609
610
611
612
613
            // 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
614
615
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
616
617

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

619
                data.insert("contestname".to_string(), to_json(&c.name));
620
621
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
622
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
623
624
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
625
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
626

Robert Czechowski's avatar
Robert Czechowski committed
627
628
629
630
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

631
632
633
634
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
635
}
Robert Czechowski's avatar
Robert Czechowski committed
636

Robert Czechowski's avatar
Robert Czechowski committed
637
638
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
639
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
640
641
642
643
644
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
648
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
649
650

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

Robert Czechowski's avatar
Robert Czechowski committed
653
654
655
656
657
658
659
660
    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
661
    data.insert("group".to_string(), to_json(&v));
662
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
663

664
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
665
666
}

Robert Czechowski's avatar
Robert Czechowski committed
667
668
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
669
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
670
671
    pub firstname: String,
    pub lastname: String,
672
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
673
674
675
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
683
    if group.admin != session.id {
684
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
685
686
    }

Robert Czechowski's avatar
Robert Czechowski committed
687
688
689
690
691
692
693
694
695
696
697
    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()),
698
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
699
700
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
701
702
703

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
704
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
705
706
707

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

709
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
710
711
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
712

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

    if session.csrf_token != csrf_token {
718
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
719
720
    }

721
    let group_code = helpers::make_group_code();
722
    // TODO: check for collisions
723

Robert Czechowski's avatar
Robert Czechowski committed
724
725
    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
726

Robert Czechowski's avatar
Robert Czechowski committed
727
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
728
729
730

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

732
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
733
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
734
735
736
737
738
739
740

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

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

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

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

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

752
753
754
755
756
    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
757
758
759
760
761
762
763
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

764
765
    for line in v {
        if name != line[0] {
766
            if name != "" {
767
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
768
            }
769
770
771
772
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
773
774
775
776
777
778
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
779
780
781
782
783
784
785
786
787
788
789
        }

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

790
791
792
    Ok(())
}

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

799
    let data = json_val::Map::new();
800
801
802

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

804
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
805
806
807
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
808
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
809
810

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

813
814
    match user_id {
        None => {
815
816
817
818
819
            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));
820
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
821
822
823
824
825
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
826

827
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
828
            if session.password.is_some() {
829
                data.insert("profile_username".to_string(), to_json(&session.username));
830
831
            }
            if session.managed_by.is_none() {
832
                data.insert("profile_not_in_group".into(), to_json(&true));
833
            }
834
835
836
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
837
838
            data.insert("ownprofile".into(), to_json(&true));

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

856
857
858
859
860
            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));
861
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
862
863
864
865
866
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
867

868
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
869
            if user.username.is_some() {
870
                data.insert("profile_username".to_string(), to_json(&user.username));
871
872
            }
            if user.managed_by.is_none() {
873
                data.insert("profile_not_in_group".into(), to_json(&true));
874
            }
875
876
877
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
878
            data.insert("ownprofile".into(), to_json(&false));
879

880
881
882
883
884
885
886
887
            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));
                    }
                }
            }
888
889
        }
    }
890

891
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
892
893
}

894
#[derive(Debug, PartialEq)]
895
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
896
897
898
899
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
900
901
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
902
    fn into(self) -> String { format!("{:?}", self) }
903
904
}

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

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

931
    let mut result = ProfileStatus::NothingChanged;
932

933
    let mut password_and_salt = None;
934

935
936
937
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
938
939
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
940

941
942
943
944
945
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
946
947
948
949
950
951
952
953
954
955
956
957
        }
    }

    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
        {
958
            return Ok(ProfileStatus::NothingChanged);
959
960
        } else {
            result = ProfileStatus::DataChanged;