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