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 subtask_identifier: Option<String>,
144    pub value: String,
145    pub date: Timespec,
146}
147
148#[derive(Clone, Copy, Default, Debug)]
149pub struct Grade {
150    pub taskgroup: i32,
151    pub user: i32,
152    pub grade: Option<i32>,
153    pub validated: bool,
154}
155
156pub struct Participation {
157    pub contest: i32,
158    pub user: i32,
159    pub start: Timespec,
160    pub team: Option<i32>,
161}
162
163pub trait HasId {
164    fn get_id(&self) -> Option<i32>;
165    fn set_id(&mut self, id: i32);
166}
167impl HasId for Submission {
168    fn get_id(&self) -> Option<i32> { self.id }
169    fn set_id(&mut self, id: i32) { self.id = Some(id); }
170}
171impl HasId for Task {
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 Taskgroup {
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 Contest {
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 Group {
184    fn get_id(&self) -> Option<i32> { self.id }
185    fn set_id(&mut self, id: i32) { self.id = Some(id); }
186}
187
188impl SessionUser {
189    pub fn minimal(id: i32, session_token: String, csrf_token: String) -> Self {
190        SessionUser { id,
191                      session_token: Some(session_token),
192                      csrf_token,
193                      last_login: None,
194                      last_activity: None,
195                      account_created: Some(time::get_time()),
196                      // müssen die überhaupt außerhalb der datenbankabstraktion sichtbar sein?
197                      username: None,
198                      password: None,
199                      salt: None,
200                      logincode: None,
201                      email: None,
202                      email_unconfirmed: None,
203                      email_confirmationcode: None,
204
205                      firstname: None,
206                      lastname: None,
207                      street: None,
208                      zip: None,
209                      city: None,
210                      nation: None,
211                      grade: 0,
212                      sex: None,
213                      anonymous: false,
214
215                      is_admin: Some(false),
216                      is_teacher: false,
217                      managed_by: None,
218                      school_name: None,
219
220                      oauth_foreign_id: None,
221                      oauth_provider: None }
222    }
223
224    pub fn group_user_stub() -> Self {
225        SessionUser { id: 0,
226                      session_token: None,
227                      csrf_token: "".to_string(),
228                      last_login: None,
229                      last_activity: None,
230                      account_created: Some(time::get_time()),
231
232                      username: None,
233                      password: None,
234                      salt: None,
235                      logincode: None,
236                      email: None,
237                      email_unconfirmed: None,
238                      email_confirmationcode: None,
239
240                      firstname: None,
241                      lastname: None,
242                      street: None,
243                      zip: None,
244                      city: None,
245                      nation: None,
246                      grade: 0,
247                      sex: None,
248                      anonymous: false,
249
250                      is_admin: None,
251                      is_teacher: false,
252                      managed_by: None,
253                      school_name: None,
254
255                      oauth_foreign_id: None,
256                      oauth_provider: None }
257    }
258
259    pub fn is_alive(&self) -> bool {
260        let duration = Duration::hours(9); // TODO: hardcoded value, should be moved into constant or sth
261        let now = time::get_time();
262        if let Some(last_activity) = self.last_activity {
263            now - last_activity < duration
264        } else {
265            false
266        }
267    }
268
269    pub fn is_logged_in(&self) -> bool {
270        (self.password.is_some() || self.logincode.is_some() || self.oauth_foreign_id.is_some()) && self.is_alive()
271    }
272
273    pub fn is_teacher(&self) -> bool { self.is_teacher }
274
275    pub fn is_admin(&self) -> bool { self.is_admin == Some(true) }
276
277    pub fn ensure_alive(self) -> Option<Self> {
278        if self.is_alive() {
279            Some(self)
280        } else {
281            None
282        }
283    }
284
285    pub fn ensure_logged_in(self) -> Option<Self> {
286        if self.is_logged_in() {
287            Some(self)
288        } else {
289            None
290        }
291    }
292
293    pub fn ensure_teacher(self) -> Option<Self> {
294        if self.is_logged_in() && self.is_teacher() {
295            Some(self)
296        } else {
297            None
298        }
299    }
300
301    pub fn ensure_admin(self) -> Option<Self> {
302        if self.is_logged_in() && self.is_admin() {
303            Some(self)
304        } else {
305            None
306        }
307    }
308
309    pub fn ensure_teacher_or_admin(self) -> Option<Self> {
310        if self.is_logged_in() && (self.is_admin() || self.is_teacher()) {
311            Some(self)
312        } else {
313            None
314        }
315    }
316}
317
318impl Taskgroup {
319    pub fn new(name: String, positionalnumber: Option<i32>) -> Self {
320        Taskgroup { id: None, contest: 0, name, active: true, positionalnumber, tasks: Vec::new() }
321    }
322}
323
324impl Task {
325    pub fn new(mut location: String, mut language: Option<String>, stars: i32) -> Self {
326        if language.is_none() {
327            (language, location) = match location.chars().next() {
328                Some('B') => (Some("blockly".to_string()), (&location[1..]).to_string()),
329                Some('P') => (Some("python".to_string()), (&location[1..]).to_string()),
330                _ => (None, location),
331            };
332        }
333
334        Task { id: None, taskgroup: 0, location, language, stars }
335    }
336}
337
338pub trait OptionSession {
339    fn ensure_alive(self) -> Self;
340    fn ensure_logged_in(self) -> Self;
341    #[allow(dead_code)]
342    fn ensure_teacher(self) -> Self;
343    #[allow(dead_code)]
344    fn ensure_admin(self) -> Self;
345}
346
347impl OptionSession for Option<SessionUser> {
348    fn ensure_alive(self) -> Self { self?.ensure_alive() }
349    fn ensure_logged_in(self) -> Self { self?.ensure_logged_in() }
350    fn ensure_teacher(self) -> Self { self?.ensure_teacher() }
351    fn ensure_admin(self) -> Self { self?.ensure_admin() }
352}