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
31
32
    pub public: bool,
}

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

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

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

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

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

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

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

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

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

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

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

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

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

171
172
173
174
175
176
177
178
179
180
181
182
    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));

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

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

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

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

        subtaskinfos.push(si);
    }
    subtaskinfos
}

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
260
261
#[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 }
}

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

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

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

271
272
273
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
274
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
275
276
277
278

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

279
    let constraints = check_contest_constraints(&session, &contest);
280

281
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
282
    let has_duration = contest.duration > 0;
283

284
    data.insert("constraints".to_string(), to_json(&constraints));
285
    data.insert("has_duration".to_string(), to_json(&has_duration));
286
    data.insert("can_start".to_string(), to_json(&can_start));
287

288
289
290
291
292
293
294
295
296
    // 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) {
    if opt_part.is_none() && contest.duration == 0 && constraints.contest_running && constraints.grade_matching {
        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() });
    }

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
423
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };

424
    data.insert("contest".to_string(), to_json(&ci));
425
    data.insert("contestname".to_string(), to_json(&c.name));
426

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

791
792
793
    Ok(())
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

932
    let mut result = ProfileStatus::NothingChanged;
933

934
    let mut password_and_salt = None;
935

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

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

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

965
966
967
968
969
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
970
            session.sex = sex;
971

972
973
974
975
976
977
978
979
980
981
982
            if street