core.rs 41 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"));
}

Robert Czechowski's avatar
Robert Czechowski committed
60
pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>,
61
                                 (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
62
63
                                 -> (String, json_val::Map<String, json_val::Value>)
{
64
65
66
67
68
69
    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) {
70
            fill_user_data(&session, &mut data);
71
72
73
        }
    }

74
    let mut oauth_links: Vec<(String, String, String)> = Vec::new();
75
76
77
78
79
80
81
82
    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()));
        }
    }

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

86
    data.insert("parent".to_string(), to_json(&"base"));
87
88
89
    ("index".to_owned(), data)
}

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

92
93
94
95
96
97
98
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));
99
            data.insert("session_id".to_string(), to_json(&session.id));
100
101
102
103
104
105
106
107
108
109
110
111
112
            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));
113
114
                    data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
                    data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
115
116
                    data.insert("logincode".to_string(), to_json(&session.logincode));
                    data.insert("managed_by".to_string(), to_json(&session.managed_by));
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
                }
            }
        }
        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);
    }
}

134
135
136
137
#[derive(PartialEq, Eq)]
pub enum ContestVisibility {
    All,
    Open,
138
    Current,
139
140
}

141
142
143
144
145
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
146
147
    let mut data = json_val::Map::new();

148
    let session = conn.get_session_or_new(&session_token);
149
    fill_user_data(&session, &mut data);
150

151
152
153
154
    if session.is_logged_in() {
        data.insert("can_start".to_string(), to_json(&true));
    }

155
156
157
158
159
160
161
162
163
164
165
166
    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));

167
168
169
170
171
172
173
174
    let v: Vec<ContestInfo> =
        conn.get_contest_list()
            .iter()
            .map(|c| ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public })
            .filter(|ci| ci.public)
            .filter(|ci| ci.duration == 0 || visibility != ContestVisibility::Open)
            .filter(|ci| ci.duration != 0 || visibility != ContestVisibility::Current)
            .collect();
Robert Czechowski's avatar
Robert Czechowski committed
175
    data.insert("contest".to_string(), to_json(&v));
176
177
    data.insert("contestlist_header".to_string(),
                to_json(&match visibility {
178
                            ContestVisibility::Open => "Trainingsaufgaben",
179
180
181
                            ContestVisibility::Current => "Aktuelle Wettbewerbe",
                            ContestVisibility::All => "Alle Wettbewerbe",
                        }));
Robert Czechowski's avatar
Robert Czechowski committed
182
183
184
185

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

186
fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
187
188
    let mut subtaskinfos = Vec::new();
    let mut not_print_yet = true;
189
    for st in &tg.tasks {
190
191
192
193
194
195
        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;
        }

196
197
198
199
200
201
        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 };
202
203
204
205
206
207

        subtaskinfos.push(si);
    }
    subtaskinfos
}

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#[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 }
}

240
241
pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, query_string: Option<String>)
                                        -> MedalValueResult {
242
243
    let session = conn.get_session_or_new(&session_token);

244
    let contest = conn.get_contest_by_id_complete(contest_id);
245
    let grades = conn.get_contest_user_grades(&session_token, contest_id);
246

247
    let mut opt_part = conn.get_participation(&session_token, contest_id);
248

249
250
251
    // 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?
252
    // if opt_part.is_none() && (contest.duration == 0 || session.is_teacher) {
253
    // TODO: Should autostart only happen in the contest time?
254
    if opt_part.is_none() && contest.duration == 0 {
255
256
        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() });
257
    }
258

259
260
261
262
    let ci = ContestInfo { id: contest.id.unwrap(),
                           name: contest.name.clone(),
                           duration: contest.duration,
                           public: contest.public };
Robert Czechowski's avatar
Robert Czechowski committed
263
264
265
266

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

267
    let constraints = check_contest_constraints(&session, &contest);
268

269
    let can_start = session.is_logged_in() && constraints.contest_running && constraints.grade_matching;
270
    let has_duration = contest.duration > 0;
271

272
    data.insert("constraints".to_string(), to_json(&constraints));
273
    data.insert("has_duration".to_string(), to_json(&has_duration));
274
    data.insert("can_start".to_string(), to_json(&can_start));
275

276
    let now = time::get_time();
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    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));
300
301
    }

302
303
304
    if let Some(participation) = opt_part {
        let mut totalgrade = 0;
        let mut max_totalgrade = 0;
305

306
        let mut tasks = Vec::new();
307
        for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
308
309
310
311
312
313
314
            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);
        }
315
        let relative_points = (totalgrade * 100) / max_totalgrade;
316
317

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

319
320
321
322
323
        let now = time::get_time();
        let passed_secs = now.sec - participation.start.sec;
        if passed_secs < 0 {
            // behandle inkonsistente Serverzeit
        }
324
325
        let left_secs = i64::from(contest.duration) * 60 - passed_secs;
        let is_time_left = contest.duration == 0 || left_secs >= 0;
326
327
328
329
        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);
330

331
        data.insert("is_started".to_string(), to_json(&true));
332
        data.insert("participation_start_date".to_string(), to_json(&passed_secs));
333
334
        data.insert("total_points".to_string(), to_json(&totalgrade));
        data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
335
336
337
        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));
338
339
340
341
342
343
        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
344
    }
345

346
347
348
349
350
351
    // 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));
352
353
    }

354
    Ok(("contest".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
355
356
}

357
pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
358
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
359
360
    let mut data = json_val::Map::new();
    fill_user_data(&session, &mut data);
361

362
363
    let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);

364
    let mut results: Vec<(String, i32, Vec<(String, String, i32, Vec<String>)>)> = Vec::new();
365
366

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

Daniel Brüning's avatar
Daniel Brüning committed
369
        //TODO: use user
370
        for (user, userdata) in groupdata {
371
372
            let mut userresults: Vec<String> = Vec::new();

373
374
375
            userresults.push(String::new());
            let mut summe = 0;

376
            for grade in userdata {
377
378
379
380
381
                if let Some(g) = grade.grade {
                    userresults.push(format!("{}", g));
                    summe += g;
                } else {
                    userresults.push(format!("–"));
382
                }
383
384
            }

385
386
            userresults[0] = format!("{}", summe);

387
388
            groupresults.push((user.firstname.unwrap_or_else(|| "–".to_string()),
                               user.lastname.unwrap_or_else(|| "–".to_string()),
389
390
                               user.id,
                               userresults))
391
        }
392

393
        results.push((format!("{}", group.name), group.id.unwrap_or(0), groupresults));
394
395
396
397
    }

    data.insert("taskname".to_string(), to_json(&tasknames));
    data.insert("result".to_string(), to_json(&results));
398
399

    let c = conn.get_contest_by_id(contest_id);
400
    let ci = ContestInfo { id: c.id.unwrap(), name: c.name.clone(), duration: c.duration, public: c.public };
401
    data.insert("contest".to_string(), to_json(&ci));
402
    data.insert("contestname".to_string(), to_json(&c.name));
403

404
405
406
    Ok(("contestresults".to_owned(), data))
}

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

413
    // Check logged in or open contest
414
    if contest.duration != 0 && !session.is_logged_in() {
415
        return Err(MedalError::AccessDenied);
416
    }
417

418
    // Check CSRF token
419
420
421
    if session.is_logged_in() && session.csrf_token != csrf_token {
        return Err(MedalError::CsrfCheckFailed);
    }
422

423
424
425
426
427
428
429
    // Check other constraints
    let constraints = check_contest_constraints(&session, &contest);

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

430
    // Start contest
431
    match conn.new_participation(&session_token, contest_id) {
Robert Czechowski's avatar
Robert Czechowski committed
432
        Ok(_) => Ok(()),
433
        _ => Err(MedalError::AccessDenied), // Contest already started TODO: Maybe redirect to page with hint
Robert Czechowski's avatar
Robert Czechowski committed
434
    }
Robert Czechowski's avatar
Robert Czechowski committed
435
436
}

437
438
439
440
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
441
442
    let (username, password) = login_data;

443
    match conn.login(None, &username, &password) {
Robert Czechowski's avatar
Robert Czechowski committed
444
        Ok(session_token) => Ok(session_token),
Robert Czechowski's avatar
Robert Czechowski committed
445
446
        Err(()) => {
            let mut data = json_val::Map::new();
447
            data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
Robert Czechowski's avatar
Robert Czechowski committed
448
            data.insert("username".to_string(), to_json(&username));
449
450
451
452
453
454
455
456
457
458
459
460
461
462
            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));

463
            Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
464
465
466
467
        }
    }
}

Robert Czechowski's avatar
Robert Czechowski committed
468
pub fn login_with_code<T: MedalConnection>(
469
    conn: &T, code: &str, (self_url, oauth_providers): (Option<String>, Option<Vec<OauthProvider>>))
Robert Czechowski's avatar
Robert Czechowski committed
470
    -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
471
    match conn.login_with_code(None, &code) {
Robert Czechowski's avatar
Robert Czechowski committed
472
473
474
475
476
477
478
        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));
479
480
481
482
483
484
485
486
487
488
489
490
491
492
                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
493
                Err(("login".to_owned(), data))
Robert Czechowski's avatar
Robert Czechowski committed
494
            }
Robert Czechowski's avatar
Robert Czechowski committed
495
        },
Robert Czechowski's avatar
Robert Czechowski committed
496
497
498
    }
}

499
pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
500
    session_token.map(|token| conn.logout(&token));
Robert Czechowski's avatar
Robert Czechowski committed
501
502
}

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

507
    match match subtask {
Robert Czechowski's avatar
Robert Czechowski committed
508
509
510
              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
511
        Some(submission) => Ok(submission.value),
Robert Czechowski's avatar
Robert Czechowski committed
512
        None => Ok("{}".to_string()),
Robert Czechowski's avatar
Robert Czechowski committed
513
514
515
    }
}

516
517
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
518
519
                                           -> MedalResult<String>
{
520
    let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
521
522

    if session.csrf_token != csrf_token {
523
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
524
525
    }

Robert Czechowski's avatar
Robert Czechowski committed
526
527
528
529
530
531
532
533
534
535
    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() };
536

Robert Czechowski's avatar
Robert Czechowski committed
537
538
539
540
541
    conn.submit_submission(submission);

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

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

545
    let (t, tg, c) = conn.get_task_by_id_complete(task_id);
546
547
    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?
548

549
550
551
    let mut prevtaskgroup: Option<Taskgroup> = None;
    let mut nexttaskgroup: Option<Taskgroup> = None;
    let mut current_found = false;
552

553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
    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);
        }
    }
568

569
    match conn.get_participation(&session_token, c.id.expect("Value from database")) {
570
        None => Err(MedalError::AccessDenied),
571
572
573
574
575
576
        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
577

578
            let mut data = json_val::Map::new();
579
            data.insert("participation_start_date".to_string(), to_json(&format!("{}", passed_secs)));
580
581
582
            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
583

584
            let left_secs = i64::from(c.duration) * 60 - passed_secs;
585
            if c.duration > 0 && left_secs < 0 {
586
                Err(MedalError::AccessDenied)
587
588
589
590
591
            // 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
592
593
                data.insert("time_left".to_string(), to_json(&format!("{}:{:02}", hour, min)));
                data.insert("time_left_sec".to_string(), to_json(&format!(":{:02}", sec)));
594
595

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

597
                data.insert("contestname".to_string(), to_json(&c.name));
598
599
                data.insert("name".to_string(), to_json(&tg.name));
                data.insert("taskid".to_string(), to_json(&task_id));
600
                data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
601
602
                data.insert("taskpath".to_string(), to_json(&taskpath));
                data.insert("contestid".to_string(), to_json(&c.id));
Robert Czechowski's avatar
Robert Czechowski committed
603
                data.insert("seconds_left".to_string(), to_json(&left_secs));
Robert Czechowski's avatar
Robert Czechowski committed
604

Robert Czechowski's avatar
Robert Czechowski committed
605
606
607
608
                if c.duration > 0 {
                    data.insert("duration".to_string(), to_json(&true));
                }

609
610
611
612
                Ok(("task".to_owned(), data))
            }
        }
    }
Robert Czechowski's avatar
Robert Czechowski committed
613
}
Robert Czechowski's avatar
Robert Czechowski committed
614

Robert Czechowski's avatar
Robert Czechowski committed
615
616
#[derive(Serialize, Deserialize)]
pub struct GroupInfo {
617
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
618
619
620
621
622
    pub name: String,
    pub tag: String,
    pub code: String,
}

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

Robert Czechowski's avatar
Robert Czechowski committed
626
    //    let groupvec = conn.get_group(session_token);
Robert Czechowski's avatar
Robert Czechowski committed
627
628

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

Robert Czechowski's avatar
Robert Czechowski committed
631
632
633
634
635
636
637
638
    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
639
    data.insert("group".to_string(), to_json(&v));
640
    data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
641

642
    Ok(("groups".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
643
644
}

Robert Czechowski's avatar
Robert Czechowski committed
645
646
#[derive(Serialize, Deserialize)]
pub struct MemberInfo {
647
    pub id: i32,
Robert Czechowski's avatar
Robert Czechowski committed
648
649
    pub firstname: String,
    pub lastname: String,
650
    pub grade: String,
Robert Czechowski's avatar
Robert Czechowski committed
651
652
653
    pub logincode: String,
}

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

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

Robert Czechowski's avatar
Robert Czechowski committed
661
    if group.admin != session.id {
662
        return Err(MedalError::AccessDenied);
Robert Czechowski's avatar
Robert Czechowski committed
663
664
    }

Robert Czechowski's avatar
Robert Czechowski committed
665
666
667
668
669
670
671
672
673
674
675
    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()),
676
677
678
679
680
681
682
683
684
685
686
687
                                   grade: match m.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
688
689
                                   logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()) })
             .collect();
Robert Czechowski's avatar
Robert Czechowski committed
690
691
692

    data.insert("group".to_string(), to_json(&gi));
    data.insert("member".to_string(), to_json(&v));
693
    data.insert("groupname".to_string(), to_json(&gi.name));
Robert Czechowski's avatar
Robert Czechowski committed
694
695
696

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

698
pub fn modify_group<T: MedalConnection>(_conn: &T, _group_id: i32, _session_token: &str) -> MedalResult<()> {
Robert Czechowski's avatar
Robert Czechowski committed
699
700
    unimplemented!()
}
Robert Czechowski's avatar
Robert Czechowski committed
701

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

    if session.csrf_token != csrf_token {
707
        return Err(MedalError::CsrfCheckFailed);
Robert Czechowski's avatar
Robert Czechowski committed
708
709
    }

710
    let group_code = helpers::make_group_code();
711
    // TODO: check for collisions
712

Robert Czechowski's avatar
Robert Czechowski committed
713
714
    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
715

Robert Czechowski's avatar
Robert Czechowski committed
716
    conn.add_group(&mut group);
Robert Czechowski's avatar
Robert Czechowski committed
717
718
719

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

721
pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
722
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
723
724
725
726
727
728
729

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

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

730
// TODO: Should creating the users and groups happen in a batch operation to speed things up?
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
731
732
pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
                                         -> MedalResult<()> {
733
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
734
735
736
737
738

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

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

741
742
743
744
745
    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
746
747
748
749
750
751
752
    let mut group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: "".to_string(),
                            admin: session.id,
                            members: Vec::new() };

753
754
    for line in v {
        if name != line[0] {
755
            if name != "" {
756
                conn.create_group_with_users(group);
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
757
            }
758
759
760
761
            name = line[0].clone();
            group_code = helpers::make_group_code();
            // TODO: check for collisions

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
762
763
764
765
766
767
            group = Group { id: None,
                            name: name.clone(),
                            groupcode: group_code,
                            tag: name.clone(),
                            admin: session.id,
                            members: Vec::new() };
768
769
770
771
772
773
774
775
776
777
778
        }

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

779
780
781
    Ok(())
}

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

788
    let data = json_val::Map::new();
789
790
791

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

793
pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
Robert Czechowski's avatar
Robert Czechowski committed
794
795
796
                                        query_string: Option<String>)
                                        -> MedalValueResult
{
797
    let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
798
799

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

802
803
    match user_id {
        None => {
804
805
806
807
808
            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));
809
            data.insert(format!("sel{}", session.grade), to_json(&"selected"));
810
811
812
813
814
            if let Some(sex) = session.sex {
                data.insert(format!("sex_{}", sex), to_json(&"selected"));
            } else {
                data.insert("sex_None".to_string(), to_json(&"selected"));
            }
815

816
            data.insert("profile_logincode".to_string(), to_json(&session.logincode));
817
            if session.password.is_some() {
818
                data.insert("profile_username".to_string(), to_json(&session.username));
819
820
            }
            if session.managed_by.is_none() {
821
                data.insert("profile_not_in_group".into(), to_json(&true));
822
            }
823
824
825
            if session.oauth_provider != Some("pms".to_string()) {
                data.insert("profile_not_pms".into(), to_json(&true));
            }
826
827
            data.insert("ownprofile".into(), to_json(&true));

828
            if let Some(query) = query_string {
829
                if query.starts_with("status=") {
830
                    let status: &str = &query[7..];
831
                    if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch", "firstlogin"].contains(&status) {
832
833
                        data.insert((status).to_string(), to_json(&true));
                    }
834
835
                }
            }
Robert Czechowski's avatar
Robert Czechowski committed
836
        }
837
838
839
840
841
842
843
844
        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);
            }

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

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

869
870
871
872
873
874
875
876
            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));
                    }
                }
            }
877
878
        }
    }
879

880
    Ok(("profile".to_string(), data))
Robert Czechowski's avatar
Robert Czechowski committed
881
882
}

883
#[derive(Debug, PartialEq)]
884
pub enum ProfileStatus {
Daniel Brüning's avatar
Daniel Brüning committed
885
886
887
888
    NothingChanged,
    DataChanged,
    PasswordChanged,
    PasswordMissmatch,
889
890
}
impl std::convert::Into<String> for ProfileStatus {
Robert Czechowski's avatar
Robert Czechowski committed
891
    fn into(self) -> String { format!("{:?}", self) }
892
893
}

894
pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
                                        (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
912
913
                                        -> MedalResult<ProfileStatus>
{
914
    let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
Robert Czechowski's avatar
Robert Czechowski committed
915
916

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

920
    let mut result = ProfileStatus::NothingChanged;
921

922
    let mut password_and_salt = None;
923

924
925
926
    if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
        if password != "" || password_repeat != "" {
            if password == password_repeat {
927
928
                let salt = helpers::make_salt();
                let hash = helpers::hash_password(&password, &salt)?;
Daniel Brüning's avatar
Daniel Brüning committed
929

930
931
932
933
934
                password_and_salt = Some((hash, salt));
                result = ProfileStatus::PasswordChanged;
            } else {
                result = ProfileStatus::PasswordMissmatch;
            }
935
936
937
938
939
940
941
942
943
944
945
946
        }
    }

    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
        {
947
            return Ok(ProfileStatus::NothingChanged);
948
949
        } else {
            result = ProfileStatus::DataChanged;
950
        }
951
952
    }

953
954
955
956
957
    match user_id {
        None => {
            session.firstname = Some(firstname);
            session.lastname = Some(lastname);
            session.grade = grade;
958
            session.sex = sex;
959

960
961
962
963
964
965
966
967
968
969
970
            if street.is_some() {
                session.street = street;
            }
            if zip.is_some() {
                session.zip = zip;
            }
            if city.is_some() {
                session.city = city;
            }

            if let Some((password, salt)) = password_and_salt {
971
972
                session.password = Some(password);
                session.salt = Some(salt);
973
            }
Daniel Brüning's avatar
Daniel Brüning committed
974

975
976
977
978
979
980
981