core.rs 57.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*  medal                                                                                                            *\
 *  Copyright (C) 2020  Bundesweite Informatikwettbewerbe                                                            *
 *                                                                                                                   *
 *  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero        *
 *  General Public License as published  by the Free Software Foundation, either version 3 of the License, or (at    *
 *  your option) any later version.                                                                                  *
 *                                                                                                                   *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the       *
 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public      *
 *  License for more details.                                                                                        *
 *                                                                                                                   *
 *  You should have received a copy of the GNU Affero General Public License along with this program.  If not, see   *
\*  <http://www.gnu.org/licenses/>.                                                                                  */

15
16
use time;

17
18
19
use db_conn::MedalConnection;
#[cfg(feature = "signup")]
use db_conn::SignupResult;
20
use db_objects::OptionSession;
21
use db_objects::SessionUser;
22
use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
23
24
25
use helpers;
use oauth_provider::OauthProvider;
use webfw_iron::{json_val, to_json};
26

27
28
#[derive(Serialize, Deserialize)]
pub struct SubTaskInfo {
29
    pub id: i32,
30
    pub linktext: String,
31
32
    pub active: bool,
    pub greyout: bool,
33
34
35
36
37
38
39
40
}

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

Robert Czechowski's avatar
Robert Czechowski committed
41
42
#[derive(Serialize, Deserialize)]
pub struct ContestInfo {
43
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
44
    pub name: String,
45
    pub duration: i32,
Robert Czechowski's avatar
Robert Czechowski committed
46
47
48
    pub public: bool,
}

49
50
#[derive(Clone)]
pub enum MedalError {
51
52
    NotLoggedIn,
    AccessDenied,
53
54
55
    CsrfCheckFailed,
    SessionTimeout,
    DatabaseError,
56
    PasswordHashingError,
57
    UnmatchedPasswords,
58
    NotFound,
59
}
60

61
62
63
64
type MedalValue = (String, json_val::Map<String, json_val::Value>);
type MedalResult<T> = Result<T, MedalError>;
type MedalValueResult = MedalResult<MedalValue>;

65
66
67
68
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));
    }
69
70
71
    if session.is_admin() {
        data.insert("admin".to_string(), to_json(&true));
    }
72
73
74
75
76
    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));
77
    data.insert("parent".to_string(), to_json(&"base"));
78
79

    data.insert("medal_version".to_string(), to_json(&env!("CARGO_PKG_VERSION")));
80
81
}

82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
fn fill_oauth_data((self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>),
                   data: &mut json_val::Map<String, serde_json::Value>)
{
    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));
}

98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
113
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
114
                                 oauth_infos: (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
115
116
                                 -> (String, json_val::Map<String, json_val::Value>)
{
117
118
119
120
121
122
    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) {
123
            fill_user_data(&session, &mut data);
124
125
126
        }
    }

127
    fill_oauth_data(oauth_infos, &mut data);
128

129
    data.insert("parent".to_string(), to_json(&"base"));
130
131
132
    ("index".to_owned(), data)
}

133
pub fn status<T: MedalConnection>(conn: &T, _: ()) -> String { conn.get_debug_information() }
134

135
136
137
138
139
140
141
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));
142
            data.insert("session_id".to_string(), to_json(&session.id));
143
144
145
146
147
148
149
150
151
152
153
154
155
            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));
156
157
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
158
159
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
                }
            }
        }
        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);
    }
}

177
178
179
180
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
181
    Current,
182
    LoginRequired,
183
184
}

185
pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str,
186
                                         oauth_infos: (Option<String>, Option<Vec<OauthProvider>>),
187
188
189
                                         visibility: ContestVisibility)
                                         -> MedalValue
{
Robert Czechowski's avatar
Robert Czechowski committed
190
191
    let mut data = json_val::Map::new();

192
    let session = conn.get_session_or_new(&session_token);
193
    fill_user_data(&session, &mut data);
194

195
196
197
198
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

199
    fill_oauth_data(oauth_infos, &mut data);
200

201
202
203
204
205
206
207
208
209
    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)
210
211
212
213
214
            .filter(|c| {
                !c.requires_login.unwrap_or(false)
                || visibility == ContestVisibility::LoginRequired
                || visibility == ContestVisibility::All
            })
215
216
217
            .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
218
    data.insert("contest".to_string(), to_json(&v));
219
220
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
221
                            ContestVisibility::Open => "Trainingsaufgaben",
222
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
223
                            ContestVisibility::LoginRequired => "Herausforderungen",
224
225
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
226
227
228
229

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

230
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
231
232
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
233
    for st in &tg.tasks {
234
235
236
237
238
239
        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;
        }

240
241
242
243
244
        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));
Robert Czechowski's avatar
Robert Czechowski committed
245
        let si = SubTaskInfo { id: st.id.unwrap(), linktext, active, greyout };
246
247
248
249
250
251

        subtaskinfos.push(si);
    }
    subtaskinfos
}

252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#[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 }
}

284
285
286
287
288
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str,
                                        query_string: Option<String>,
                                        oauth_infos: (Option<String>, Option<Vec<OauthProvider>>))
                                        -> MedalValueResult
{
289
290
    let session = conn.get_session_or_new(&session_token);

291
    let contest = conn.get_contest_by_id_complete(contest_id);
292
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
293

294
    let mut opt_part = conn.get_participation(&session_token, contest_id);
295

296
297
298
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
299
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
300
301

    let mut data = json_val::Map::new();
302
303
    data.insert("parent".to_string(), to_json(&"base"));
    data.insert("empty".to_string(), to_json(&"empty"));
Robert Czechowski's avatar
Robert Czechowski committed
304
    data.insert("contest".to_string(), to_json(&ci));
305
    fill_oauth_data(oauth_infos, &mut data);
Robert Czechowski's avatar
Robert Czechowski committed
306

307
    let constraints = check_contest_constraints(&session, &contest);
308

309
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
310
    let has_duration = contest.duration > 0;
311

312
    data.insert("constraints".to_string(), to_json(&constraints));
313
    data.insert("has_duration".to_string(), to_json(&has_duration));
314
    data.insert("can_start".to_string(), to_json(&can_start));
315

316
317
318
319
    // 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) {
320
321
322
323
324
325
    if opt_part.is_none()
       && contest.duration == 0
       && constraints.contest_running
       && constraints.grade_matching
       && contest.requires_login != Some(true)
    {
326
327
328
329
        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() });
    }

330
    let now = time::get_time();
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
    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));
354
355
    }

356
357
358
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
359

360
        let mut tasks = Vec::new();
361
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
362
363
364
365
366
367
368
            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);
        }
369
        let relative_points = (totalgrade * 100) / max_totalgrade;
370
371

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

373
374
375
376
377
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
378
379
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
380
381
382
383
        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);
384

385
        data.insert("is_started".to_string(), to_json(&true));
386
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
387
388
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
389
390
391
        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));
392
393
394
395
396
397
        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
398
    }
399

400
401
402
403
404
405
    // 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));
406
407
    }

408
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
409
410
}

411
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
412
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
413
414
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
415

416
417
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

418
    let mut results: Vec<(String, i32, Vec<(String, String, i32, String, Vec<String>)>)> = Vec::new();
419
420

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

Daniel Brüning's avatar
Daniel Brüning committed
423
        //TODO: use user
424
        for (user, userdata) in groupdata {
425
426
            let mut userresults: Vec<String> = Vec::new();

427
428
429
            userresults.push(String::new());
            let mut summe = 0;

430
            for grade in userdata {
431
432
433
434
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
Robert Czechowski's avatar
Robert Czechowski committed
435
                    userresults.push("–".to_string());
436
                }
437
438
            }

439
440
            userresults[0] = format!("{}", summe);

441
442
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
443
                               user.id,
444
                               grade_to_string(user.grade),
445
                               userresults))
446
        }
447

Robert Czechowski's avatar
Robert Czechowski committed
448
        results.push((group.name.to_string(), group.id.unwrap_or(0), groupresults));
449
450
451
452
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
453
454

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

457
    data.insert("contest".to_string(), to_json(&ci));
458
    data.insert("contestname".to_string(), to_json(&c.name));
459

460
461
462
    Ok(("contestresults".to_owned(), data))
}

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

469
    // Check logged in or open contest
470
    if contest.duration != 0 && !session.is_logged_in() {
471
        return Err(MedalError::AccessDenied);
472
    }
473

474
    // Check CSRF token
475
476
477
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
478

479
480
481
482
483
484
485
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

486
    // Start contest
487
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
488
        Ok(_) => Ok(()),
489
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
490
    }
Robert Czechowski's avatar
Robert Czechowski committed
491
492
}

493
pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String),
494
                                 oauth_infos: (Option<String>, Option<Vec<OauthProvider>>))
495
496
                                 -> Result<String, MedalValue>
{
Robert Czechowski's avatar
Robert Czechowski committed
497
498
    let (username, password) = login_data;

499
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
500
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
501
502
        Err(()) => {
            let mut data = json_val::Map::new();
503
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
504
            data.insert("username".to_string(), to_json(&username));
505
506
            data.insert("parent".to_string(), to_json(&"base"));

507
            fill_oauth_data(oauth_infos, &mut data);
508

509
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
510
511
512
513
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
514
pub fn login_with_code<T: MedalConnection>(
515
    conn: &T, code: &str, oauth_infos: (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
516
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
517
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
518
519
520
521
522
523
524
        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));
525
526
                data.insert("parent".to_string(), to_json(&"base"));

527
                fill_oauth_data(oauth_infos, &mut data);
528

Robert Czechowski's avatar
Robert Czechowski committed
529
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
530
            }
Robert Czechowski's avatar
Robert Czechowski committed
531
        },
Robert Czechowski's avatar
Robert Czechowski committed
532
533
534
    }
}

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

539
#[cfg(feature = "signup")]
540
541
542
543
544
545
546
547
548
549
550
551
552
553
pub fn signup<T: MedalConnection>(conn: &T, session_token:Option<String>, signup_data: (String, String, String)) -> MedalResult<SignupResult> {
    let (username, email, password) = signup_data;

    if username == "" || email == "" || password == "" {
        return Ok(SignupResult::EmptyFields)
    }

    let salt = helpers::make_salt();
    let hash = helpers::hash_password(&password, &salt)?;

    let result = conn.signup(&session_token.unwrap(), &username, &email, hash, &salt);
    Ok(result)
}

554
#[cfg(feature = "signup")]
555
556
557
558
559
560
561
562
563
564
565
566
567
pub fn signupdata (query_string: Option<String>) -> json_val::Map<String, json_val::Value> {
    let mut data = json_val::Map::new();
    if let Some(query) = query_string {
        if query.starts_with("status=") {
            let status: &str = &query[7..];
            if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
                data.insert((status).to_string(), to_json(&true));
            }
        }
    }
    data
}

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

572
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
573
574
575
              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
576
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
577
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
578
579
580
    }
}

581
582
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
583
584
                                           -> MedalResult<String>
{
585
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
586
587

    if session.csrf_token != csrf_token {
588
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
589
590
    }

591
592
593
594
595
596
597
598
599
600
601
602
    let (_, _, c) = conn.get_task_by_id_complete(task_id);

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

            let left_secs = i64::from(c.duration) * 60 - passed_secs;
603
            if c.duration > 0 && left_secs < -10 {
604
605
606
                return Err(MedalError::AccessDenied);
                // Contest over
                // TODO: Nicer message!
607
608
609
610
            }
        }
    }

Robert Czechowski's avatar
Robert Czechowski committed
611
612
613
    let submission = Submission { id: None,
                                  session_user: session.id,
                                  task: task_id,
Robert Czechowski's avatar
Robert Czechowski committed
614
                                  grade,
Robert Czechowski's avatar
Robert Czechowski committed
615
616
617
618
619
620
                                  validated: false,
                                  nonvalidated_grade: grade,
                                  needs_validation: true,
                                  subtask_identifier: subtask,
                                  value: data,
                                  date: time::get_time() };
621

Robert Czechowski's avatar
Robert Czechowski committed
622
623
624
625
626
    conn.submit_submission(submission);

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

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

630
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
631
632
    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?
633

634
635
636
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
637

638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
    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);
        }
    }
653

654
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
655
        None => Err(MedalError::AccessDenied),
656
657
658
659
660
661
        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
662

663
            let mut data = json_val::Map::new();
664
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
665
666
667
            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
668

669
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
670
            if c.duration > 0 && left_secs < 0 {
671
                Err(MedalError::AccessDenied)
672
673
674
675
676
            // 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
677
678
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
679
680

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

682
                data.insert("contestname".to_string(), to_json(&c.name));
683
684
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
685
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
686
687
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
688
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
689

Robert Czechowski's avatar
Robert Czechowski committed
690
691
692
693
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

694
695
696
697
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
698
}
Robert Czechowski's avatar
Robert Czechowski committed
699

Robert Czechowski's avatar
Robert Czechowski committed
700
701
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
702
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
703
704
705
706
707
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
711
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
712
713

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

Robert Czechowski's avatar
Robert Czechowski committed
716
717
718
719
720
721
722
723
    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
724
    data.insert("group".to_string(), to_json(&v));
725
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
726

727
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
728
729
}

Robert Czechowski's avatar
Robert Czechowski committed
730
731
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
732
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
733
734
    pub firstname: String,
    pub lastname: String,
735
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
736
737
738
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
746
    if group.admin != session.id {
747
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
748
749
    }

Robert Czechowski's avatar
Robert Czechowski committed
750
751
752
753
754
755
756
757
758
759
760
    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()),
761
                                   grade: grade_to_string(m.grade),
Robert Czechowski's avatar
Robert Czechowski committed
762
763
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
764
765
766

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
767
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
768
769
770

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

772
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
773
774
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
775

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

    if session.csrf_token != csrf_token {
781
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
782
783
    }

Robert Czechowski's avatar
Robert Czechowski committed
784
    let groupcode = helpers::make_group_code();
785
    // TODO: check for collisions
786

787
    let mut group = Group { id: None, name, groupcode, tag, admin: session.id, members: Vec::new() };
Robert Czechowski's avatar
Robert Czechowski committed
788

Robert Czechowski's avatar
Robert Czechowski committed
789
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
790
791
792

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

794
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
795
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
796
797
798
799
800
801
802

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

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

803
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
804
805
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
806
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
807
808
809
810
811

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

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

814
815
816
817
818
    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
819
820
821
822
823
824
825
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

826
827
    for line in v {
        if name != line[0] {
828
            if name != "" {
829
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
830
            }
831
832
833
834
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
835
836
837
838
839
840
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
841
842
843
844
845
846
847
848
849
850
851
        }

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

852
853
854
    Ok(())
}

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

861
    let data = json_val::Map::new();
862
863
864

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

866
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
867
868
869
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
870
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
871
872

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

875
876
    match user_id {
        None => {
877
878
879
880
881
            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));
882
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
883
884
885
886
887
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
888

889
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
890
            if session.password.is_some() {
891
                data.insert("profile_username".to_string(), to_json(&session.username));
892
893
            }
            if session.managed_by.is_none() {
894
                data.insert("profile_not_in_group".into(), to_json(&true));
895
            }
896
897
898
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
899
900
            data.insert("ownprofile".into(), to_json(&true));

901
            if let Some(query) = query_string {
902
                if query.starts_with("status=") {
903
                    let status: &str = &query[7..];
904
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin", "SignedUp"].contains(&status) {
905
906
                        data.insert((status).to_string(), to_json(&true));
                    }
907
908
                }
            }
909

910
911
            let now = time::get_time();

912
            // TODO: Needs to be filtered
913
914
915
916
917
918
919
920
            let participations: Vec<(i32, String, bool, bool)> = conn.get_all_participations_complete(session.id).into_iter().rev().map(|(participation, contest)| {
                let passed_secs = now.sec - participation.start.sec;
                let left_secs = i64::from(contest.duration) * 60 - passed_secs;
                let is_time_left = contest.duration == 0 || left_secs >= 0;
                let has_timelimit = contest.duration != 0;
                (contest.id.unwrap(), contest.name, has_timelimit, is_time_left)
            }).collect();
            data.insert("participations".into(), to_json(&participations));
Robert Czechowski's avatar
Robert Czechowski committed
921
        }
922
        // Case user_id: teacher modifing a students profile
923
924
925
926
927
928
929
930
        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);
            }

931
932
933
934
935
            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));
936
            data.insert(format!("sel{}", user.grade), to_json(&"selected"));
937
938
939
940
941
            if let Some(sex) = user.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
942

943
            data.insert("profile_logincode".to_string(), to_json(&user.logincode));
944
            if user.username.is_some() {
945
                data.insert("profile_username".to_string(), to_json(&user.username));
946
947
            }
            if user.managed_by.is_none() {
948
                data.insert("profile_not_in_group".into(), to_json(&true));
949
            }
950
951
952
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
953
            data.insert("ownprofile".into(), to_json(&false));
954

955
956
957
958
959
960
961
962