db_conn_postgres.rs 54 KB
Newer Older
1
2
#![cfg(feature = "postgres")]

3
4
extern crate postgres;

5
6
7
use postgres::Connection;
use time;
use time::Duration;
8
9
10

use db_conn::{MedalConnection, MedalObject};
use db_objects::*;
11
use helpers;
12

13
trait Queryable {
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
14
15
    fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F)
                           -> postgres::Result<Option<T>>
16
        where F: FnOnce(postgres::rows::Row<'_>) -> T;
17
18
    fn query_map_many<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F) -> postgres::Result<Vec<T>>
        where F: FnMut(postgres::rows::Row<'_>) -> T;
19
    fn exists(&self, sql: &str, params: &[&dyn postgres::types::ToSql]) -> bool;
20
    fn get_last_id(&self) -> Option<i32>;
21
22
23
}

impl Queryable for Connection {
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
24
25
    fn query_map_one<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F)
                           -> postgres::Result<Option<T>>
26
27
        where F: FnOnce(postgres::rows::Row<'_>) -> T {
        let rows = self.query(sql, params)?;
28

29
30
        Ok(rows.iter().next().map(f))
    }
31

32
33
34
35
36
    fn query_map_many<T, F>(&self, sql: &str, params: &[&dyn postgres::types::ToSql], f: F) -> postgres::Result<Vec<T>>
        where F: FnMut(postgres::rows::Row<'_>) -> T {
        Ok(self.query(sql, params)?.iter().map(f).collect())
    }

37
38
39
40
41
    fn exists(&self, sql: &str, params: &[&dyn postgres::types::ToSql]) -> bool {
        let stmt = self.prepare(sql).unwrap();
        !stmt.query(params).unwrap().is_empty()
    }

42
43
44
45
46
47
    fn get_last_id(&self) -> Option<i32> {
        self.query("SELECT lastval()", &[]).unwrap().iter().next().map(|row| {
                                                                      let r: i64 = row.get(0);
                                                                      r as i32
                                                                  })
    }
48
    // Empty line intended
49
50
}

51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
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
impl MedalObject<Connection> for Submission {
    fn save(&mut self, conn: &Connection) {
        match self.get_id() {
            Some(_id) => unimplemented!(),
            None => {
                let query = "INSERT INTO submission (task, session, grade, validated, nonvalidated_grade,
                                                     subtask_identifier, value, date, needs_validation)
                             VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)";
                conn.execute(query,
                             &[&self.task,
                               &self.session_user,
                               &self.grade,
                               &self.validated,
                               &self.nonvalidated_grade,
                               &self.subtask_identifier,
                               &self.value,
                               &self.date,
                               &self.needs_validation])
                    .unwrap();
                self.set_id(conn.get_last_id().unwrap());
            }
        }
    }
}

impl MedalObject<Connection> for Grade {
    fn save(&mut self, conn: &Connection) {
        let query = "INSERT INTO grade (taskgroup, session, grade, validated)
                     VALUES ($1, $2, $3, $4)
                     ON CONFLICT ON CONSTRAINT grade_pkey DO UPDATE SET grade = excluded.grade, validated = excluded.validated";
        conn.execute(query, &[&self.taskgroup, &self.user, &self.grade, &self.validated]).unwrap();
    }
}

impl MedalObject<Connection> for Participation {
    fn save(&mut self, conn: &Connection) {
        let query = "INSERT INTO participation (contest, session, start_date)
                     VALUES ($1, $2, $3)";
        conn.execute(query, &[&self.contest, &self.user, &self.start]).unwrap();
    }
}

impl MedalObject<Connection> for Group {
    fn save(&mut self, conn: &Connection) {
        match self.get_id() {
            Some(_id) => unimplemented!(),
            None => {
                let query = "INSERT INTO usergroup (name, groupcode, tag, admin)
                             VALUES ($1, $2, $3, $4)";
                conn.execute(query, &[&self.name, &self.groupcode, &self.tag, &self.admin]).unwrap();
                self.set_id(conn.get_last_id().unwrap());
            }
        }
    }
}

impl MedalObject<Connection> for Task {
    fn save(&mut self, conn: &Connection) {
        let query = "SELECT id
                     FROM task
                     WHERE taskgroup = $1
                     AND location = $2";
        conn.query_map_one(query, &[&self.taskgroup, &self.location], |row| row.get(0))
            .unwrap_or(None)
            .and_then(|id| {
                self.set_id(id);
                Some(())
            })
            .unwrap_or(()); // Err means no entry yet and is expected result

        let id = match self.get_id() {
            Some(id) => {
                let query = "UPDATE task
                             SET taskgroup = $1, location = $2, stars = $3
                             WHERE id = $4";
                conn.execute(query, &[&self.taskgroup, &self.location, &self.stars, &id]).unwrap();
                id
            }
            None => {
                let query = "INSERT INTO task (taskgroup, location, stars)
                             VALUES ($1, $2, $3)";
                conn.execute(query, &[&self.taskgroup, &self.location, &self.stars]).unwrap();
                conn.get_last_id().unwrap()
            }
        };
        self.set_id(id);
    }
}

impl MedalObject<Connection> for Taskgroup {
    fn save(&mut self, conn: &Connection) {
        if let Some(first_task) = self.tasks.get(0) {
            let query = "SELECT taskgroup.id
                         FROM taskgroup
                         JOIN task
                         ON task.taskgroup = taskgroup.id
                         WHERE contest = $1
                         AND task.location = $2";
            conn.query_map_one(query, &[&self.contest, &first_task.location], |row| row.get(0))
                .unwrap_or(None)
                .and_then(|id| {
                    self.set_id(id);
                    Some(())
                })
                .unwrap_or(()); // Err means no entry yet and is expected result
        }

        let id = match self.get_id() {
            Some(id) => {
                let query = "UPDATE taskgroup
                             SET contest = $1, name = $2, positionalnumber = $3
                             WHERE id = $4";
                conn.execute(query, &[&self.contest, &self.name, &self.positionalnumber, &id]).unwrap();
                id
            }
            None => {
                let query = "INSERT INTO taskgroup (contest, name, positionalnumber)
                             VALUES ($1, $2, $3)";
                conn.execute(query, &[&self.contest, &self.name, &self.positionalnumber]).unwrap();
                conn.get_last_id().unwrap()
            }
        };
        self.set_id(id);
        for mut task in &mut self.tasks {
            task.taskgroup = id;
            task.save(conn);
        }
    }
}

impl MedalObject<Connection> for Contest {
    fn save(&mut self, conn: &Connection) {
        let query = "SELECT id
                     FROM contest
                     WHERE location = $1
                     AND filename = $2";
        conn.query_map_one(query, &[&self.location, &self.filename], |row| row.get(0))
            .unwrap_or(None)
            .and_then(|id| {
                self.set_id(id);
                Some(())
            })
            .unwrap_or(()); // Err means no entry yet and is expected result

        let id = match self.get_id() {
            Some(id) => {
                let query = "UPDATE contest
                             SET location = $1,filename = $2, name = $3, duration = $4, public = $5, start_date = $6, end_date = $7
                             WHERE id = $8";
                conn.execute(query,
                             &[&self.location,
                               &self.filename,
                               &self.name,
                               &self.duration,
                               &self.public,
                               &self.start,
                               &self.end,
                               &id])
                    .unwrap();
                id
            }
            None => {
                let query = "INSERT INTO contest (location, filename, name, duration, public, start_date, end_date)
                             VALUES ($1, $2, $3, $4, $5, $6, $7)";
                conn.execute(query,
                             &[&self.location,
                               &self.filename,
                               &self.name,
                               &self.duration,
                               &self.public,
                               &self.start,
                               &self.end])
                    .unwrap();
                conn.get_last_id().unwrap()
            }
        };
        self.set_id(id);
        for mut taskgroup in &mut self.taskgroups {
            taskgroup.contest = id;
            taskgroup.save(conn);
        }
    }
}

235
impl MedalConnection for Connection {
236
237
238
239
240
241
    fn dbtype(&self) -> &'static str { "postgres" }

    fn migration_already_applied(&self, name: &str) -> bool {
        let create_string = "CREATE TABLE IF NOT EXISTS migrations (name TEXT PRIMARY KEY);";
        self.execute(create_string, &[]).unwrap();

242
243
        let query = "SELECT name FROM migrations WHERE name = $1";
        self.exists(query, &[&name])
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
    }

    fn apply_migration(&mut self, name: &str, contents: &str) {
        print!("Applying migration `{}` … ", name);

        let tx = self.transaction().unwrap();

        tx.batch_execute(&contents).unwrap();
        tx.execute("INSERT INTO migrations (name) VALUES ($1)", &[&name]).unwrap();

        tx.commit().unwrap();

        println!("OK.");
    }

    // fn get_session<T: ToSql>(&self, key: T, keyname: &str) -> Option<SessionUser> {
    fn get_session(&self, key: &str) -> Option<SessionUser> {
261
262
263
264
265
        let query = "SELECT id, csrf_token, last_login, last_activity, permanent_login, username, password, logincode,
                            email, email_unconfirmed, email_confirmationcode, firstname, lastname, street, zip, city,
                            nation, grade, is_teacher, managed_by, oauth_provider, oauth_foreign_id, salt
                     FROM session
                     WHERE session_token = $1";
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
        let session = self.query_map_one(query, &[&key], |row| SessionUser { id: row.get(0),
                                                                             session_token: Some(key.to_string()),
                                                                             csrf_token: row.get(1),
                                                                             last_login: row.get(2),
                                                                             last_activity: row.get(3),
                                                                             permanent_login: row.get(4),

                                                                             username: row.get(5),
                                                                             password: row.get(6),
                                                                             salt: row.get(22),
                                                                             logincode: row.get(7),
                                                                             email: row.get(8),
                                                                             email_unconfirmed: row.get(9),
                                                                             email_confirmationcode: row.get(10),

                                                                             firstname: row.get(11),
                                                                             lastname: row.get(12),
                                                                             street: row.get(13),
                                                                             zip: row.get(14),
                                                                             city: row.get(15),
                                                                             nation: row.get(16),
                                                                             grade: row.get(17),

                                                                             is_teacher: row.get(18),
                                                                             managed_by: row.get(19),

                                                                             oauth_provider: row.get(20),
                                                                             oauth_foreign_id: row.get(21) })
                          .ok()??;
295
296
297

        let duration = if session.permanent_login { Duration::days(90) } else { Duration::minutes(90) };
        let now = time::get_time();
298

299
300
        if let Some(last_activity) = session.last_activity {
            if now - last_activity < duration {
301
302
303
304
                let query = "UPDATE session
                             SET last_activity = $1
                             WHERE id = $2";
                self.execute(query, &[&now, &session.id]).unwrap();
305
                return Some(session);
306
            } else {
307
308
309
310
                // Session timed out
                // Should remove session token from session
                return None;
            }
311
        }
312
313
314
        // last_activity undefined
        // TODO: What should happen here?
        None
315
316
317
318
319
320
321
322
323
324
325
326
    }
    fn save_session(&self, session: SessionUser) {
        self.execute("UPDATE session SET
                      username = $1,
                      password = $2,
                      salt = $3,
                      logincode = $4,
                      firstname = $5,
                      lastname = $6,
                      street = $7,
                      zip = $8,
                      city = $9,
327
328
329
                      grade = $10,
                      is_teacher = $11
                      WHERE id = $12",
330
331
332
333
334
335
336
337
338
339
                     &[&session.username,
                       &session.password,
                       &session.salt,
                       &session.logincode,
                       &session.firstname,
                       &session.lastname,
                       &session.street,
                       &session.zip,
                       &session.city,
                       &session.grade,
340
                       &session.is_teacher,
341
342
343
344
                       &session.id])
            .unwrap();
    }
    fn new_session(&self, session_token: &str) -> SessionUser {
345
        let csrf_token = helpers::make_csrf_token();
346
347

        let now = time::get_time();
348
349
        let query = "INSERT INTO session (session_token, csrf_token, last_activity, permanent_login, grade,
                                          is_teacher)
350
351
                     VALUES ($1, $2, $3, $4, $5, $6)";
        self.execute(query, &[&session_token, &csrf_token, &now, &false, &0, &false]).unwrap();
352

353
354
        let id = self.get_last_id().expect("Expected to get last row id");

355
        SessionUser::minimal(id, session_token.to_owned(), csrf_token)
356
357
    }
    fn get_session_or_new(&self, key: &str) -> SessionUser {
358
359
360
        let query = "UPDATE session
                     SET session_token = $1
                     WHERE session_token = $2";
361
362
363
        self.get_session(&key).ensure_alive().unwrap_or_else(|| {
                                                 // TODO: Factor this out in own function
                                                 // TODO: Should a new session key be generated every time?
364
                                                 self.execute(query, &[&Option::<String>::None, &key]).unwrap();
365
366
                                                 self.new_session(&key)
                                             })
367
368
369
    }

    fn get_user_by_id(&self, user_id: i32) -> Option<SessionUser> {
370
371
372
373
374
        let query = "SELECT session_token, csrf_token, last_login, last_activity, permanent_login, username, password,
                            logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname, street,
                            zip, city, nation, grade, is_teacher, managed_by, oauth_provider, oauth_foreign_id, salt
                     FROM session
                     WHERE id = $1";
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
        self.query_map_one(query, &[&user_id], |row| SessionUser { id: user_id,
                                                                   session_token: row.get(0),
                                                                   csrf_token: row.get(1),
                                                                   last_login: row.get(2),
                                                                   last_activity: row.get(3),
                                                                   permanent_login: row.get(4),

                                                                   username: row.get(5),
                                                                   password: row.get(6),
                                                                   salt: row.get(22),
                                                                   logincode: row.get(7),
                                                                   email: row.get(8),
                                                                   email_unconfirmed: row.get(9),
                                                                   email_confirmationcode: row.get(10),

                                                                   firstname: row.get(11),
                                                                   lastname: row.get(12),
                                                                   street: row.get(13),
                                                                   zip: row.get(14),
                                                                   city: row.get(15),
                                                                   nation: row.get(16),
                                                                   grade: row.get(17),

                                                                   is_teacher: row.get(18),
                                                                   managed_by: row.get(19),

                                                                   oauth_provider: row.get(20),
                                                                   oauth_foreign_id: row.get(21) })
403
            .ok()?
404
405
406
407
408
409
410
411
412
413
    }

    fn get_user_and_group_by_id(&self, user_id: i32) -> Option<(SessionUser, Option<Group>)> {
        let session = self.get_user_by_id(user_id)?;

        let group_id = match session.managed_by {
            Some(id) => id,
            None => return Some((session, None)),
        };

414
415
416
        let query = "SELECT name, groupcode, tag, admin
                     FROM usergroup
                     WHERE id = $1";
417
418
419
420
421
422
423
424
425
        let res = self.query_map_one(query, &[&group_id], |row| Group { id: Some(group_id),
                                                                        name: row.get(0),
                                                                        groupcode: row.get(1),
                                                                        tag: row.get(2),
                                                                        admin: row.get(3),
                                                                        members: Vec::new() })
                      .ok()?;
        match res {
            Some(group) => Some((session, Some(group))),
426
427
428
429
430
431
            _ => Some((session, None)),
        }
    }

    //TODO: use session
    fn login(&self, _session: Option<&str>, username: &str, password: &str) -> Result<String, ()> {
432
433
434
435
        let query = "SELECT id, password, salt
                     FROM session
                     WHERE username = $1";
        self.query_map_one(query, &[&username], |row| {
436
437
438
439
                let (id, password_hash, salt): (i32, Option<String>, Option<String>) =
                    (row.get(0), row.get(1), row.get(2));

                //password_hash ist das, was in der Datenbank steht
440
441
442
                if helpers::verify_password(&password,
                                            &salt.expect("salt from database empty"),
                                            &password_hash.expect("password from database empty"))
443
444
445
446
                {
                    // TODO: fail more pleasantly
                    // Login okay, update session now!

447
448
                    let session_token = helpers::make_session_token();
                    let csrf_token = helpers::make_csrf_token();
449
450
                    let now = time::get_time();

451
452
453
                    let query = "UPDATE session
                                 SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
                                 WHERE id = $4";
454
                    self.execute(query, &[&session_token, &csrf_token, &now, &id]).unwrap();
455
456
457
458
459

                    Ok(session_token)
                } else {
                    Err(())
                }
460
461
462
            })
            .map_err(|_| ())?
            .ok_or(())?
463
464
465
466
    }

    //TODO: use session
    fn login_with_code(&self, _session: Option<&str>, logincode: &str) -> Result<String, ()> {
467
468
469
470
        let query = "SELECT id
                     FROM session
                     WHERE logincode = $1";
        self.query_map_one(query, &[&logincode], |row| {
471
472
473
                // Login okay, update session now!
                let id: i32 = row.get(0);

474
475
                let session_token = helpers::make_session_token();
                let csrf_token = helpers::make_csrf_token();
476
477
                let now = time::get_time();

478
479
480
                let query = "UPDATE session
                             SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
                             WHERE id = $4";
481
                self.execute(query, &[&session_token, &csrf_token, &now, &id]).unwrap();
482

483
                session_token
484
485
486
            })
            .map_err(|_| ())?
            .ok_or(())
487
488
489
    }

    //TODO: use session
490
491
    fn login_foreign(&self, _session: Option<&str>, foreign_id: &str, is_teacher: bool, firstname: &str,
                     lastname: &str)
492
493
                     -> Result<String, ()>
    {
494
495
        let session_token = helpers::make_session_token();
        let csrf_token = helpers::make_csrf_token();
496
497
        let now = time::get_time();

498
499
500
        let query = "SELECT id
                     FROM session
                     WHERE oauth_foreign_id = $1";
501
502
        match self.query_map_one(query, &[&foreign_id], |row| -> i32 { row.get(0) }) {
            Ok(Some(id)) => {
503
504
505
                let query = "UPDATE session
                             SET session_token = $1, csrf_token = $2, last_login = $3, last_activity = $3
                             WHERE id = $4";
506
                self.execute(query, &[&session_token, &csrf_token, &now, &id]).unwrap();
507
508
509
510
511

                Ok(session_token)
            }
            // Add!
            _ => {
512
513
514
515
                let query = "INSERT INTO session (session_token, csrf_token, last_login, last_activity,
                                                  permanent_login, grade, is_teacher, oauth_foreign_id,
                                                  firstname, lastname)
                             VALUES ($1, $2, $3, $3, $4, $5, $6, $7, $8, $9)";
516
517
518
519
520
521
522
523
524
525
526
                self.execute(query,
                             &[&session_token,
                               &csrf_token,
                               &now,
                               &false,
                               &0,
                               &is_teacher,
                               &foreign_id,
                               &firstname,
                               &lastname])
                    .unwrap();
527
528
529
530
531
532
533
534

                Ok(session_token)
            }
        }
    }

    //TODO: use session
    fn create_user_with_groupcode(&self, _session: Option<&str>, groupcode: &str) -> Result<String, ()> {
535
536
537
        let query = "SELECT id
                     FROM usergroup
                     WHERE groupcode = $1";
538
539
        let group_id =
            self.query_map_one(query, &[&groupcode], |row| -> i32 { row.get(0) }).map_err(|_| ())?.ok_or(())?;
540

541
542
543
        // Login okay, create session!
        let session_token = helpers::make_session_token();
        let csrf_token = helpers::make_csrf_token();
544
        let login_code = helpers::make_login_code(); // TODO: check for collisions
545
        let now = time::get_time();
546

547
548
549
        let query = "INSERT INTO session (session_token, csrf_token, last_login, last_activity, permanent_login,
                                          logincode, grade, is_teacher, managed_by)
                     VALUES ($1, $2, $3, $3, $4, $5, $6, $7, $8)";
550
        self.execute(query, &[&session_token, &csrf_token, &now, &false, &login_code, &0, &false, &group_id]).unwrap();
551

552
        Ok(session_token)
553
554
    }

Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
555
    fn create_group_with_users(&self, mut group: Group) {
556
557
558
559
560
561
        // Generate group ID:
        group.save(self);

        for user in group.members {
            let csrf_token = helpers::make_csrf_token();
            let login_code = helpers::make_login_code(); // TODO: check for collisions
Robert Czechowski's avatar
rustfmt    
Robert Czechowski committed
562

563
564
565
            let query = "INSERT INTO session (firstname, lastname, csrf_token, permanent_login, logincode, grade,
                                              is_teacher, managed_by)
                         VALUES ($1, $2, $3, $4, $5, $6, $7, $8)";
566
567
568
569
570
571
572
573
574
575
            self.execute(query,
                         &[&user.firstname,
                           &user.lastname,
                           &csrf_token,
                           &false,
                           &login_code,
                           &user.grade,
                           &false,
                           &group.id])
                .unwrap();
576
        }
577
578
    }

579
    fn logout(&self, session: &str) {
580
581
582
        let query = "UPDATE session
                     SET session_token = NULL
                     WHERE session_token = $1";
583
        self.execute(query, &[&session]).unwrap();
584
585
586
587
    }

    fn load_submission(&self, session: &SessionUser, task: i32, subtask: Option<&str>) -> Option<Submission> {
        match subtask {
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
            None => {
                let query = "SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
                             FROM submission
                             WHERE task = $1
                             AND session = $2
                             ORDER BY id DESC
                             LIMIT 1";
                self.query_map_one(query, &[&task, &session.id], |row| Submission { id: Some(row.get(0)),
                                                                                    task: task,
                                                                                    session_user: session.id,
                                                                                    grade: row.get(1),
                                                                                    validated: row.get(2),
                                                                                    nonvalidated_grade: row.get(3),
                                                                                    subtask_identifier: None,
                                                                                    value: row.get(4),
                                                                                    date: row.get(5),
                                                                                    needs_validation: row.get(6) })
                    .ok()?
            }
            Some(subtask_id) => {
                let query = "SELECT id, grade, validated, nonvalidated_grade, value, date, needs_validation
                             FROM submission
                             WHERE task = $1
                             AND session = $2
                             AND subtask_identifier = $3
                             ORDER BY id DESC
                             LIMIT 1";
                self.query_map_one(query, &[&task, &session.id, &subtask_id], |row| {
                        Submission { id: Some(row.get(0)),
                                     task: task,
                                     session_user: session.id,
                                     grade: row.get(1),
                                     validated: row.get(2),
                                     nonvalidated_grade: row.get(3),
                                     subtask_identifier: Some(subtask_id.to_string()),
                                     value: row.get(4),
                                     date: row.get(5),
                                     needs_validation: row.get(6) }
                    })
                    .ok()?
            }
629
630
631
632
633
634
635
636
637
638
639
640
641
        }
    }
    fn submit_submission(&self, mut submission: Submission) {
        submission.save(self);

        let mut grade = self.get_grade_by_submission(submission.id.unwrap());
        if grade.grade.is_none() || submission.grade > grade.grade.unwrap() {
            grade.grade = Some(submission.grade);
            grade.validated = false;
            grade.save(self);
        }
    }
    fn get_grade_by_submission(&self, submission_id: i32) -> Grade {
642
643
644
645
646
647
        let query = "SELECT grade.taskgroup, grade.session, grade.grade, grade.validated
                     FROM grade
                     JOIN task ON grade.taskgroup = task.taskgroup
                     JOIN submission ON task.id = submission.task
                     AND grade.session = submission.session
                     WHERE submission.id = $1";
648
649
650
651
652
653
654
        self.query_map_one(query, &[&submission_id], |row| Grade { taskgroup: row.get(0),
                                                                   user: row.get(1),
                                                                   grade: row.get(2),
                                                                   validated: row.get(3) })
            .unwrap_or(None)
            .unwrap_or_else(|| {
                let query = "SELECT task.taskgroup, submission.session
655
656
657
                         FROM submission
                         JOIN task ON task.id = submission.task
                         WHERE submission.id = $1";
658
659
660
661
662
663
664
                self.query_map_one(query, &[&submission_id], |row| Grade { taskgroup: row.get(0),
                                                                           user: row.get(1),
                                                                           grade: None,
                                                                           validated: false })
                    .unwrap()
                    .unwrap() // should this unwrap?
            })
665
666
667
668
    }

    fn get_contest_groups_grades(&self, session_id: i32, contest_id: i32)
                                 -> (Vec<String>, Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)>) {
669
670
671
672
        let query = "SELECT id, name
                     FROM taskgroup
                     WHERE contest = $1
                     ORDER BY id ASC";
673
674
675
        let tasknames: Vec<(i32, String)> =
            self.query_map_many(query, &[&contest_id], |row| (row.get(0), row.get(1))).unwrap();

676
677
678
679
680
681
682
        let mut taskindex: ::std::collections::BTreeMap<i32, usize> = ::std::collections::BTreeMap::new();

        let n_tasks = tasknames.len();
        for (index, (i, _)) in tasknames.iter().enumerate() {
            taskindex.insert(*i, index);
        }

683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
        let query = "SELECT grade.taskgroup, grade.session, grade.grade, grade.validated, usergroup.id, usergroup.name,
                            usergroup.groupcode, usergroup.tag, student.id, student.username, student.logincode,
                            student.firstname, student.lastname
                     FROM grade
                     JOIN taskgroup ON grade.taskgroup = taskgroup.id
                     JOIN session AS student ON grade.session = student.id
                     JOIN usergroup ON student.managed_by = usergroup.id
                     WHERE usergroup.admin = $1
                     AND taskgroup.contest = $2
                     ORDER BY usergroup.id, student.id, taskgroup.id ASC";
        let gradeinfo =
            self.query_map_many(query, &[&session_id, &contest_id], |row| {
                    (Grade { taskgroup: row.get(0), user: row.get(1), grade: row.get(2), validated: row.get(3) },
                     Group { id: Some(row.get(4)),
                             name: row.get(5),
                             groupcode: row.get(6),
                             tag: row.get(7),
                             admin: session_id,
                             members: Vec::new() },
                     UserInfo { id: row.get(8),
                                username: row.get(9),
                                logincode: row.get(10),
                                firstname: row.get(11),
                                lastname: row.get(12) })
                })
                .unwrap();
        let mut gradeinfo_iter = gradeinfo.iter();
710
711

        if let Some(t /*Ok((grade, mut group, mut userinfo))*/) = gradeinfo_iter.next() {
712
            let (grade, mut group, mut userinfo) = t.clone();
713
714
715
716
717
718

            let mut grades: Vec<Grade> = vec![Default::default(); n_tasks];
            let mut users: Vec<(UserInfo, Vec<Grade>)> = Vec::new();
            let mut groups: Vec<(Group, Vec<(UserInfo, Vec<Grade>)>)> = Vec::new();

            let index = grade.taskgroup;
719
            grades[taskindex[&index]] = grade;
720
721

            for ggu in gradeinfo_iter {
722
723
                let (g, gr, ui) = ggu;
                if gr.id != group.id {
724
725
                    users.push((userinfo, grades));
                    userinfo = ui.clone();
726
727
                    grades = vec![Default::default(); n_tasks];

728
729
                    groups.push((group, users));
                    group = gr.clone();
730
731
                    users = Vec::new();
                } else if ui.id != userinfo.id {
732
733
                    users.push((userinfo, grades));
                    userinfo = ui.clone();
734
                    grades = vec![Default::default(); n_tasks];
735
                }
736
                let index = g.taskgroup;
737
                grades[taskindex[&index]] = *g;
738
            }
739
740
            users.push((userinfo, grades));
            groups.push((group, users));
741
742
743
744
745
746
747

            (tasknames.iter().map(|(_, name)| name.clone()).collect(), groups)
        } else {
            (Vec::new(), Vec::new()) // should those be default filled?
        }
    }
    fn get_contest_user_grades(&self, session_token: &str, contest_id: i32) -> Vec<Grade> {
748
749
750
751
        let query = "SELECT id, name
                     FROM taskgroup
                     WHERE contest = $1
                     ORDER BY id ASC";
752
753
        let tasknames: Vec<(i32, String)> =
            self.query_map_many(query, &[&contest_id], |row| (row.get(0), row.get(1))).unwrap();
754
755
756
757
758
759
760
        let mut taskindex: ::std::collections::BTreeMap<i32, usize> = ::std::collections::BTreeMap::new();

        let n_tasks = tasknames.len();
        for (index, (i, _)) in tasknames.iter().enumerate() {
            taskindex.insert(*i, index);
        }

761
762
763
764
765
766
767
768
769
770
771
772
773
774
        let query = "SELECT grade.taskgroup, grade.session, grade.grade, grade.validated
                     FROM grade
                     JOIN taskgroup ON grade.taskgroup = taskgroup.id
                     JOIN session ON session.id = grade.session
                     WHERE session.session_token = $1
                     AND taskgroup.contest = $2
                     ORDER BY taskgroup.id ASC";
        let gradeinfo =
            self.query_map_many(query, &[&session_token, &contest_id], |row| Grade { taskgroup: row.get(0),
                                                                                     user: row.get(1),
                                                                                     grade: row.get(2),
                                                                                     validated: row.get(3) })
                .unwrap();
        let gradeinfo_iter = gradeinfo.iter();
775
776
777
778
779

        let mut grades: Vec<Grade> = vec![Default::default(); n_tasks];

        for g in gradeinfo_iter {
            let index = g.taskgroup;
780
            grades[taskindex[&index]] = *g;
781
782
783
784
785
786
        }

        grades
    }

    fn get_taskgroup_user_grade(&self, session_token: &str, taskgroup_id: i32) -> Grade {
787
        let query = "SELECT grade.taskgroup, grade.session, grade.grade, grade.validated
788
789
790
791
                     FROM grade
                     JOIN session ON session.id = grade.session
                     WHERE session.session_token = $1
                     AND grade.taskgroup = $2";
792
793
794
795
796
797
        self.query_map_one(query, &[&session_token, &taskgroup_id], |row| Grade { taskgroup: row.get(0),
                                                                                  user: row.get(1),
                                                                                  grade: row.get(2),
                                                                                  validated: row.get(3) })
            .unwrap_or(None)
            .unwrap_or_default()
798
799
800
    }

    fn get_contest_list(&self) -> Vec<Contest> {
801
        let query = "SELECT id, location, filename, name, duration, public, start_date, end_date, min_grade, max_grade
802
803
                     FROM contest
                     ORDER BY id";
804
805
806
807
808
809
810
811
        self.query_map_many(query, &[], |row| Contest { id: Some(row.get(0)),
                                                        location: row.get(1),
                                                        filename: row.get(2),
                                                        name: row.get(3),
                                                        duration: row.get(4),
                                                        public: row.get(5),
                                                        start: row.get(6),
                                                        end: row.get(7),
812
813
                                                        min_grade: row.get(8),
                                                        max_grade: row.get(9),
814
815
                                                        taskgroups: Vec::new() })
            .unwrap()
816
817
818
    }

    fn get_contest_by_id(&self, contest_id: i32) -> Contest {
819
        let query = "SELECT location, filename, name, duration, public, start_date, end_date, min_grade, max_grade
820
821
                     FROM contest
                     WHERE id = $1";
822
823
824
825
826
827
828
829
        self.query_map_one(query, &[&contest_id], |row| Contest { id: Some(contest_id),
                                                                  location: row.get(0),
                                                                  filename: row.get(1),
                                                                  name: row.get(2),
                                                                  duration: row.get(3),
                                                                  public: row.get(4),
                                                                  start: row.get(5),
                                                                  end: row.get(6),
830
831
                                                                  min_grade: row.get(7),
                                                                  max_grade: row.get(8),
832
                                                                  taskgroups: Vec::new() })
833
834
835
836
837
            .unwrap()
            .unwrap() // TODO: Should return Option?
    }

    fn get_contest_by_id_complete(&self, contest_id: i32) -> Contest {
838
        let query = "SELECT contest.location, contest.filename, contest.name, contest.duration, contest.public,
839
840
                            contest.start_date, contest.end_date, contest.min_grade, contest.max_grade, taskgroup.id,
                            taskgroup.name, task.id, task.location, task.stars
841
842
843
844
                     FROM contest
                     JOIN taskgroup ON contest.id = taskgroup.contest
                     JOIN task ON taskgroup.id = task.taskgroup
                     WHERE contest.id = $1
845
                     ORDER BY taskgroup.positionalnumber";
846
847
848
849
850
851
852
853
854
855
        let taskgroupcontest =
            self.query_map_many(query, &[&contest_id], |row| {
                    (Contest { id: Some(contest_id),
                               location: row.get(0),
                               filename: row.get(1),
                               name: row.get(2),
                               duration: row.get(3),
                               public: row.get(4),
                               start: row.get(5),
                               end: row.get(6),
856
857
                               min_grade: row.get(7),
                               max_grade: row.get(8),
858
                               taskgroups: Vec::new() },
859
                     Taskgroup { id: Some(row.get(9)),
860
                                 contest: contest_id,
861
                                 name: row.get(10),
862
863
                                 positionalnumber: None,
                                 tasks: Vec::new() },
864
                     Task { id: Some(row.get(11)), taskgroup: row.get(9), location: row.get(11), stars: row.get(13) })
865
866
867
                })
                .unwrap();
        let mut taskgroupcontest_iter = taskgroupcontest.into_iter();
868
869
870
871

        let (mut contest, mut taskgroup, task) = taskgroupcontest_iter.next().unwrap();
        taskgroup.tasks.push(task);
        for tgc in taskgroupcontest_iter {
872
873
874
875
            let (_, tg, t) = tgc;
            if tg.id != taskgroup.id {
                contest.taskgroups.push(taskgroup);
                taskgroup = tg;
876
            }
877
            taskgroup.tasks.push(t);
878
879
880
881
882
883
        }
        contest.taskgroups.push(taskgroup);
        contest
    }

    fn get_contest_by_id_partial(&self, contest_id: i32) -> Contest {
884
        let query = "SELECT contest.location, contest.filename, contest.name, contest.duration, contest.public,
885
886
                            contest.start_date, contest.end_date, contest.min_grade, contest.max_grade, taskgroup.id,
                            taskgroup.name
887
888
889
                     FROM contest
                     JOIN taskgroup ON contest.id = taskgroup.contest
                     WHERE contest.id = $1";
890
891
892
893
894
895
896
897
898
        let taskgroupcontest = self.query_map_many(query, &[&contest_id], |row| {
                                       (Contest { id: Some(contest_id),
                                                  location: row.get(0),
                                                  filename: row.get(1),
                                                  name: row.get(2),
                                                  duration: row.get(3),
                                                  public: row.get(4),
                                                  start: row.get(5),
                                                  end: row.get(6),
899
900
                                                  min_grade: row.get(7),
                                                  max_grade: row.get(8),
901
                                                  taskgroups: Vec::new() },
902
                                        Taskgroup { id: Some(row.get(9)),
903
                                                    contest: contest_id,
904
                                                    name: row.get(10),
905
906
907
908
                                                    positionalnumber: None,
                                                    tasks: Vec::new() })
                                   })
                                   .unwrap();
909
        let mut taskgroupcontest_iter = taskgroupcontest.into_iter();
910
911
912
913

        let (mut contest, taskgroup) = taskgroupcontest_iter.next().unwrap();
        contest.taskgroups.push(taskgroup);
        for tgc in taskgroupcontest_iter {
914
915
            let (_, tg) = tgc;
            contest.taskgroups.push(tg);
916
917
918
919
920
        }
        contest
    }

    fn get_participation(&self, session: &str, contest_id: i32) -> Option<Participation> {
921
922
923
924
925
        let query = "SELECT session, start_date
                     FROM participation
                     JOIN session ON session.id = session
                     WHERE session.session_token = $1
                     AND contest = $2";
926
927
928
929
        self.query_map_one(query, &[&session, &contest_id], |row| Participation { contest: contest_id,
                                                                                  user: row.get(0),
                                                                                  start: row.get(1) })
            .ok()?
930
931
    }
    fn new_participation(&self, session: &str, contest_id: i32) -> Result<Participation, ()> {
932
933
934
935
936
        let query = "SELECT session, start_date
                     FROM participation
                     JOIN session ON session.id = session
                     WHERE session.session_token = $1
                     AND contest = $2";
937
938
        match self.query_map_one(query, &[&session, &contest_id], |_| {}).map_err(|_| ())? {
            Some(()) => Err(()),
939
940
941
            None => {
                let now = time::get_time();
                self.execute(
942
                             "INSERT INTO participation (contest, session, start_date)
943
                     SELECT $1, id, $2 FROM session WHERE session_token = $3",
944
945
946
                             &[&contest_id, &now, &session],
                )
                    .unwrap();
947
948
949
950
951
952

                Ok(self.get_participation(session, contest_id).unwrap()) // TODO: This errors if not logged in …
            }
        }
    }
    fn get_task_by_id(&self, task_id: i32) -> Task {
953
954
955
        let query = "SELECT location, stars, taskgroup
                     FROM task
                     WHERE id = $1";
956
957
958
959
        self.query_map_one(query, &[&task_id], |row| Task { id: Some(task_id),
                                                            taskgroup: row.get(2),
                                                            location: row.get(0),
                                                            stars: row.get(1) })
960
961
962
963
            .unwrap()
            .unwrap()
    }
    fn get_task_by_id_complete(&self, task_id: i32) -> (Task, Taskgroup, Contest) {
964
965
        let query = "SELECT task.location, task.stars, taskgroup.id, taskgroup.name, contest.id, contest.location,
                            contest.filename, contest.name, contest.duration, contest.public, contest.start_date,
966
                            contest.end_date, contest.min_grade, contest.max_grade
967
968
969
970
                     FROM contest
                     JOIN taskgroup ON taskgroup.contest = contest.id
                     JOIN task ON task.taskgroup = taskgroup.id
                     WHERE task.id = $1";
971
972
        self.query_map_one(query, &[&task_id], |row| {
                (Task { id: Some(task_id), taskgroup: row.get(2), location: row.get(0), stars: row.get(1) },
973
974
975
976
977
                 Taskgroup { id: Some(row.get(2)),
                             contest: row.get(4),
                             name: row.get(3),
                             positionalnumber: None,
                             tasks: Vec::new() },
978
979
980
981
982
983
984
985
                 Contest { id: Some(row.get(4)),
                           location: row.get(5),
                           filename: row.get(6),
                           name: row.get(7),
                           duration: row.get(8),
                           public: row.get(9),
                           start: row.get(10),
                           end: row.get(11),
986
987
                           min_grade: row.get(12),
                           max_grade: row.get(13),
988
989
990
991
                           taskgroups: Vec::new() })
            })
            .unwrap()
            .unwrap()
992
993
994
995
    }

    fn get_submission_to_validate(&self, tasklocation: &str, subtask: Option<&str>) -> i32 {
        match subtask {
996
            Some(st) => {
997
998
999
1000
1001
1002
1003
                let query = "SELECT id
                             FROM submission
                             JOIN task ON submission.task = task.id
                             WHERE task.location = $1
                             AND subtask_identifier = $2
                             AND needs_validation = 1
                             LIMIT 1";
1004
1005
1006
                self.query_map_one(query, &[&tasklocation, &st], |row| row.get(0)).unwrap().unwrap()
            }
            None => {
1007
1008
1009
1010
1011
1012
                let query = "SELECT id
                             FROM submission
                             JOIN task ON submission.task = task.id
                             WHERE task.location = $1
                             AND needs_validation = 1
                             LIMIT 1";
1013
1014
                self.query_map_one(query, &[&tasklocation], |row| row.get(0)).unwrap().unwrap()
            }
1015
1016
1017
1018
        }
    }

    fn find_next_submission_to_validate(&self, userid: i32, taskgroupid: i32) {
1019
1020
1021
1022
1023
1024
1025
        let query = "SELECT id, validated
                     FROM submission
                     JOIN task ON submission.task = task.id
                     WHERE task.taskgroup = $1
                     AND submission.session = $2
                     ORDER BY value DESC id DESC
                     LIMIT 1";
1026
1027
        let (id, validated): (i32, bool) =
            self.query_map_one(query, &[&taskgroupid, &userid], |row| (row.get(0), row.get(1))).unwrap().unwrap();
1028
        if !validated {
1029
1030
1031
            let query = "UPDATE submission
                         SET needs_validation = 1
                         WHERE id = $1";
1032
            self.execute(query, &[&id]).unwrap();
1033
1034
1035
1036
1037
1038
        }
    }

    fn add_group(&self, group: &mut Group) { group.save(self); }

    fn get_groups(&self, session_id: i32) -> Vec<Group> {
1039
1040
1041
        let query = "SELECT id, name, groupcode, tag
                     FROM usergroup
                     WHERE admin = $1";
1042
1043
1044
1045
1046
1047
        self.query_map_many(query, &[&session_id], |row| Group { id: Some(row.get(0)),
                                                                 name: row.get(1),
                                                                 groupcode: row.get(2),
                                                                 tag: row.get(3),
                                                                 admin: session_id,
                                                                 members: Vec::new() })
1048
1049
1050
1051
1052
1053
            .unwrap()
    }
    fn get_groups_complete(&self, _session_id: i32) -> Vec<Group> {
        unimplemented!();
    }
    fn get_group_complete(&self, group_id: i32) -> Option<Group> {
1054
1055
1056
        let query = "SELECT name, groupcode, tag, admin
                     FROM usergroup
                     WHERE id  = $1";
1057
1058
1059
1060
1061
1062
        let mut group = self.query_map_one(query, &[&group_id], |row| Group { id: Some(group_id),
                                                                              name: row.get(0),
                                                                              groupcode: row.get(1),
                                                                              tag: row.get(2),
                                                                              admin: row.get(3),
                                                                              members: Vec::new() })
1063
1064
1065
                            .unwrap()
                            .unwrap(); // TODO handle error

1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
        let query = "SELECT id, session_token, csrf_token, last_login, last_activity, permanent_login, username,
                            password, logincode, email, email_unconfirmed, email_confirmationcode, firstname, lastname,
                            street, zip, city, nation, grade, is_teacher, oauth_provider, oauth_foreign_id, salt
                     FROM session
                     WHERE managed_by = $1";
        group.members = self.query_map_many(query, &[&group_id], |row| SessionUser { id: row.get(0),
                                                                                     session_token: row.get(1),
                                                                                     csrf_token: row.get(2),
                                                                                     last_login: row.get(3),
                                                                                     last_activity: row.get(4),
                                                                                     permanent_login: row.get(5),

                                                                                     username: row.get(6),
                                                                                     password: row.get(7),
                                                                                     salt: row.get(22),
                                                                                     logincode: row.get(8),
                                                                                     email: row.get(9),
                                                                                     email_unconfirmed: row.get(10),
                                                                                     email_confirmationcode:
                                                                                         row.get(11),

                                                                                     firstname: row.get(12),
                                                                                     lastname: row.get(13),
                                                                                     street: row.get(14),
                                                                                     zip: row.get(15),
                                                                                     city: row.get(16),
                                                                                     nation: row.get(17),
                                                                                     grade: row.get(18),

                                                                                     is_teacher: row.get(19),
                                                                                     managed_by: Some(group_id),

                                                                                     oauth_provider: row.get(20),
                                                                                     oauth_foreign_id: row.get(21) })
                            .unwrap();
1101
1102
        Some(group)
    }
1103

1104
    fn reset_all_contest_visibilities(&self) { self.execute("UPDATE contest SET public = $1", &[&false]).unwrap(); }
1105
}