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