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