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    pub min_grade: i32,
95    pub max_grade: i32,
96}
97
98#[derive(Debug)]
99pub struct Contest {
100    pub id: Option<i32>,
101    pub location: String,
102    pub filename: String,
103    pub name: String,
104    pub duration: i32,
105    pub public: bool,
106    pub start: Option<Timespec>,
107    pub end: Option<Timespec>,
108    pub review_start: Option<Timespec>,
109    pub review_end: Option<Timespec>,
110    pub min_grade: Option<i32>,
111    pub max_grade: Option<i32>,
112    pub max_teamsize: Option<i32>,
113    pub positionalnumber: Option<i32>,
114    pub requires_login: Option<bool>,
115    pub requires_contests: Vec<RequiredContest>,
116    pub protected: bool,
117    pub secret: Option<String>,
118    pub message: Option<String>,
119    pub image: Option<String>,
120    pub language: Option<String>,
121    pub category: Option<String>,
122    pub colour: Option<String>,
123    pub standalone_task: Option<bool>,
124    pub tags: Vec<String>,
125    pub stickers: Vec<(String, i32)>,
126    pub taskgroups: Vec<Taskgroup>,
127}
128
129#[derive(Debug)]
130pub struct Taskgroup {
131    pub id: Option<i32>,
132    pub contest: i32,
133    pub name: String,
134    pub active: bool,
135    pub positionalnumber: Option<i32>,
136    pub tasks: Vec<Task>,
137}
138
139#[derive(Debug)]
140pub struct Task {
141    pub id: Option<i32>,
142    pub taskgroup: i32,
143    pub location: String,
144    pub language: Option<String>,
145    pub stars: i32,
146}
147
148pub struct Submission {
149    pub id: Option<i32>,
150    pub user: i32,
151    pub task: i32,
152    pub grade: i32,
153    pub validated: bool,
154    pub nonvalidated_grade: i32,
155    pub needs_validation: bool,
156    pub autosave: bool,
157    pub higher_grade: bool,
158    pub latest: bool,
159    pub highest_grade_latest: bool,
160    pub subtask_identifier: Option<String>,
161    pub value: String,
162    pub date: Timespec,
163}
164
165#[derive(Clone, Copy, Default, Debug)]
166pub struct Grade {
167    pub taskgroup: i32,
168    pub user: i32,
169    pub grade: Option<i32>,
170    pub validated: bool,
171}
172
173pub struct Participation {
174    pub contest: i32,
175    pub user: i32,
176    pub start: Timespec,
177    pub team: Option<i32>,
178    pub annotation: Option<String>,
179}
180
181pub trait HasId {
182    fn get_id(&self) -> Option<i32>;
183    fn set_id(&mut self, id: i32);
184}
185impl HasId for Submission {
186    fn get_id(&self) -> Option<i32> { self.id }
187    fn set_id(&mut self, id: i32) { self.id = Some(id); }
188}
189impl HasId for Task {
190    fn get_id(&self) -> Option<i32> { self.id }
191    fn set_id(&mut self, id: i32) { self.id = Some(id); }
192}
193impl HasId for Taskgroup {
194    fn get_id(&self) -> Option<i32> { self.id }
195    fn set_id(&mut self, id: i32) { self.id = Some(id); }
196}
197impl HasId for Contest {
198    fn get_id(&self) -> Option<i32> { self.id }
199    fn set_id(&mut self, id: i32) { self.id = Some(id); }
200}
201impl HasId for Group {
202    fn get_id(&self) -> Option<i32> { self.id }
203    fn set_id(&mut self, id: i32) { self.id = Some(id); }
204}
205
206impl SessionUser {
207    pub fn minimal(id: i32, session_token: String, csrf_token: String) -> Self {
208        SessionUser { id,
209                      session_token: Some(session_token),
210                      csrf_token,
211                      last_login: None,
212                      last_activity: None,
213                      account_created: Some(time::get_time()),
214                      // müssen die überhaupt außerhalb der datenbankabstraktion sichtbar sein?
215                      username: None,
216                      password: None,
217                      salt: None,
218                      logincode: None,
219                      email: None,
220                      email_unconfirmed: None,
221                      email_confirmationcode: None,
222
223                      firstname: None,
224                      lastname: None,
225                      street: None,
226                      zip: None,
227                      city: None,
228                      nation: None,
229                      grade: 0,
230                      sex: None,
231                      anonymous: false,
232                      additional_contest_time: None,
233                      data_protection_clearance: 0,
234
235                      is_admin: Some(false),
236                      is_teacher: false,
237                      managed_by: None,
238                      school_name: None,
239
240                      oauth_foreign_id: None,
241                      oauth_provider: None }
242    }
243
244    pub fn group_user_stub() -> Self {
245        SessionUser { id: 0,
246                      session_token: None,
247                      csrf_token: "".to_string(),
248                      last_login: None,
249                      last_activity: None,
250                      account_created: Some(time::get_time()),
251
252                      username: None,
253                      password: None,
254                      salt: None,
255                      logincode: None,
256                      email: None,
257                      email_unconfirmed: None,
258                      email_confirmationcode: None,
259
260                      firstname: None,
261                      lastname: None,
262                      street: None,
263                      zip: None,
264                      city: None,
265                      nation: None,
266                      grade: 0,
267                      sex: None,
268                      anonymous: false,
269                      additional_contest_time: None,
270                      data_protection_clearance: 0,
271
272                      is_admin: None,
273                      is_teacher: false,
274                      managed_by: None,
275                      school_name: None,
276
277                      oauth_foreign_id: None,
278                      oauth_provider: None }
279    }
280
281    pub fn is_alive(&self) -> bool {
282        let duration = Duration::hours(9); // TODO: hardcoded value, should be moved into constant or sth
283        let now = time::get_time();
284        if let Some(last_activity) = self.last_activity {
285            now - last_activity < duration
286        } else {
287            false
288        }
289    }
290
291    pub fn is_logged_in(&self) -> bool {
292        (self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some()) && self.is_alive()
293    }
294
295    pub fn is_teacher(&self) -> bool { self.is_teacher }
296
297    pub fn is_admin(&self) -> bool { self.is_admin == Some(true) }
298
299    pub fn ensure_alive(self) -> Option<Self> {
300        if self.is_alive() {
301            Some(self)
302        } else {
303            None
304        }
305    }
306
307    pub fn ensure_logged_in(self) -> Option<Self> {
308        if self.is_logged_in() {
309            Some(self)
310        } else {
311            None
312        }
313    }
314
315    pub fn ensure_teacher(self) -> Option<Self> {
316        if self.is_logged_in() && self.is_teacher() {
317            Some(self)
318        } else {
319            None
320        }
321    }
322
323    pub fn ensure_admin(self) -> Option<Self> {
324        if self.is_logged_in() && self.is_admin() {
325            Some(self)
326        } else {
327            None
328        }
329    }
330
331    pub fn ensure_teacher_or_admin(self) -> Option<Self> {
332        if self.is_logged_in() && (self.is_admin() || self.is_teacher()) {
333            Some(self)
334        } else {
335            None
336        }
337    }
338}
339
340impl Taskgroup {
341    pub fn new(name: String, positionalnumber: Option<i32>) -> Self {
342        Taskgroup { id: None, contest: 0, name, active: true, positionalnumber, tasks: Vec::new() }
343    }
344}
345
346impl Task {
347    pub fn new(mut location: String, mut language: Option<String>, stars: i32) -> Self {
348        if language.is_none() {
349            (language, location) = match location.chars().next() {
350                Some('B') => (Some("blockly".to_string()), (&location[1..]).to_string()),
351                Some('P') => (Some("python".to_string()), (&location[1..]).to_string()),
352                _ => (None, location),
353            };
354        }
355
356        Task { id: None, taskgroup: 0, location, language, stars }
357    }
358}
359
360pub trait OptionSession {
361    fn ensure_alive(self) -> Self;
362    fn ensure_logged_in(self) -> Self;
363    #[allow(dead_code)]
364    fn ensure_teacher(self) -> Self;
365    #[allow(dead_code)]
366    fn ensure_admin(self) -> Self;
367}
368
369impl OptionSession for Option<SessionUser> {
370    fn ensure_alive(self) -> Self { self?.ensure_alive() }
371    fn ensure_logged_in(self) -> Self { self?.ensure_logged_in() }
372    fn ensure_teacher(self) -> Self { self?.ensure_teacher() }
373    fn ensure_admin(self) -> Self { self?.ensure_admin() }
374}