medal/
db_objects.rs

1/*  medal                                                                                                            *\
2 *  Copyright (C) 2022  Bundesweite Informatikwettbewerbe, Robert Czechowski                                                            *
3 *                                                                                                                   *
4 *  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero        *
5 *  General Public License as published  by the Free Software Foundation, either version 3 of the License, or (at    *
6 *  your option) any later version.                                                                                  *
7 *                                                                                                                   *
8 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the       *
9 *  implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public      *
10 *  License for more details.                                                                                        *
11 *                                                                                                                   *
12 *  You should have received a copy of the GNU Affero General Public License along with this program.  If not, see   *
13\*  <http://www.gnu.org/licenses/>.                                                                                  */
14
15extern crate time;
16
17use self::time::{Duration, Timespec};
18
19#[derive(Clone, Debug)]
20pub struct SessionUser {
21    pub id: i32,
22    pub session_token: Option<String>, // delete this to log out
23    pub csrf_token: String,
24    pub last_login: Option<Timespec>,
25    pub last_activity: Option<Timespec>,
26    pub account_created: Option<Timespec>,
27
28    pub username: Option<String>,
29    pub password: Option<String>,
30    pub salt: Option<String>,
31    pub logincode: Option<String>,
32    pub email: Option<String>,
33    pub email_unconfirmed: Option<String>,
34    pub email_confirmationcode: Option<String>,
35
36    pub firstname: Option<String>,
37    pub lastname: Option<String>,
38    pub street: Option<String>,
39    pub zip: Option<String>,
40    pub city: Option<String>,
41    pub nation: Option<String>,
42    pub grade: i32,
43    pub sex: Option<i32>,
44    pub anonymous: bool,
45    pub additional_contest_time: Option<i32>,
46    pub data_protection_clearance: i32,
47
48    pub is_admin: Option<bool>,
49    pub is_teacher: bool,
50    pub managed_by: Option<i32>,
51    pub school_name: Option<String>,
52
53    pub oauth_foreign_id: Option<String>,
54    pub oauth_provider: Option<String>,
55}
56
57pub enum Sex {
58    #[allow(dead_code)]
59    NotStated = 0,
60    Male      = 1,
61    Female    = 2,
62    Diverse   = 3,
63    #[allow(dead_code)]
64    Other     = 4,
65}
66
67// Short version for display
68#[derive(Clone, Default)]
69pub struct UserInfo {
70    pub id: i32,
71    pub username: Option<String>,
72    pub logincode: Option<String>,
73    pub firstname: Option<String>,
74    pub lastname: Option<String>,
75    pub grade: i32,
76    pub annotation: Option<String>,
77    pub team: Option<i32>,
78}
79
80#[derive(Clone, Debug)]
81pub struct Group {
82    pub id: Option<i32>,
83    pub name: String,
84    pub groupcode: String,
85    pub tag: String,
86    pub admins: Vec<i32>,
87    pub members: Vec<SessionUser>,
88}
89
90#[derive(Debug)]
91pub struct RequiredContest {
92    pub filename: String,
93    pub required_stars: i32,
94}
95
96#[derive(Debug)]
97pub struct Contest {
98    pub id: Option<i32>,
99    pub location: String,
100    pub filename: String,
101    pub name: String,
102    pub duration: i32,
103    pub public: bool,
104    pub start: Option<Timespec>,
105    pub end: Option<Timespec>,
106    pub review_start: Option<Timespec>,
107    pub review_end: Option<Timespec>,
108    pub min_grade: Option<i32>,
109    pub max_grade: Option<i32>,
110    pub max_teamsize: Option<i32>,
111    pub positionalnumber: Option<i32>,
112    pub requires_login: Option<bool>,
113    pub requires_contests: Vec<RequiredContest>,
114    pub protected: bool,
115    pub secret: Option<String>,
116    pub message: Option<String>,
117    pub image: Option<String>,
118    pub language: Option<String>,
119    pub category: Option<String>,
120    pub colour: Option<String>,
121    pub standalone_task: Option<bool>,
122    pub tags: Vec<String>,
123    pub stickers: Vec<(String, i32)>,
124    pub taskgroups: Vec<Taskgroup>,
125}
126
127#[derive(Debug)]
128pub struct Taskgroup {
129    pub id: Option<i32>,
130    pub contest: i32,
131    pub name: String,
132    pub active: bool,
133    pub positionalnumber: Option<i32>,
134    pub tasks: Vec<Task>,
135}
136
137#[derive(Debug)]
138pub struct Task {
139    pub id: Option<i32>,
140    pub taskgroup: i32,
141    pub location: String,
142    pub language: Option<String>,
143    pub stars: i32,
144}
145
146pub struct Submission {
147    pub id: Option<i32>,
148    pub user: i32,
149    pub task: i32,
150    pub grade: i32,
151    pub validated: bool,
152    pub nonvalidated_grade: i32,
153    pub needs_validation: bool,
154    pub autosave: bool,
155    pub latest: bool,
156    pub highest_grade_latest: bool,
157    pub subtask_identifier: Option<String>,
158    pub value: String,
159    pub date: Timespec,
160}
161
162#[derive(Clone, Copy, Default, Debug)]
163pub struct Grade {
164    pub taskgroup: i32,
165    pub user: i32,
166    pub grade: Option<i32>,
167    pub validated: bool,
168}
169
170pub struct Participation {
171    pub contest: i32,
172    pub user: i32,
173    pub start: Timespec,
174    pub team: Option<i32>,
175    pub annotation: Option<String>,
176}
177
178pub trait HasId {
179    fn get_id(&self) -> Option<i32>;
180    fn set_id(&mut self, id: i32);
181}
182impl HasId for Submission {
183    fn get_id(&self) -> Option<i32> { self.id }
184    fn set_id(&mut self, id: i32) { self.id = Some(id); }
185}
186impl HasId for Task {
187    fn get_id(&self) -> Option<i32> { self.id }
188    fn set_id(&mut self, id: i32) { self.id = Some(id); }
189}
190impl HasId for Taskgroup {
191    fn get_id(&self) -> Option<i32> { self.id }
192    fn set_id(&mut self, id: i32) { self.id = Some(id); }
193}
194impl HasId for Contest {
195    fn get_id(&self) -> Option<i32> { self.id }
196    fn set_id(&mut self, id: i32) { self.id = Some(id); }
197}
198impl HasId for Group {
199    fn get_id(&self) -> Option<i32> { self.id }
200    fn set_id(&mut self, id: i32) { self.id = Some(id); }
201}
202
203impl SessionUser {
204    pub fn minimal(id: i32, session_token: String, csrf_token: String) -> Self {
205        SessionUser { id,
206                      session_token: Some(session_token),
207                      csrf_token,
208                      last_login: None,
209                      last_activity: None,
210                      account_created: Some(time::get_time()),
211                      // müssen die überhaupt außerhalb der datenbankabstraktion sichtbar sein?
212                      username: None,
213                      password: None,
214                      salt: None,
215                      logincode: None,
216                      email: None,
217                      email_unconfirmed: None,
218                      email_confirmationcode: None,
219
220                      firstname: None,
221                      lastname: None,
222                      street: None,
223                      zip: None,
224                      city: None,
225                      nation: None,
226                      grade: 0,
227                      sex: None,
228                      anonymous: false,
229                      additional_contest_time: None,
230                      data_protection_clearance: 0,
231
232                      is_admin: Some(false),
233                      is_teacher: false,
234                      managed_by: None,
235                      school_name: None,
236
237                      oauth_foreign_id: None,
238                      oauth_provider: None }
239    }
240
241    pub fn group_user_stub() -> Self {
242        SessionUser { id: 0,
243                      session_token: None,
244                      csrf_token: "".to_string(),
245                      last_login: None,
246                      last_activity: None,
247                      account_created: Some(time::get_time()),
248
249                      username: None,
250                      password: None,
251                      salt: None,
252                      logincode: None,
253                      email: None,
254                      email_unconfirmed: None,
255                      email_confirmationcode: None,
256
257                      firstname: None,
258                      lastname: None,
259                      street: None,
260                      zip: None,
261                      city: None,
262                      nation: None,
263                      grade: 0,
264                      sex: None,
265                      anonymous: false,
266                      additional_contest_time: None,
267                      data_protection_clearance: 0,
268
269                      is_admin: None,
270                      is_teacher: false,
271                      managed_by: None,
272                      school_name: None,
273
274                      oauth_foreign_id: None,
275                      oauth_provider: None }
276    }
277
278    pub fn is_alive(&self) -> bool {
279        let duration = Duration::hours(9); // TODO: hardcoded value, should be moved into constant or sth
280        let now = time::get_time();
281        if let Some(last_activity) = self.last_activity {
282            now - last_activity < duration
283        } else {
284            false
285        }
286    }
287
288    pub fn is_logged_in(&self) -> bool {
289        (self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some()) && self.is_alive()
290    }
291
292    pub fn is_teacher(&self) -> bool { self.is_teacher }
293
294    pub fn is_admin(&self) -> bool { self.is_admin == Some(true) }
295
296    pub fn ensure_alive(self) -> Option<Self> {
297        if self.is_alive() {
298            Some(self)
299        } else {
300            None
301        }
302    }
303
304    pub fn ensure_logged_in(self) -> Option<Self> {
305        if self.is_logged_in() {
306            Some(self)
307        } else {
308            None
309        }
310    }
311
312    pub fn ensure_teacher(self) -> Option<Self> {
313        if self.is_logged_in() && self.is_teacher() {
314            Some(self)
315        } else {
316            None
317        }
318    }
319
320    pub fn ensure_admin(self) -> Option<Self> {
321        if self.is_logged_in() && self.is_admin() {
322            Some(self)
323        } else {
324            None
325        }
326    }
327
328    pub fn ensure_teacher_or_admin(self) -> Option<Self> {
329        if self.is_logged_in() && (self.is_admin() || self.is_teacher()) {
330            Some(self)
331        } else {
332            None
333        }
334    }
335}
336
337impl Taskgroup {
338    pub fn new(name: String, positionalnumber: Option<i32>) -> Self {
339        Taskgroup { id: None, contest: 0, name, active: true, positionalnumber, tasks: Vec::new() }
340    }
341}
342
343impl Task {
344    pub fn new(mut location: String, mut language: Option<String>, stars: i32) -> Self {
345        if language.is_none() {
346            (language, location) = match location.chars().next() {
347                Some('B') => (Some("blockly".to_string()), (&location[1..]).to_string()),
348                Some('P') => (Some("python".to_string()), (&location[1..]).to_string()),
349                _ => (None, location),
350            };
351        }
352
353        Task { id: None, taskgroup: 0, location, language, stars }
354    }
355}
356
357pub trait OptionSession {
358    fn ensure_alive(self) -> Self;
359    fn ensure_logged_in(self) -> Self;
360    #[allow(dead_code)]
361    fn ensure_teacher(self) -> Self;
362    #[allow(dead_code)]
363    fn ensure_admin(self) -> Self;
364}
365
366impl OptionSession for Option<SessionUser> {
367    fn ensure_alive(self) -> Self { self?.ensure_alive() }
368    fn ensure_logged_in(self) -> Self { self?.ensure_logged_in() }
369    fn ensure_teacher(self) -> Self { self?.ensure_teacher() }
370    fn ensure_admin(self) -> Self { self?.ensure_admin() }
371}