1use time;
16
17use config::OauthProvider;
18use db_conn::MedalConnection;
19#[cfg(feature = "signup")]
20use db_conn::SignupResult;
21use db_objects::OptionSession;
22use db_objects::SessionUser;
23use db_objects::{Contest, Grade, Group, Participation, Submission, Taskgroup};
24use helpers;
25use webauthn_rs::prelude as webauthn;
26use webfw_iron::{json_val, to_json};
27
28#[derive(Serialize, Deserialize)]
29pub struct SubTaskInfo {
30 pub id: i32,
31 pub linktext: String,
32 pub active: bool,
33 pub greyout: bool,
34}
35
36#[derive(Serialize, Deserialize)]
37pub struct TaskInfo {
38 pub name: String,
39 pub subtasks: Vec<SubTaskInfo>,
40}
41
42#[derive(Clone, Serialize, Deserialize)]
43pub struct ContestInfo {
44 pub id: i32,
45 pub name: String,
46 pub duration: i32,
47 pub public: bool,
48 pub requires_login: bool,
49 pub image: Option<String>,
50 pub language: Option<String>,
51 pub category: Option<String>,
52 pub team_participation: bool,
53 pub tags: Vec<String>,
54}
55
56#[derive(Clone, Debug)]
57pub enum MedalError {
58 NotLoggedIn,
59 AccessDenied,
60 CsrfCheckFailed,
61 SessionTimeout,
62 DatabaseError,
63 ConfigurationError,
64 DatabaseConnectionError,
65 PasswordHashingError,
66 UnmatchedPasswords,
67 NotFound,
68 AccountIncomplete,
69 UnknownId,
70 OauthError(String),
71 WebauthnError,
72 ErrorWithJson(json_val::Map<String, json_val::Value>),
73}
74
75pub struct LoginInfo {
76 pub password_login: bool,
77 pub self_url: Option<String>,
78 pub oauth_providers: Option<Vec<OauthProvider>>,
79}
80
81type MedalValue = (String, json_val::Map<String, json_val::Value>);
82type MedalResult<T> = Result<T, MedalError>;
83type MedalValueResult = MedalResult<MedalValue>;
84
85type JsonValue = json_val::Map<String, json_val::Value>;
86type JsonValueResult = MedalResult<JsonValue>;
87
88fn fill_user_data_prefix(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>, prefix: &str) {
89 data.insert(prefix.to_string() + "username", to_json(&session.username));
90 data.insert(prefix.to_string() + "firstname", to_json(&session.firstname));
91 data.insert(prefix.to_string() + "lastname", to_json(&session.lastname));
92 data.insert(prefix.to_string() + "teacher", to_json(&session.is_teacher));
93 data.insert(prefix.to_string() + "is_teacher", to_json(&session.is_teacher));
94 data.insert(prefix.to_string() + "teacher_schoolname", to_json(&session.school_name));
95 data.insert(prefix.to_string() + "admin", to_json(&session.is_admin));
96 data.insert(prefix.to_string() + "is_admin", to_json(&session.is_admin));
97 data.insert(prefix.to_string() + "logged_in", to_json(&session.is_logged_in()));
98 data.insert(prefix.to_string() + "csrf_token", to_json(&session.csrf_token));
99 data.insert(prefix.to_string() + "sex",
100 to_json(&(match session.sex {
101 Some(0) | None => "/",
102 Some(1) => "m",
103 Some(2) => "w",
104 Some(3) => "d",
105 Some(4) => "…",
106 _ => "?",
107 })));
108}
109
110fn fill_user_data(session: &SessionUser, data: &mut json_val::Map<String, serde_json::Value>) {
111 fill_user_data_prefix(session, data, "");
112
113 data.insert("parent".to_string(), to_json(&"base"));
114 data.insert("medal_version".to_string(), to_json(&env!("GIT_VERSION")));
115}
116
117fn fill_oauth_data(login_info: LoginInfo, data: &mut json_val::Map<String, serde_json::Value>) {
118 let mut oauth_links: Vec<(String, String, String)> = Vec::new();
119 if let Some(oauth_providers) = login_info.oauth_providers {
120 for oauth_provider in oauth_providers {
121 oauth_links.push((oauth_provider.provider_id.to_owned(),
122 oauth_provider.login_link_text.to_owned(),
123 oauth_provider.url.to_owned()));
124 }
125 }
126
127 data.insert("self_url".to_string(), to_json(&login_info.self_url));
128 data.insert("oauth_links".to_string(), to_json(&oauth_links));
129
130 data.insert("password_login".to_string(), to_json(&login_info.password_login));
131}
132
133fn grade_to_string(grade: i32) -> String {
134 match grade {
135 0 => "Noch kein Schüler".to_string(),
136 n @ 1..=10 => format!("{}", n),
137 11 => "11 (G8)".to_string(),
138 12 => "12 (G8)".to_string(),
139 111 => "11 (G9)".to_string(),
140 112 => "12 (G9)".to_string(),
141 113 => "13 (G9)".to_string(),
142 114 => "Berufsschule".to_string(),
143 255 => "Kein Schüler mehr".to_string(),
144 _ => "?".to_string(),
145 }
146}
147
148fn common_prefix<'a>(a: &'a str, b: &'a str) -> &'a str {
149 let mut ac = a.char_indices();
150 let mut bc = b.char_indices();
151
152 let mut ai;
153 let mut bi;
154 loop {
155 ai = ac.next();
156 bi = bc.next();
157 if ai != bi {
158 break;
159 }
160 if ai.is_none() {
161 break;
162 }
163 }
164 if ai.is_none() {
165 return a;
166 }
167 if bi.is_none() {
168 return b;
169 }
170
171 a.get(..(ai.unwrap().0)).unwrap()
172}
173
174pub fn index<T: MedalConnection>(conn: &T, session_token: Option<String>, login_info: LoginInfo) -> MedalValueResult {
175 let mut data = json_val::Map::new();
176
177 if let Some(token) = session_token {
178 if let Some(session) = conn.get_session(&token) {
179 fill_user_data(&session, &mut data);
180
181 if session.logincode.is_some() && session.firstname.is_none() {
182 return Err(MedalError::AccountIncomplete);
183 }
184 }
185 }
186
187 fill_oauth_data(login_info, &mut data);
188
189 let now = time::get_time();
190 let contest_list = conn.get_contest_list();
191 let mut contests_running: Vec<(String, String, (i64, i64))> =
192 contest_list.iter()
193 .filter(|c| c.public)
194 .filter(|c| c.duration != 0 || c.category == Some("contest".to_string()))
195 .filter(|c| !c.requires_login.unwrap_or(false) || c.category.is_some())
196 .filter(|c| c.end.map(|end| now <= end).unwrap_or(false))
197 .filter(|c| c.start.map(|start| now >= start).unwrap_or(true))
198 .map(|c| {
199 (c.name.clone(),
200 if let Some(cat) = c.category.as_ref() {
201 format!("{}/{}", cat, c.id.unwrap_or(0))
202 } else {
203 format!("{}", c.id.unwrap_or(0))
204 },
205 {
206 let t = c.end.unwrap().sec - now.sec;
207 (t / 60 / 60 / 24, t / 60 / 60 % 24)
208 })
209 })
210 .collect();
211
212 let mut i = 0;
213 while i < contests_running.len() {
214 let mut count = 0;
215 while i + 1 < contests_running.len() {
216 if contests_running[i].2 == contests_running[i + 1].2 {
217 contests_running[i].0 = common_prefix(&contests_running[i].0, &contests_running[i + 1].0).to_string();
220 count += 1;
221 contests_running.remove(i + 1);
222 } else {
223 break;
224 }
225 }
226 if count > 0 {
227 contests_running[i].0 = format!("{}… ({}x)", contests_running[i].0, count + 1);
228 contests_running[i].1 = "?filter=current".to_string();
229 }
230 i += 1;
231 }
232
233 if contests_running.len() > 0 {
234 data.insert("current_contests".to_string(), to_json(&contests_running));
235 }
236
237 let mut contests_comming: Vec<(String, String, (i64, i64))> =
238 contest_list.iter()
239 .filter(|c| c.public)
240 .filter(|c| c.duration != 0 || c.category == Some("contest".to_string()))
241 .filter(|c| !c.requires_login.unwrap_or(false) || c.category.is_some())
242 .filter(|c| c.start.map(|start| now <= start).unwrap_or(false))
243 .map(|c| {
244 (c.name.clone(),
245 if let Some(cat) = c.category.as_ref() {
246 format!("{}/{}", cat, c.id.unwrap_or(0))
247 } else {
248 format!("{}", c.id.unwrap_or(0))
249 },
250 {
251 let t = c.start.unwrap().sec - now.sec;
252 (t / 60 / 60 / 24, t / 60 / 60 % 24)
253 })
254 })
255 .collect();
256
257 let mut i = 0;
258 while i < contests_comming.len() {
259 let mut count = 0;
260 while i + 1 < contests_comming.len() {
261 if contests_comming[i].2 == contests_comming[i + 1].2 {
262 contests_comming[i].0 = common_prefix(&contests_comming[i].0, &contests_comming[i + 1].0).to_string();
265 count += 1;
266 contests_comming.remove(i + 1);
267 } else {
268 break;
269 }
270 }
271 if count > 0 {
272 contests_comming[i].0 = format!("{}… ({}x)", contests_comming[i].0, count + 1);
273 contests_comming[i].1 = "?filter=current".to_string();
274 }
275 i += 1;
276 }
277
278 if contests_comming.len() > 0 {
279 data.insert("upcoming_contests".to_string(), to_json(&contests_comming));
280 }
281
282 data.insert("parent".to_string(), to_json(&"base"));
283 data.insert("index".to_string(), to_json(&true));
284 Ok(("index".to_owned(), data))
285}
286
287pub fn show_login<T: MedalConnection>(conn: &T, session_token: Option<String>, login_info: LoginInfo)
288 -> (String, json_val::Map<String, json_val::Value>) {
289 let mut data = json_val::Map::new();
290
291 if let Some(token) = session_token {
292 if let Some(session) = conn.get_session(&token) {
293 fill_user_data(&session, &mut data);
294 }
295 }
296
297 fill_oauth_data(login_info, &mut data);
298
299 data.insert("parent".to_string(), to_json(&"base"));
300 ("login".to_owned(), data)
301}
302
303pub fn status<T: MedalConnection>(conn: &T, config_secret: Option<String>, given_secret: Option<String>)
304 -> MedalResult<String> {
305 if config_secret == given_secret {
306 Ok(conn.get_debug_information())
307 } else {
308 Err(MedalError::AccessDenied)
309 }
310}
311
312pub fn debug<T: MedalConnection>(conn: &T, session_token: Option<String>)
313 -> (String, json_val::Map<String, json_val::Value>) {
314 let mut data = json_val::Map::new();
315
316 if let Some(token) = session_token {
317 if let Some(session) = conn.get_session(&token) {
318 data.insert("known_session".to_string(), to_json(&true));
319 data.insert("session_id".to_string(), to_json(&session.id));
320 data.insert("now_timestamp".to_string(), to_json(&time::get_time().sec));
321 if let Some(last_activity) = session.last_activity {
322 data.insert("session_timestamp".to_string(), to_json(&last_activity.sec));
323 data.insert("timediff".to_string(), to_json(&(time::get_time() - last_activity).num_seconds()));
324 }
325 if session.is_alive() {
326 data.insert("alive_session".to_string(), to_json(&true));
327 if session.is_logged_in() {
328 data.insert("logged_in".to_string(), to_json(&true));
329 data.insert("username".to_string(), to_json(&session.username));
330 data.insert("firstname".to_string(), to_json(&session.firstname));
331 data.insert("lastname".to_string(), to_json(&session.lastname));
332 data.insert("teacher".to_string(), to_json(&session.is_teacher));
333 data.insert("oauth_provider".to_string(), to_json(&session.oauth_provider));
334 data.insert("oauth_id".to_string(), to_json(&session.oauth_foreign_id));
335 data.insert("logincode".to_string(), to_json(&session.logincode));
336 data.insert("managed_by".to_string(), to_json(&session.managed_by));
337 }
338 }
339 }
340 data.insert("session".to_string(), to_json(&token));
341 } else {
342 data.insert("session".to_string(), to_json(&"No session token given"));
343 }
344
345 ("debug".to_owned(), data)
346}
347
348pub fn debug_create_session<T: MedalConnection>(conn: &T, session_token: Option<String>) {
349 if let Some(token) = session_token {
350 conn.get_session_or_new(&token).unwrap();
351 }
352}
353
354#[derive(PartialEq, Eq, Debug)]
355pub enum ContestVisibility {
356 All,
357 Open,
358 Current,
359 LoginRequired,
360 StandaloneTask,
361 WithSecret(String),
362}
363
364pub fn show_contests<T: MedalConnection>(conn: &T, session_token: &str, category: Option<String>,
365 login_info: LoginInfo, visibility: ContestVisibility, is_results: bool)
366 -> MedalValueResult {
367 let mut data = json_val::Map::new();
368
369 let session = conn.get_session_or_new(&session_token).map_err(|_| MedalError::DatabaseConnectionError)?;
370 fill_user_data(&session, &mut data);
371
372 if session.is_logged_in() {
373 data.insert("can_start".to_string(), to_json(&true));
374 }
375
376 fill_oauth_data(login_info, &mut data);
377
378 let contest_list = if is_results {
379 conn.get_contest_list_with_group_member_participations(session.id)
380 } else {
381 conn.get_contest_list()
382 };
383
384 let now = time::get_time();
385 let v: Vec<ContestInfo> =
386 contest_list.iter()
387 .filter(|c| category.as_ref().map(|cat| c.category.as_ref() == Some(cat)).unwrap_or(true))
388 .filter(|c| c.public || matches!(visibility, ContestVisibility::WithSecret(_)))
389 .filter(|c| {
390 if let ContestVisibility::WithSecret(secret) = &visibility {
391 c.secret.as_ref() == Some(secret)
392 } else {
393 true
394 }
395 })
396 .filter(|c| {
397 (!c.standalone_task.unwrap_or(false))
398 || visibility == ContestVisibility::StandaloneTask
399 || category == Some("standalone_task".to_string())
400 })
401 .filter(|c| c.standalone_task.unwrap_or(false) || visibility != ContestVisibility::StandaloneTask)
402 .filter(|c| {
403 c.end.map(|end| now <= end).unwrap_or(true)
404 || (visibility == ContestVisibility::All && (c.category.is_none() || is_results))
405 })
406 .filter(|c| c.duration == 0 || visibility != ContestVisibility::Open)
407 .filter(|c| c.duration != 0 || visibility != ContestVisibility::Current)
408 .filter(|c| c.requires_login.unwrap_or(false) || visibility != ContestVisibility::LoginRequired)
409 .filter(|c| {
410 !c.requires_login.unwrap_or(false)
411 || visibility == ContestVisibility::LoginRequired
412 || visibility == ContestVisibility::All
413 })
414 .map(|c| ContestInfo { id: c.id.unwrap(),
415 name: c.name.clone(),
416 duration: c.duration,
417 public: c.public,
418 requires_login: c.requires_login.unwrap_or(false),
419 image: c.image.as_ref().map(|i| format!("/{}{}", c.location, i)),
420 language: c.language.clone(),
421 category: c.category.clone(),
422 team_participation: false,
423 tags: c.tags.clone() })
424 .collect();
425
426 if category.is_some() {
427 data.insert("contests".to_string(), to_json(&v));
428 } else {
429 let contests_training: Vec<ContestInfo> =
430 v.clone().into_iter().filter(|c| !c.requires_login).filter(|c| c.duration == 0).collect();
431 let contests_contest: Vec<ContestInfo> =
432 v.clone().into_iter().filter(|c| !c.requires_login).filter(|c| c.duration != 0).collect();
433 let contests_challenge: Vec<ContestInfo> = v.into_iter().filter(|c| c.requires_login).collect();
434
435 data.insert("contests_training".to_string(), to_json(&contests_training));
436 data.insert("contests_contest".to_string(), to_json(&contests_contest));
437 data.insert("contests_challenge".to_string(), to_json(&contests_challenge));
438
439 data.insert("contests_training_header".to_string(), to_json(&"Trainingsaufgaben"));
440 data.insert("contests_contest_header".to_string(), to_json(&"Wettbewerbe"));
441 data.insert("contests_challenge_header".to_string(), to_json(&"Herausforderungen"));
442
443 if visibility == ContestVisibility::StandaloneTask {
444 data.insert("contests_training_header".to_string(), to_json(&"Einzelne Aufgaben ohne Wertung"));
445 }
446 }
447
448 if let ContestVisibility::WithSecret(secret) = visibility {
449 data.insert("secret".to_string(), to_json(&secret));
450 data.insert("has_secret".to_string(), to_json(&true));
451 }
452
453 Ok(("contests".to_owned(), data))
454}
455
456fn generate_subtaskstars(tg: &Taskgroup, grade: &Grade, ast: Option<i32>) -> Vec<SubTaskInfo> {
457 let mut subtaskinfos = Vec::new();
458 let mut not_print_yet = true;
459 for st in &tg.tasks {
460 let mut blackstars: usize = 0;
461 if not_print_yet && st.stars >= grade.grade.unwrap_or(0) {
462 blackstars = grade.grade.unwrap_or(0) as usize;
463 not_print_yet = false;
464 }
465
466 let greyout = not_print_yet && st.stars < grade.grade.unwrap_or(0);
467 let active = ast.is_some() && st.id == ast;
468 let linktext = format!("{}{}",
469 str::repeat("★", blackstars as usize),
470 str::repeat("☆", st.stars as usize - blackstars as usize));
471 let si = SubTaskInfo { id: st.id.unwrap(), linktext, active, greyout };
472
473 subtaskinfos.push(si);
474 }
475 subtaskinfos
476}
477
478#[derive(Serialize, Deserialize)]
479pub struct ContestStartConstraints {
480 pub contest_not_begun: bool,
481 pub contest_over: bool,
482 pub contest_running: bool,
483 pub grade_too_low: bool,
484 pub grade_too_high: bool,
485 pub grade_matching: bool,
486}
487
488fn check_contest_qualification<T: MedalConnection>(conn: &T, session: &SessionUser, contest: &Contest) -> Option<bool> {
489 let required_contests = contest.requires_contest.as_ref()?.split(',');
491
492 for req_contest in required_contests {
493 if conn.has_participation_by_contest_file(session.id, &contest.location, req_contest) {
494 return Some(true);
495 }
496 }
497
498 Some(false)
499}
500
501fn check_contest_constraints(session: &SessionUser, contest: &Contest) -> ContestStartConstraints {
502 let now = time::get_time();
503 let student_grade = session.grade % 100 - if session.grade / 100 == 1 { 1 } else { 0 };
504
505 let contest_not_begun = contest.start.map(|start| now < start).unwrap_or(false);
506 let contest_over = contest.end.map(|end| now > end).unwrap_or(false);
507 let grade_too_low =
508 contest.min_grade.map(|min_grade| student_grade < min_grade && !session.is_teacher).unwrap_or(false);
509 let grade_too_high =
510 contest.max_grade.map(|max_grade| student_grade > max_grade && !session.is_teacher).unwrap_or(false);
511
512 let contest_running = !contest_not_begun && !contest_over;
513 let grade_matching = !grade_too_low && !grade_too_high;
514
515 ContestStartConstraints { contest_not_begun,
516 contest_over,
517 contest_running,
518 grade_too_low,
519 grade_too_high,
520 grade_matching }
521}
522
523#[derive(Serialize, Deserialize)]
524pub struct ContestTimeInfo {
525 pub passed_secs_total: i64,
526 pub left_secs_total: i64,
527 pub left_mins_total: i64,
528 pub left_hour: i64,
529 pub left_min: i64,
530 pub left_sec: i64,
531 pub has_timelimit: bool,
532 pub is_time_left: bool,
533 pub exempt_from_timelimit: bool,
534 pub can_still_compete: bool,
535 pub review_has_timelimit: bool,
536 pub has_future_review: bool,
537 pub has_review_end: bool,
538 pub is_review: bool,
539 pub can_still_compete_or_review: bool,
540
541 pub until_review_start_day: i64,
542 pub until_review_start_hour: i64,
543 pub until_review_start_min: i64,
544
545 pub until_review_end_day: i64,
546 pub until_review_end_hour: i64,
547 pub until_review_end_min: i64,
548}
549
550fn check_contest_time_left(session: &SessionUser, contest: &Contest, participation: &Participation) -> ContestTimeInfo {
551 let now = time::get_time();
552 let passed_secs_total = now.sec - participation.start.sec;
553 if passed_secs_total < 0 {
554 }
556 let left_secs_total = i64::from(contest.duration) * 60 - passed_secs_total;
557
558 let is_time_left = contest.duration == 0 || left_secs_total >= 0;
559 let exempt_from_timelimit = session.is_teacher() || session.is_admin();
560
561 let can_still_compete = is_time_left || exempt_from_timelimit;
562
563 let review_has_timelimit = contest.review_end.is_none() && contest.review_start.is_some();
564 let has_future_review = (contest.review_start.is_some() || contest.review_end.is_some())
565 && contest.review_end.map(|end| end > now).unwrap_or(true);
566 let has_review_end = contest.review_end.is_some();
567 let is_review = !can_still_compete
568 && (contest.review_start.is_some() || contest.review_end.is_some())
569 && contest.review_start.map(|start| now >= start).unwrap_or(true)
570 && contest.review_end.map(|end| now <= end).unwrap_or(true);
571
572 let until_review_start = contest.review_start.map(|start| start.sec - now.sec).unwrap_or(0);
573 let until_review_end = contest.review_end.map(|end| end.sec - now.sec).unwrap_or(0);
574
575 ContestTimeInfo { passed_secs_total,
576 left_secs_total,
577 left_mins_total: left_secs_total / 60,
578 left_hour: left_secs_total / (60 * 60),
579 left_min: (left_secs_total / 60) % 60,
580 left_sec: left_secs_total % 60,
581 has_timelimit: contest.duration != 0,
582 is_time_left,
583 exempt_from_timelimit,
584 can_still_compete,
585 review_has_timelimit,
586 has_future_review,
587 has_review_end,
588 is_review,
589 can_still_compete_or_review: can_still_compete || is_review,
590
591 until_review_start_day: until_review_start / (60 * 60 * 24),
592 until_review_start_hour: (until_review_start / (60 * 60)) % 24,
593 until_review_start_min: (until_review_start / 60) % 60,
594
595 until_review_end_day: until_review_end / (60 * 60 * 24),
596 until_review_end_hour: (until_review_end / (60 * 60)) % 24,
597 until_review_end_min: (until_review_end / 60) % 60 }
598}
599
600pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str,
601 query_string: Option<String>, login_info: LoginInfo, secret: Option<String>)
602 -> MedalResult<Result<MedalValue, i32>> {
603 let session = conn.get_session_or_new(&session_token).unwrap();
604
605 if session.logincode.is_some() && session.firstname.is_none() {
606 return Err(MedalError::AccountIncomplete);
607 }
608
609 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
610
611 let mut opt_part = conn.get_participation(session.id, contest_id);
612 let mut grades = if let Some(part) = opt_part.as_ref() {
613 if let Some(team) = part.team {
614 conn.get_contest_user_grades(team, contest_id)
615 } else {
616 conn.get_contest_user_grades(session.id, contest_id)
617 }
618 } else {
619 conn.get_contest_user_grades(session.id, contest_id)
620 };
621
622 let ci = ContestInfo { id: contest.id.unwrap(),
623 name: contest.name.clone(),
624 duration: contest.duration,
625 public: contest.public,
626 requires_login: contest.requires_login.unwrap_or(false),
627 image: None,
628 language: None,
629 category: contest.category.clone(),
630 team_participation: contest.max_teamsize.map(|size| size > 1).unwrap_or(false),
631 tags: Vec::new() };
632
633 let mut data = json_val::Map::new();
634 data.insert("parent".to_string(), to_json(&"base"));
635 data.insert("empty".to_string(), to_json(&"empty"));
636 data.insert("contest".to_string(), to_json(&ci));
637 data.insert("title".to_string(), to_json(&ci.name));
638 data.insert("message".to_string(), to_json(&contest.message));
639 fill_oauth_data(login_info, &mut data);
640
641 if secret.is_some() && secret != contest.secret {
642 return Err(MedalError::AccessDenied);
643 }
644
645 let has_secret = contest.secret.is_some();
646 let mut require_secret = false;
647 if has_secret {
648 data.insert("secret_field".to_string(), to_json(&true));
649
650 if secret.is_some() {
651 data.insert("secret_field_prefill".to_string(), to_json(&secret));
652 } else {
653 require_secret = true;
654 }
655 }
656
657 let constraints = check_contest_constraints(&session, &contest);
658 let is_qualified = check_contest_qualification(conn, &session, &contest).unwrap_or(true);
659
660 let has_tasks = contest.taskgroups.len() > 0;
661 let can_start = constraints.contest_running
662 && constraints.grade_matching
663 && is_qualified
664 && (has_tasks || has_secret)
665 && (session.is_logged_in() || contest.secret.is_some() && !contest.requires_login.unwrap_or(false));
666
667 let has_duration = contest.duration > 0;
668
669 data.insert("constraints".to_string(), to_json(&constraints));
670 data.insert("is_qualified".to_string(), to_json(&is_qualified));
671 data.insert("has_duration".to_string(), to_json(&has_duration));
672 data.insert("can_start".to_string(), to_json(&can_start));
673 data.insert("has_tasks".to_string(), to_json(&has_tasks));
674 data.insert("no_tasks".to_string(), to_json(&!has_tasks));
675
676 if opt_part.is_none()
681 && contest.duration == 0
682 && constraints.contest_running
683 && constraints.grade_matching
684 && !require_secret
685 && contest.requires_login != Some(true)
686 {
687 conn.new_participation(session.id, contest_id, None).map_err(|_| MedalError::AccessDenied)?;
688 opt_part = Some(Participation { contest: contest_id,
689 user: session.id,
690 start: time::get_time(),
691 team: None,
692 annotation: None });
693 }
694
695 let now = time::get_time();
696 if let Some(start) = contest.start {
697 if now < start {
698 let until = start - now;
699 data.insert("time_until_start".to_string(),
700 to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
701 }
702 }
703
704 if let Some(end) = contest.end {
705 if now < end {
706 let until = end - now;
707 data.insert("time_until_end".to_string(),
708 to_json(&[until.num_days(), until.num_hours() % 24, until.num_minutes() % 60]));
709 }
710 }
711
712 if session.is_logged_in() {
713 data.insert("logged_in".to_string(), to_json(&true));
714 data.insert("username".to_string(), to_json(&session.username));
715 data.insert("firstname".to_string(), to_json(&session.firstname));
716 data.insert("lastname".to_string(), to_json(&session.lastname));
717 data.insert("teacher".to_string(), to_json(&session.is_teacher));
718 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
719 }
720
721 if let Some(participation) = opt_part {
722 let time_info = check_contest_time_left(&session, &contest, &participation);
723 data.insert("time_info".to_string(), to_json(&time_info));
724
725 let time_left_formatted =
726 format!("{}:{:02}:{:02}", time_info.left_hour, time_info.left_min, time_info.left_sec);
727 data.insert("time_left_formatted".to_string(), to_json(&time_left_formatted));
728
729 if let Some(team) = participation.team {
731 if team != session.id {
733 if time_info.is_time_left {
734 data.insert("block_team_participation".to_string(), to_json(&true));
736 } else {
737 grades = conn.get_contest_user_grades(team, contest_id);
739 }
740 }
741
742 let names =
743 conn.get_team_partners_by_contest_and_teamlead(contest_id, team)
744 .iter()
745 .filter(|user| user.id != session.id)
746 .map(|user| {
747 user.firstname.clone().unwrap_or_default() + " " + &user.lastname.clone().unwrap_or_default()
748 })
749 .collect::<Vec<String>>()
750 .join(", ");
751 data.insert("team_partners".to_string(), to_json(&names));
752 }
753
754 let mut totalgrade = 0;
755 let mut max_totalgrade = 0;
756
757 let mut tasks = Vec::new();
758 for (taskgroup, grade) in contest.taskgroups.into_iter().zip(grades) {
759 let subtaskstars = generate_subtaskstars(&taskgroup, &grade, None);
760 let ti = TaskInfo { name: taskgroup.name, subtasks: subtaskstars };
761 tasks.push(ti);
762
763 totalgrade += grade.grade.unwrap_or(0);
764 max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
765 }
766 let relative_points = if max_totalgrade > 0 { (totalgrade * 100) / max_totalgrade } else { 0 };
767
768 data.insert("tasks".to_string(), to_json(&tasks));
769
770 data.insert("is_started".to_string(), to_json(&true));
771 data.insert("total_points".to_string(), to_json(&totalgrade));
772 data.insert("max_total_points".to_string(), to_json(&max_totalgrade));
773 data.insert("relative_points".to_string(), to_json(&relative_points));
774 data.insert("lean_page".to_string(), to_json(&true));
775
776 if has_tasks && contest.standalone_task.unwrap_or(false) {
777 return Ok(Err(tasks[0].subtasks[0].id));
778 }
779 }
780
781 if let Some(query_string) = query_string {
782 if !query_string.starts_with("bare") {
783 data.insert("not_bare".to_string(), to_json(&true));
784 }
785
786 if query_string.contains("team_participation=no_logincode") {
787 data.insert("team_error".to_string(), to_json(&"Kein Logincode angegeben"));
788 }
789 if query_string.contains("team_participation=invalid_logincode") {
790 data.insert("team_error".to_string(), to_json(&"Ungültiger Logincode angegeben"));
791 }
792 if query_string.contains("team_participation=own_logincode") {
793 data.insert("team_error".to_string(), to_json(&"Eigener Logincode angegeben"));
794 }
795 if query_string.contains("team_participation=logincode_has_participation") {
796 data.insert("team_error".to_string(), to_json(&"Logincode hat diesen Wettbewerb bereits gestartet"));
797 }
798 } else {
799 data.insert("not_bare".to_string(), to_json(&true));
800 }
801
802 Ok(Ok(("contest".to_owned(), data)))
803}
804
805pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
806 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
807 let mut data = json_val::Map::new();
808 fill_user_data(&session, &mut data);
809
810 let (tasknames, resultdata) = conn.get_contest_groups_grades(session.id, contest_id);
811
812 #[derive(Serialize, Deserialize)]
813 struct UserResults {
814 firstname: String,
815 lastname: String,
816 user_id: i32,
817 grade: String,
818 logincode: String,
819 annotation: String,
820 annotation_result: String,
821 results: Vec<String>,
822 }
823
824 #[derive(Serialize, Deserialize)]
825 struct GroupResults {
826 groupname: String,
827 group_id: i32,
828 groupcode: String,
829 user_results: Vec<UserResults>,
830 }
831
832 let mut results: Vec<GroupResults> = Vec::new();
833 let mut has_annotations = false;
834 let mut has_annotations_result = false;
835
836 for (group, groupdata) in resultdata {
837 let mut groupresults: Vec<UserResults> = Vec::new();
838
839 for (user, userdata) in groupdata {
840 let mut userresults: Vec<String> = Vec::new();
841
842 userresults.push(String::new());
843 let mut summe = 0;
844
845 for grade in userdata {
846 if let Some(g) = grade.grade {
847 userresults.push(format!("{}", g));
848 summe += g;
849 } else {
850 userresults.push("–".to_string());
851 }
852 }
853
854 userresults[0] = format!("{}", summe);
855
856 let (annotation, annotation_result) = if let Some(annotation) = user.annotation {
857 let mut split = annotation.split('\x1f');
858
859 (split.next()
860 .filter(|s| s.len() > 0)
861 .map(|s| {
862 has_annotations = true;
863 s.to_string()
864 })
865 .unwrap_or_default(),
866 split.next()
867 .filter(|s| s.len() > 0)
868 .map(|s| {
869 has_annotations_result = true;
870 s.to_string()
871 })
872 .unwrap_or_default())
873 } else {
874 (Default::default(), Default::default())
875 };
876
877 groupresults.push(UserResults { firstname: user.firstname.unwrap_or_else(|| "–".to_string()),
878 lastname: user.lastname.unwrap_or_else(|| "–".to_string()),
879 user_id: user.id,
880 grade: grade_to_string(user.grade),
881 logincode: user.logincode.unwrap_or_else(|| "".to_string()),
882 annotation,
883 annotation_result,
884 results: userresults });
885 }
886
887 results.push(GroupResults { groupname: group.name.to_string(),
888 group_id: group.id.unwrap_or(0),
889 groupcode: group.groupcode,
890 user_results: groupresults });
891 }
892
893 data.insert("taskname".to_string(), to_json(&tasknames));
894 data.insert("result".to_string(), to_json(&results));
895 data.insert("has_annotations".to_string(), to_json(&has_annotations));
896 data.insert("has_annotations_result".to_string(), to_json(&has_annotations_result));
897
898 let c = conn.get_contest_by_id(contest_id).ok_or(MedalError::UnknownId)?;
899 let ci = ContestInfo { id: c.id.unwrap(),
900 name: c.name.clone(),
901 duration: c.duration,
902 public: c.public,
903 requires_login: c.requires_login.unwrap_or(false),
904 image: None,
905 language: None,
906 category: c.category.clone(),
907 team_participation: false,
908 tags: Vec::new() };
909
910 data.insert("contest".to_string(), to_json(&ci));
911 data.insert("contestname".to_string(), to_json(&c.name));
912
913 Ok(("contestresults".to_owned(), data))
914}
915
916pub fn start_contest<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str, csrf_token: &str,
917 secret: Option<String>, logincode_team: Option<String>)
918 -> MedalResult<Result<(), String>> {
919 let session = conn.get_session_or_new(&session_token).unwrap();
921 let contest = conn.get_contest_by_id(contest_id).ok_or(MedalError::UnknownId)?;
922
923 if contest.duration != 0
925 && !session.is_logged_in()
926 && (contest.requires_login.unwrap_or(false) || contest.secret.is_none())
927 {
928 return Err(MedalError::AccessDenied);
929 }
930
931 if session.is_logged_in() && session.csrf_token != csrf_token {
933 return Err(MedalError::CsrfCheckFailed);
934 }
935
936 let constraints = check_contest_constraints(&session, &contest);
938
939 if !(constraints.contest_running && constraints.grade_matching) {
940 return Err(MedalError::AccessDenied);
941 }
942
943 let is_qualified = check_contest_qualification(conn, &session, &contest);
944
945 if is_qualified == Some(false) {
946 return Err(MedalError::AccessDenied);
947 }
948
949 if contest.secret != secret {
950 return Err(MedalError::AccessDenied);
951 }
952
953 if let Some(logincode_team) = logincode_team {
954 match contest.max_teamsize {
955 None => return Err(MedalError::AccessDenied), Some(max_teamsize) => {
957 if max_teamsize < 2 {
958 return Err(MedalError::AccessDenied); }
960 }
961 };
962
963 if logincode_team == "" {
964 return Ok(Err("no_logincode".to_string()));
965 }
966
967 let teampartner = conn.get_user_and_group_by_logincode(&logincode_team);
968 if let Some((teampartner_user, _)) = teampartner {
969 if teampartner_user.id == session.id {
970 return Ok(Err("own_logincode".to_string()));
971 }
972
973 if conn.get_participation(teampartner_user.id, contest_id).is_some() {
974 return Ok(Err("logincode_has_participation".to_string()));
975 }
976
977 if conn.get_participation(session.id, contest_id).is_some() {
978 return Err(MedalError::AccessDenied); }
980
981 let res = conn.new_participation(session.id, contest_id, Some(session.id));
985 let _ = conn.new_participation(teampartner_user.id, contest_id, Some(session.id));
986 return match res {
987 Ok(_) => Ok(Ok(())),
988 _ => Err(MedalError::AccessDenied), };
990 } else {
991 return Ok(Err("invalid_logincode".to_string()));
992 }
993 }
994
995 match conn.new_participation(session.id, contest_id, None) {
997 Ok(_) => Ok(Ok(())),
998 _ => Err(MedalError::AccessDenied), }
1000}
1001
1002pub fn login<T: MedalConnection>(conn: &T, login_data: (String, String), login_info: LoginInfo)
1003 -> Result<String, MedalValue> {
1004 let (username, password) = login_data;
1005
1006 match conn.login(None, &username, &password) {
1007 Ok(session_token) => Ok(session_token),
1008 Err(()) => {
1009 let mut data = json_val::Map::new();
1010 data.insert("reason".to_string(), to_json(&"Login fehlgeschlagen. Bitte erneut versuchen.".to_string()));
1011 data.insert("username".to_string(), to_json(&username));
1012 data.insert("parent".to_string(), to_json(&"base"));
1013
1014 fill_oauth_data(login_info, &mut data);
1015
1016 Err(("login".to_owned(), data))
1017 }
1018 }
1019}
1020
1021pub fn login_with_code<T: MedalConnection>(
1022 conn: &T, code: &str, login_info: LoginInfo)
1023 -> Result<Result<String, String>, (String, json_val::Map<String, json_val::Value>)> {
1024 match conn.login_with_code(None, &code.trim()) {
1025 Ok(session_token) => Ok(Ok(session_token)),
1026 Err(()) => match conn.create_user_with_groupcode(None, &code.trim()) {
1027 Ok(session_token) => Ok(Err(session_token)),
1028 Err(()) => {
1029 let mut data = json_val::Map::new();
1030 data.insert("reason".to_string(), to_json(&"Kein gültiger Code. Bitte erneut versuchen.".to_string()));
1031 data.insert("code".to_string(), to_json(&code));
1032 data.insert("parent".to_string(), to_json(&"base"));
1033
1034 fill_oauth_data(login_info, &mut data);
1035
1036 Err(("login".to_owned(), data))
1037 }
1038 },
1039 }
1040}
1041
1042fn webauthn(self_url: &str) -> webauthn::Webauthn {
1043 use webauthn_rs::prelude::*;
1044
1045 let rp_id = self_url.split("://").nth(1).unwrap().split('/').next().unwrap().split(':').next().unwrap();
1047 let rp_origin = Url::parse(self_url).expect("Invalid URL");
1048 WebauthnBuilder::new(rp_id, &rp_origin).expect("Invalid configuration").build().expect("Invalid configuration")
1049}
1050
1051pub fn register_key_challenge<T: MedalConnection>(conn: &T, self_url: &str, session_token: &str) -> JsonValueResult {
1052 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1053
1054 let webauthn = webauthn(self_url);
1055
1056 let cred_ids = conn.get_all_webauthn_credentials();
1057
1058 let (ccr, skr) =
1060 webauthn.start_passkey_registration(webauthn::Uuid::from_u64_pair(0, session.id as u64),
1061 session.firstname.as_ref().unwrap_or(&("".to_string())),
1062 session.firstname.as_ref().unwrap_or(&("".to_string())),
1063 Some(cred_ids.into_iter()
1064 .map(|x| serde_json::from_str(&format!("\"{}\"", x)).unwrap())
1065 .collect()))
1066 .expect("Failed to start webauthn registration.");
1067
1068 conn.set_webauthn_passkey_registration(session.id, &serde_json::json!(skr).to_string());
1069
1070 let mut data = json_val::Map::new();
1071 data.insert("challenge".to_string(), to_json(&ccr));
1072 Ok(data)
1073}
1074
1075pub fn register_key<T: MedalConnection>(conn: &T, self_url: &str, session_token: &str, csrf_token: &str,
1076 credential: String, name: String)
1077 -> JsonValueResult {
1078 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1079
1080 if session.csrf_token != csrf_token {
1081 return Err(MedalError::CsrfCheckFailed);
1082 }
1083
1084 let registration: String = conn.get_webauthn_passkey_registration(session.id).ok_or(MedalError::WebauthnError)?;
1085
1086 let webauthn = webauthn(self_url);
1087
1088 let registration: webauthn::PasskeyRegistration = serde_json::from_str(®istration).unwrap();
1089 let credential: webauthn::RegisterPublicKeyCredential = serde_json::from_str(&credential).unwrap();
1090
1091 let passkey = webauthn.finish_passkey_registration(&credential, ®istration);
1092
1093 match passkey {
1094 Ok(passkey) => {
1095 if let serde_json::Value::String(cred_id) = serde_json::json!(passkey.cred_id()) {
1096 conn.add_webauthn_passkey(session.id, &cred_id, &serde_json::json!(passkey).to_string(), &name);
1097 } else {
1098 println!("Webauthn: Could not unwrap cred_id from webauthn Passkey");
1099 return Err(MedalError::WebauthnError);
1100 }
1101 }
1102 Err(webauthn::WebauthnError::UserNotVerified) => {
1103 println!("Webauthn: UserNotVerified");
1105 return Err(MedalError::WebauthnError);
1106 }
1107 Err(err) => {
1108 println!("Webauthn: Other error: {:#?}", err);
1109 return Err(MedalError::WebauthnError);
1110 }
1111 }
1112
1113 let data = json_val::Map::new();
1114 Ok(data)
1115}
1116
1117pub fn delete_key<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, token_id: i32)
1118 -> JsonValueResult {
1119 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1120
1121 if session.csrf_token != csrf_token {
1122 return Err(MedalError::CsrfCheckFailed);
1123 }
1124
1125 if !conn.get_webauthn_passkey_names_for_user(session.id).into_iter().any(|(id, _name)| id == token_id) {
1126 return Err(MedalError::NotFound);
1127 }
1128
1129 conn.delete_webauthn_passkey(session.id, token_id);
1130
1131 let data = json_val::Map::new();
1132 Ok(data)
1133}
1134
1135pub fn login_with_key_challenge<T: MedalConnection>(conn: &T, self_url: &str) -> JsonValue {
1136 let passkeys = conn.get_all_webauthn_passkeys()
1137 .into_iter()
1138 .map(|passkey| {
1139 let passkey: webauthn::Passkey = serde_json::from_str(&passkey).unwrap();
1140 passkey
1141 })
1142 .collect::<Vec<webauthn::Passkey>>();
1143
1144 let webauthn = webauthn(self_url);
1145
1146 let (challenge, authentication) = webauthn.start_passkey_authentication(&passkeys).unwrap();
1147
1148 let auth_id = conn.store_webauthn_auth_challenge(&serde_json::json!(challenge).to_string(),
1149 &serde_json::json!(authentication).to_string());
1150
1151 let mut data = json_val::Map::new();
1152 data.insert("id".to_string(), to_json(&auth_id));
1153 data.insert("challenge".to_string(), to_json(&challenge));
1154 data
1155}
1156
1157pub fn login_with_key<T: MedalConnection>(conn: &T, self_url: &str, auth_id: i32, credential: &str)
1158 -> Result<String, String> {
1159 let authentication = conn.get_webauthn_auth_challenge_by_id(auth_id).unwrap();
1160
1161 let authentication: webauthn::PasskeyAuthentication = serde_json::from_str(&authentication).unwrap();
1162 let credential: webauthn::PublicKeyCredential = serde_json::from_str(&credential).unwrap();
1163
1164 let webauthn = webauthn(self_url);
1165
1166 match webauthn.finish_passkey_authentication(&credential, &authentication) {
1167 Err(err) => {
1168 println!("Webauthn: Other error: {:#?}", err);
1169 let mut data = json_val::Map::new();
1170 data.insert("reason".to_string(),
1171 to_json(&"Unbekannter Key. Bitte Key zuerst im Profil registrieren.".to_string()));
1172 Err(serde_json::json!(data).to_string())
1173 }
1174 Ok(authresult) => {
1175 if let serde_json::Value::String(cred_id) = serde_json::json!(authresult.cred_id()) {
1176 match conn.login_with_key(None, &cred_id) {
1177 Ok(session_token) => Ok(session_token),
1178 Err(()) => {
1179 let mut data = json_val::Map::new();
1180 println!("Webauthn: Auth fail");
1181 data.insert("reason".to_string(), to_json(&"Key konnte nicht authentifiziert werden. Bitte über anderen Weg einloggen.".to_string()));
1182 Err(serde_json::json!(data).to_string())
1183 }
1184 }
1185 } else {
1186 panic!("Webauthn: Could not unwrap cred_id from webauthn AuthenticationResult")
1187 }
1188 }
1189 }
1190}
1191
1192pub fn logout<T: MedalConnection>(conn: &T, session_token: Option<String>) {
1193 session_token.map(|token| conn.logout(&token));
1194}
1195
1196#[cfg(feature = "signup")]
1197pub fn signup<T: MedalConnection>(conn: &T, session_token: Option<String>, signup_data: (String, String, String))
1198 -> MedalResult<SignupResult> {
1199 let (username, email, password) = signup_data;
1200
1201 if username == "" || email == "" || password == "" {
1202 return Ok(SignupResult::EmptyFields);
1203 }
1204
1205 let salt = helpers::make_salt();
1206 let hash = helpers::hash_password(&password, &salt)?;
1207
1208 let result = conn.signup(&session_token.unwrap(), &username, &email, hash, &salt);
1209 Ok(result)
1210}
1211
1212#[cfg(feature = "signup")]
1213pub fn signupdata(query_string: Option<String>) -> json_val::Map<String, json_val::Value> {
1214 let mut data = json_val::Map::new();
1215 if let Some(query) = query_string {
1216 if let Some(status) = query.strip_prefix("status=") {
1217 if ["EmailTaken", "UsernameTaken", "UserLoggedIn", "EmptyFields"].contains(&status) {
1218 data.insert((status).to_string(), to_json(&true));
1219 }
1220 }
1221 }
1222 data
1223}
1224
1225pub fn load_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, subtask: Option<String>,
1226 submission_id: Option<i32>)
1227 -> MedalResult<String> {
1228 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1229
1230 match submission_id {
1231 None => match conn.load_submission(&session, task_id, subtask.as_deref()) {
1232 Some(submission) => Ok(submission.value),
1233 None => Ok("{}".to_string()),
1234 },
1235 Some(submission_id) => {
1236 let (submission, _, _, _) =
1237 conn.get_submission_by_id_complete_shallow_contest(submission_id).ok_or(MedalError::UnknownId)?;
1238
1239 if submission.user != session.id && !session.is_admin.unwrap_or(false) {
1241 if let Some((_, Some(group))) = conn.get_user_and_group_by_id(submission.user) {
1242 if !group.admins.contains(&session.id) {
1243 return Err(MedalError::AccessDenied);
1245 }
1246 } else {
1247 return Err(MedalError::AccessDenied);
1249 }
1250 }
1251 Ok(submission.value)
1252 }
1253 }
1254}
1255
1256#[allow(clippy::too_many_arguments)]
1257pub fn save_submission<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, csrf_token: &str,
1258 data: String, grade_percentage: i32, autosave: bool,
1259 subtask: Option<String>)
1260 -> MedalResult<String> {
1261 let session = conn.get_session(&session_token).ensure_alive().ok_or(MedalError::NotLoggedIn)?;
1262
1263 if session.csrf_token != csrf_token {
1264 return Err(MedalError::CsrfCheckFailed);
1265 }
1266
1267 let (t, _, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1268
1269 match conn.get_participation(session.id, contest.id.expect("Value from database")) {
1270 None => return Err(MedalError::AccessDenied),
1271 Some(participation) => {
1272 let time_info = check_contest_time_left(&session, &contest, &participation);
1273 if !time_info.can_still_compete && time_info.left_secs_total < -10 {
1274 return Err(MedalError::AccessDenied);
1275 }
1278 if participation.team.is_some() && participation.team != Some(session.id) {
1279 return Err(MedalError::AccessDenied);
1280 }
1281 }
1282 }
1283
1284 let grade_rounded = ((grade_percentage * t.stars * 10) / 100 + 5) / 10;
1299
1300 let submission = Submission { id: None,
1319 user: session.id,
1320 task: task_id,
1321 grade: grade_rounded,
1322 validated: false,
1323 nonvalidated_grade: grade_rounded,
1324 needs_validation: true,
1325 autosave,
1326 latest: Default::default(), highest_grade_latest: Default::default(), subtask_identifier: subtask,
1329 value: data,
1330 date: time::get_time() };
1331
1332 conn.submit_submission(submission);
1333
1334 Ok("{}".to_string())
1335}
1336
1337pub fn show_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, autosaveinterval: u64)
1338 -> MedalResult<Result<MedalValue, (i32, Option<String>)>> {
1339 let session = conn.get_session_or_new(&session_token).unwrap();
1340
1341 let (t, tg, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1342 let grade = conn.get_taskgroup_user_grade(session.id, tg.id.unwrap()); let tasklist = conn.get_contest_by_id_complete(contest.id.unwrap()).ok_or(MedalError::UnknownId)?; let mut prevtaskgroup: Option<Taskgroup> = None;
1346 let mut nexttaskgroup: Option<Taskgroup> = None;
1347 let mut current_found = false;
1348
1349 let mut subtaskstars = Vec::new();
1350
1351 for taskgroup in tasklist.taskgroups {
1352 if current_found {
1353 nexttaskgroup = Some(taskgroup);
1354 break;
1355 }
1356
1357 if taskgroup.id == tg.id {
1358 current_found = true;
1359 subtaskstars = generate_subtaskstars(&taskgroup, &grade, Some(task_id));
1360 } else {
1361 prevtaskgroup = Some(taskgroup);
1362 }
1363 }
1364
1365 match conn.get_own_participation(session.id, contest.id.expect("Value from database")) {
1366 None => Ok(Err((contest.id.unwrap(), contest.category))),
1367 Some(participation) => {
1368 let mut data = json_val::Map::new();
1369 data.insert("subtasks".to_string(), to_json(&subtaskstars));
1370 data.insert("prevtask".to_string(), to_json(&prevtaskgroup.map(|tg| tg.tasks[0].id)));
1371 data.insert("nexttask".to_string(), to_json(&nexttaskgroup.map(|tg| tg.tasks[0].id))); let time_info = check_contest_time_left(&session, &contest, &participation);
1374 data.insert("time_info".to_string(), to_json(&time_info));
1375
1376 data.insert("time_left_mh_formatted".to_string(),
1377 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1378 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1379
1380 let auto_save_interval_ms = if autosaveinterval > 0 && autosaveinterval < 31536000000 {
1381 autosaveinterval * 1000
1382 } else {
1383 31536000000
1384 };
1385 data.insert("auto_save_interval_ms".to_string(), to_json(&auto_save_interval_ms));
1386
1387 if time_info.can_still_compete && participation.team.is_some() && participation.team != Some(session.id) {
1389 return Ok(Err((contest.id.unwrap(), contest.category)));
1390 }
1391
1392 if time_info.can_still_compete || time_info.is_review {
1393 data.insert("contestname".to_string(), to_json(&contest.name));
1394 data.insert("name".to_string(), to_json(&tg.name));
1395 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1396 data.insert("taskid".to_string(), to_json(&task_id));
1397 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1398 data.insert("contestid".to_string(), to_json(&contest.id));
1399 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1400 data.insert("standalone_task".to_string(), to_json(&contest.standalone_task));
1401 data.insert("category".to_string(), to_json(&contest.category));
1402
1403 let (template, tasklocation) = if let Some(language) = t.language {
1404 match language.as_str() {
1405 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1406 "python" => {
1407 data.insert("tasklang".to_string(), to_json(&"python"));
1408 ("wtask".to_owned(), t.location.as_str())
1409 }
1410 _ => ("task".to_owned(), t.location.as_str()),
1411 }
1412 } else {
1413 match t.location.chars().next() {
1414 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1415 Some('P') => {
1416 data.insert("tasklang".to_string(), to_json(&"python"));
1417 ("wtask".to_owned(), &t.location[1..])
1418 }
1419 _ => ("task".to_owned(), t.location.as_str()),
1420 }
1421 };
1422
1423 let taskpath = format!("{}{}", contest.location, &tasklocation);
1424 data.insert("taskpath".to_string(), to_json(&taskpath));
1425
1426 Ok(Ok((template, data)))
1427 } else {
1428 Ok(Err((contest.id.unwrap(), contest.category)))
1430 }
1431 }
1432 }
1433}
1434
1435pub fn review_task<T: MedalConnection>(conn: &T, task_id: i32, session_token: &str, submission_id: i32)
1436 -> MedalResult<Result<MedalValue, i32>> {
1437 let session = conn.get_session_or_new(&session_token).unwrap();
1438
1439 let (submission, t, tg, contest) =
1440 conn.get_submission_by_id_complete_shallow_contest(submission_id).ok_or(MedalError::UnknownId)?;
1441
1442 let grade = Grade { taskgroup: tg.id.unwrap(),
1445 user: session.id,
1446 grade: Some(submission.grade),
1447 validated: submission.validated };
1448
1449 if submission.user != session.id && !session.is_admin.unwrap_or(false) {
1451 if let Some((_, Some(group))) = conn.get_user_and_group_by_id(submission.user) {
1452 if !group.admins.contains(&session.id) {
1453 return Err(MedalError::AccessDenied);
1455 }
1456 } else {
1457 return Err(MedalError::AccessDenied);
1459 }
1460 }
1461
1462 let subtaskstars = generate_subtaskstars(&tg, &grade, Some(task_id)); let mut data = json_val::Map::new();
1465 data.insert("subtasks".to_string(), to_json(&subtaskstars));
1466
1467 let time_info = ContestTimeInfo { passed_secs_total: 0,
1468 left_secs_total: 0,
1469 left_mins_total: 0,
1470 left_hour: 0,
1471 left_min: 0,
1472 left_sec: 0,
1473 has_timelimit: contest.duration != 0,
1474 is_time_left: false,
1475 exempt_from_timelimit: true,
1476 can_still_compete: false,
1477 review_has_timelimit: false,
1478 has_future_review: false,
1479 has_review_end: false,
1480 is_review: true,
1481 can_still_compete_or_review: true,
1482
1483 until_review_start_day: 0,
1484 until_review_start_hour: 0,
1485 until_review_start_min: 0,
1486
1487 until_review_end_day: 0,
1488 until_review_end_hour: 0,
1489 until_review_end_min: 0 };
1490
1491 data.insert("time_info".to_string(), to_json(&time_info));
1492
1493 data.insert("time_left_mh_formatted".to_string(),
1494 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1495 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1496
1497 data.insert("auto_save_interval_ms".to_string(), to_json(&0));
1498
1499 data.insert("name".to_string(), to_json(&tg.name));
1501 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1502 data.insert("taskid".to_string(), to_json(&task_id));
1503 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1504 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1506
1507 data.insert("submission".to_string(), to_json(&submission_id));
1508
1509 let (template, tasklocation) = if let Some(language) = t.language {
1510 match language.as_str() {
1511 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1512 "python" => {
1513 data.insert("tasklang".to_string(), to_json(&"python"));
1514 ("wtask".to_owned(), t.location.as_str())
1515 }
1516 _ => ("task".to_owned(), t.location.as_str()),
1517 }
1518 } else {
1519 match t.location.chars().next() {
1520 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1521 Some('P') => {
1522 data.insert("tasklang".to_string(), to_json(&"python"));
1523 ("wtask".to_owned(), &t.location[1..])
1524 }
1525 _ => ("task".to_owned(), t.location.as_str()),
1526 }
1527 };
1528
1529 let taskpath = format!("{}{}", contest.location, &tasklocation);
1530 data.insert("taskpath".to_string(), to_json(&taskpath));
1531
1532 Ok(Ok((template, data)))
1533}
1534
1535pub fn preview_task<T: MedalConnection>(conn: &T, task_id: i32) -> MedalResult<Result<MedalValue, i32>> {
1536 let (t, tg, contest) = conn.get_task_by_id_complete(task_id).ok_or(MedalError::UnknownId)?;
1537
1538 if !contest.public
1540 || contest.duration != 0
1541 || contest.requires_contest.is_some()
1542 || contest.requires_login == Some(true)
1543 || contest.standalone_task != Some(true)
1544 {
1545 return Err(MedalError::UnknownId);
1546 }
1547
1548 let time_info = ContestTimeInfo { passed_secs_total: 0,
1549 left_secs_total: 0,
1550 left_mins_total: 0,
1551 left_hour: 0,
1552 left_min: 0,
1553 left_sec: 0,
1554 has_timelimit: contest.duration != 0,
1555 is_time_left: false,
1556 exempt_from_timelimit: true,
1557 can_still_compete: false,
1558 review_has_timelimit: false,
1559 has_future_review: false,
1560 has_review_end: false,
1561 is_review: true,
1562 can_still_compete_or_review: true,
1563
1564 until_review_start_day: 0,
1565 until_review_start_hour: 0,
1566 until_review_start_min: 0,
1567
1568 until_review_end_day: 0,
1569 until_review_end_hour: 0,
1570 until_review_end_min: 0 };
1571
1572 let mut data = json_val::Map::new();
1573
1574 data.insert("time_info".to_string(), to_json(&time_info));
1575
1576 data.insert("time_left_mh_formatted".to_string(),
1577 to_json(&format!("{}:{:02}", time_info.left_hour, time_info.left_min)));
1578 data.insert("time_left_sec_formatted".to_string(), to_json(&format!(":{:02}", time_info.left_sec)));
1579
1580 data.insert("auto_save_interval_ms".to_string(), to_json(&0));
1581
1582 data.insert("contestname".to_string(), to_json(&contest.name));
1583 data.insert("name".to_string(), to_json(&tg.name));
1584 data.insert("title".to_string(), to_json(&format!("Aufgabe „{}“ in {}", &tg.name, &contest.name)));
1585 data.insert("taskid".to_string(), to_json(&task_id));
1586 data.insert("contestid".to_string(), to_json(&contest.id));
1587 data.insert("readonly".to_string(), to_json(&time_info.is_review));
1588 data.insert("preview".to_string(), to_json(&true));
1589
1590 let (template, tasklocation) = if let Some(language) = t.language {
1591 match language.as_str() {
1592 "blockly" => ("wtask".to_owned(), t.location.as_str()),
1593 "python" => {
1594 data.insert("tasklang".to_string(), to_json(&"python"));
1595 ("wtask".to_owned(), t.location.as_str())
1596 }
1597 _ => ("task".to_owned(), t.location.as_str()),
1598 }
1599 } else {
1600 match t.location.chars().next() {
1601 Some('B') => ("wtask".to_owned(), &t.location[1..]),
1602 Some('P') => {
1603 data.insert("tasklang".to_string(), to_json(&"python"));
1604 ("wtask".to_owned(), &t.location[1..])
1605 }
1606 _ => ("task".to_owned(), t.location.as_str()),
1607 }
1608 };
1609
1610 let taskpath = format!("{}{}", contest.location, &tasklocation);
1611 data.insert("taskpath".to_string(), to_json(&taskpath));
1612
1613 Ok(Ok((template, data)))
1614}
1615
1616#[derive(Serialize, Deserialize)]
1617pub struct GroupInfo {
1618 pub id: i32,
1619 pub name: String,
1620 pub tag: String,
1621 pub code: String,
1622}
1623
1624pub fn show_groups<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
1625 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1626
1627 let mut data = json_val::Map::new();
1628 fill_user_data(&session, &mut data);
1629
1630 let v: Vec<GroupInfo> =
1631 conn.get_groups(session.id)
1632 .iter()
1633 .map(|g| GroupInfo { id: g.id.unwrap(),
1634 name: g.name.clone(),
1635 tag: g.tag.clone(),
1636 code: g.groupcode.clone() })
1637 .collect();
1638 data.insert("group".to_string(), to_json(&v));
1639 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1640
1641 Ok(("groups".to_string(), data))
1642}
1643
1644#[derive(Serialize, Deserialize)]
1645pub struct MemberInfo {
1646 pub id: i32,
1647 pub firstname: String,
1648 pub lastname: String,
1649 pub sex: String,
1650 pub grade: String,
1651 pub logincode: String,
1652 pub anonymous: bool,
1653}
1654
1655pub fn show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
1656 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1657 let group = conn.get_group_complete(group_id).unwrap(); let mut data = json_val::Map::new();
1660 fill_user_data(&session, &mut data);
1661
1662 if !group.admins.contains(&session.id) {
1663 return Err(MedalError::AccessDenied);
1664 }
1665
1666 let gi = GroupInfo { id: group.id.unwrap(),
1667 name: group.name.clone(),
1668 tag: group.tag.clone(),
1669 code: group.groupcode.clone() };
1670
1671 let v: Vec<MemberInfo> = group.members
1672 .iter()
1673 .filter_map(|m| {
1674 Some(MemberInfo { id: m.id,
1675 firstname: m.firstname.clone()?,
1676 lastname: m.lastname.clone()?,
1677 sex: (match m.sex {
1678 Some(0) | None => "/",
1679 Some(1) => "m",
1680 Some(2) => "w",
1681 Some(3) => "d",
1682 Some(4) => "…",
1683 _ => "?",
1684 }).to_string(),
1685 grade: grade_to_string(m.grade),
1686 logincode: m.logincode.clone()?,
1687 anonymous: m.anonymous })
1688 })
1689 .collect();
1690
1691 data.insert("group".to_string(), to_json(&gi));
1692 data.insert("member".to_string(), to_json(&v));
1693 data.insert("groupname".to_string(), to_json(&gi.name));
1694
1695 Ok(("group".to_string(), data))
1696}
1697
1698pub fn add_group<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, name: String, tag: String)
1699 -> MedalResult<i32> {
1700 let session = conn.get_session(&session_token)
1701 .ensure_logged_in()
1702 .ok_or(MedalError::AccessDenied)?
1703 .ensure_teacher_or_admin()
1704 .ok_or(MedalError::AccessDenied)?;
1705
1706 if session.csrf_token != csrf_token {
1707 return Err(MedalError::CsrfCheckFailed);
1708 }
1709
1710 let mut groupcode = String::new();
1711 for i in 0..10 {
1712 if i == 9 {
1713 panic!("ERROR: Too many groupcode collisions! Give up ...");
1714 }
1715 groupcode = helpers::make_groupcode();
1716 if !conn.code_exists(&groupcode) {
1717 break;
1718 }
1719 println!("WARNING: Groupcode collision! Retrying ...");
1720 }
1721
1722 let mut group = Group { id: None, name, groupcode, tag, admins: vec![session.id], members: Vec::new() };
1723
1724 conn.add_group(&mut group);
1725
1726 Ok(group.id.unwrap())
1727}
1728
1729pub fn group_csv<T: MedalConnection>(conn: &T, session_token: &str, sex_infos: SexInformation) -> MedalValueResult {
1730 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1731
1732 let mut data = json_val::Map::new();
1733 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1734
1735 data.insert("require_sex".to_string(), to_json(&sex_infos.require_sex));
1736 data.insert("allow_sex_na".to_string(), to_json(&sex_infos.allow_sex_na));
1737 data.insert("allow_sex_diverse".to_string(), to_json(&sex_infos.allow_sex_diverse));
1738 data.insert("allow_sex_other".to_string(), to_json(&sex_infos.allow_sex_other));
1739
1740 Ok(("groupcsv".to_string(), data))
1741}
1742
1743pub fn upload_groups<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, group_data: &str)
1745 -> MedalResult<()> {
1746 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1747
1748 if session.csrf_token != csrf_token {
1749 return Err(MedalError::CsrfCheckFailed);
1750 }
1751
1752 let mut v: Vec<Vec<String>> = serde_json::from_str(group_data).or(Err(MedalError::AccessDenied))?; v.sort_unstable_by(|a, b| a[0].partial_cmp(&b[0]).unwrap());
1754
1755 let mut groupcode = String::new();
1756 let mut name = String::new();
1757 let mut group = Group { id: None,
1758 name: name.clone(),
1759 groupcode,
1760 tag: String::new(),
1761 admins: vec![session.id],
1762 members: Vec::new() };
1763
1764 for line in v {
1765 if name != line[0] {
1766 if name != "" {
1767 conn.update_or_create_group_with_users(group, session.id);
1768 }
1769 name = line[0].clone();
1770
1771 groupcode = String::new();
1772 for i in 0..10 {
1773 if i == 9 {
1774 panic!("ERROR: Too many groupcode collisions! Give up ...");
1775 }
1776 groupcode = helpers::make_groupcode();
1777 if !conn.code_exists(&groupcode) {
1778 break;
1779 }
1780 println!("WARNING: Groupcode collision! Retrying ...");
1781 }
1782
1783 group = Group { id: None,
1784 name: name.clone(),
1785 groupcode,
1786 tag: name.clone(),
1787 admins: vec![session.id],
1788 members: Vec::new() };
1789 }
1790
1791 let mut user = SessionUser::group_user_stub();
1792 user.grade = line[1].parse::<i32>().unwrap_or(0);
1793 user.firstname = Some(line[2].clone());
1794 user.lastname = Some(line[3].clone());
1795
1796 use db_objects::Sex;
1797 match line[4].as_str() {
1798 "m" => user.sex = Some(Sex::Male as i32),
1799 "f" => user.sex = Some(Sex::Female as i32),
1800 "d" => user.sex = Some(Sex::Diverse as i32),
1801 _ => user.sex = None,
1802 }
1803
1804 user.anonymous = line[5] == "y";
1805
1806 group.members.push(user);
1807 }
1808 conn.update_or_create_group_with_users(group, session.id);
1809
1810 Ok(())
1811}
1812
1813pub fn contest_result_csv<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
1814 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1815
1816 let mut data = json_val::Map::new();
1817 data.insert("csrf_token".to_string(), to_json(&session.csrf_token));
1818
1819 Ok(("admin_admissioncsv".to_string(), data))
1820}
1821
1822pub fn upload_contest_result_csv<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str,
1823 contest_id: i32, admission_data: &str)
1824 -> MedalResult<()> {
1825 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1826
1827 if session.csrf_token != csrf_token {
1828 return Err(MedalError::CsrfCheckFailed);
1829 }
1830
1831 let v: Vec<Vec<String>> = serde_json::from_str(admission_data).or(Err(MedalError::AccessDenied))?; let w: Vec<(i32, Option<String>)> = v.into_iter()
1834 .map(|vv| {
1835 (vv[0].parse().unwrap_or(-1),
1836 if vv[1].len() == 0 && vv[2].len() == 0 && vv[3].len() == 0 {
1837 None
1838 } else {
1839 Some(format!("{}\x1f{}\x1f{}", vv[1], vv[2], vv[3]))
1841 })
1842 })
1843 .collect();
1844
1845 let _annotations_inserted = conn.insert_contest_annotations(contest_id, w);
1846
1847 Ok(())
1848}
1849
1850#[allow(dead_code)]
1851pub fn show_groups_results<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalValueResult {
1852 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1853 let _g = conn.get_contest_groups_grades(session.id, contest_id);
1855
1856 let data = json_val::Map::new();
1857
1858 Ok(("groupresults".into(), data))
1859}
1860
1861pub struct SexInformation {
1862 pub require_sex: bool,
1863 pub allow_sex_na: bool,
1864 pub allow_sex_diverse: bool,
1865 pub allow_sex_other: bool,
1866}
1867
1868pub fn show_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>,
1869 query_string: Option<String>, sex_infos: SexInformation)
1870 -> MedalValueResult {
1871 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
1872
1873 let mut data = json_val::Map::new();
1874 fill_user_data(&session, &mut data);
1875
1876 data.insert("require_sex".to_string(), to_json(&sex_infos.require_sex));
1877 data.insert("allow_sex_na".to_string(), to_json(&sex_infos.allow_sex_na));
1878 data.insert("allow_sex_diverse".to_string(), to_json(&sex_infos.allow_sex_diverse));
1879 data.insert("allow_sex_other".to_string(), to_json(&sex_infos.allow_sex_other));
1880
1881 match user_id {
1882 None => {
1883 data.insert("profile_firstname".to_string(), to_json(&session.firstname));
1884 data.insert("profile_lastname".to_string(), to_json(&session.lastname));
1885 data.insert("profile_street".to_string(), to_json(&session.street));
1886 data.insert("profile_zip".to_string(), to_json(&session.zip));
1887 data.insert("profile_city".to_string(), to_json(&session.city));
1888 data.insert(format!("sel{}", session.grade), to_json(&"selected"));
1889 if let Some(sex) = session.sex {
1890 data.insert(format!("sex_{}", sex), to_json(&"selected"));
1891 } else {
1892 data.insert("sex_None".to_string(), to_json(&"selected"));
1893 }
1894
1895 data.insert("profile_logincode".to_string(), to_json(&session.logincode));
1896 if session.password.is_some() {
1897 data.insert("profile_username".to_string(), to_json(&session.username));
1898 }
1899 if session.managed_by.is_none() {
1900 data.insert("profile_not_in_group".into(), to_json(&true));
1901 }
1902 if session.oauth_provider != Some("pms".to_string()) {
1903 data.insert("profile_not_pms".into(), to_json(&true));
1904 }
1907 data.insert("ownprofile".into(), to_json(&true));
1908 data.insert("userid".into(), to_json(&session.id));
1909
1910 let webauthn = conn.get_webauthn_passkey_names_for_user(session.id);
1911 data.insert("webauthn".into(), to_json(&webauthn));
1912 data.insert("has_webauthn".into(), to_json(&!webauthn.is_empty()));
1913
1914 if let Some(query) = query_string {
1915 if let Some(status) = query.strip_prefix("status=") {
1916 if ["NothingChanged",
1917 "DataChanged",
1918 "PasswordChanged",
1919 "PasswordMissmatch",
1920 "firstlogin",
1921 "SignedUp"].contains(&status)
1922 {
1923 data.insert((status).to_string(), to_json(&true));
1924 }
1925 }
1926 }
1927
1928 let now = time::get_time();
1929
1930 let participations: (Vec<(i32, String, bool, bool, bool, Option<String>)>,
1932 Vec<(i32, String, bool, bool, bool, Option<String>)>) =
1933 conn.get_all_participations_complete(session.id)
1934 .into_iter()
1935 .rev()
1936 .map(|(participation, contest)| {
1937 let passed_secs = now.sec - participation.start.sec;
1938 let left_secs = i64::from(contest.duration) * 60 - passed_secs;
1939 let is_time_left = contest.duration == 0 || left_secs >= 0;
1940 let has_timelimit = contest.duration != 0;
1941 let requires_login = contest.requires_login == Some(true);
1942 let annotation =
1943 participation.annotation
1944 .map(|annotation| annotation.split('\x1f').nth(2).unwrap_or("").to_string());
1945
1946 (contest.id.unwrap(), contest.name, has_timelimit, is_time_left, requires_login, annotation)
1947 })
1948 .partition(|contest| contest.2 && !contest.4);
1949 data.insert("participations".into(), to_json(&participations));
1950
1951 let stars_count = conn.count_all_stars(session.id);
1952 data.insert("stars_count".into(), to_json(&stars_count));
1953 let stars_message = match stars_count {
1954 0 => "Auf gehts, dein erster Stern wartet auf dich!",
1955 1..=9 => "Ein hervorragender Anfang!",
1956 10..=99 => "Das ist ziemlich gut!",
1957 100..=999 => "Ein wahrer Meister!",
1958 _ => "Wow! Einfach wow!",
1959 }.to_string();
1960
1961 data.insert("stars_message".into(), to_json(&stars_message));
1962 }
1963 Some(user_id) => {
1965 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
1967 let group = opt_group.ok_or(MedalError::AccessDenied)?;
1968 if !group.admins.contains(&session.id) {
1969 return Err(MedalError::AccessDenied);
1970 }
1971
1972 data.insert("profile_firstname".to_string(), to_json(&user.firstname));
1973 data.insert("profile_lastname".to_string(), to_json(&user.lastname));
1974 data.insert("profile_street".to_string(), to_json(&session.street));
1975 data.insert("profile_zip".to_string(), to_json(&session.zip));
1976 data.insert("profile_city".to_string(), to_json(&session.city));
1977 data.insert(format!("sel{}", user.grade), to_json(&"selected"));
1978 if let Some(sex) = user.sex {
1979 data.insert(format!("sex_{}", sex), to_json(&"selected"));
1980 } else {
1981 data.insert("sex_None".to_string(), to_json(&"selected"));
1982 }
1983
1984 data.insert("profile_logincode".to_string(), to_json(&user.logincode));
1985 if user.username.is_some() {
1986 data.insert("profile_username".to_string(), to_json(&user.username));
1987 }
1988 if user.managed_by.is_none() {
1989 data.insert("profile_not_in_group".into(), to_json(&true));
1990 }
1991 if session.oauth_provider != Some("pms".to_string()) {
1992 data.insert("profile_not_pms".into(), to_json(&true));
1993 }
1994 data.insert("ownprofile".into(), to_json(&false));
1995
1996 if let Some(query) = query_string {
1997 if let Some(status) = query.strip_prefix("status=") {
1998 if ["NothingChanged", "DataChanged", "PasswordChanged", "PasswordMissmatch"].contains(&status) {
1999 data.insert((status).to_string(), to_json(&true));
2000 }
2001 }
2002 }
2003 }
2004 }
2005
2006 Ok(("profile".to_string(), data))
2007}
2008
2009#[derive(Debug, PartialEq, Eq)]
2010pub enum ProfileStatus {
2011 NothingChanged,
2012 DataChanged,
2013 PasswordChanged,
2014 PasswordMissmatch,
2015}
2016impl From<ProfileStatus> for String {
2017 fn from(s: ProfileStatus) -> String { format!("{:?}", s) }
2018}
2019
2020pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: &str, user_id: Option<i32>, csrf_token: &str,
2021 (firstname,
2022 lastname,
2023 street,
2024 zip,
2025 city,
2026 password,
2027 password_repeat,
2028 grade,
2029 sex): (String,
2030 String,
2031 Option<String>,
2032 Option<String>,
2033 Option<String>,
2034 Option<String>,
2035 Option<String>,
2036 i32,
2037 Option<i32>))
2038 -> MedalResult<ProfileStatus> {
2039 let mut session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2040
2041 if session.csrf_token != csrf_token {
2042 return Err(MedalError::AccessDenied); }
2044
2045 let mut result = ProfileStatus::NothingChanged;
2046
2047 let mut password_and_salt = None;
2048
2049 if let (Some(password), Some(password_repeat)) = (password, password_repeat) {
2050 if password != "" || password_repeat != "" {
2051 if password == password_repeat {
2052 let salt = helpers::make_salt();
2053 let hash = helpers::hash_password(&password, &salt)?;
2054
2055 password_and_salt = Some((hash, salt));
2056 result = ProfileStatus::PasswordChanged;
2057 } else {
2058 result = ProfileStatus::PasswordMissmatch;
2059 }
2060 }
2061 }
2062
2063 if result == ProfileStatus::NothingChanged {
2064 if session.firstname.as_ref() == Some(&firstname)
2065 && session.lastname.as_ref() == Some(&lastname)
2066 && session.street == street
2067 && session.zip == zip
2068 && session.city == city
2069 && session.grade == grade
2070 && session.sex == sex
2071 {
2072 return Ok(ProfileStatus::NothingChanged);
2073 } else {
2074 result = ProfileStatus::DataChanged;
2075 }
2076 }
2077
2078 match user_id {
2079 None => {
2080 session.firstname = Some(firstname);
2081 session.lastname = Some(lastname);
2082 session.grade = grade;
2083 session.sex = sex;
2084
2085 if street.is_some() {
2086 session.street = street;
2087 }
2088 if zip.is_some() {
2089 session.zip = zip;
2090 }
2091 if city.is_some() {
2092 session.city = city;
2093 }
2094
2095 if let Some((password, salt)) = password_and_salt {
2096 session.password = Some(password);
2097 session.salt = Some(salt);
2098 }
2099
2100 conn.save_session(session);
2101 }
2102 Some(user_id) => {
2103 let (mut user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2105 let group = opt_group.ok_or(MedalError::AccessDenied)?;
2106 if !group.admins.contains(&session.id) {
2107 return Err(MedalError::AccessDenied);
2108 }
2109
2110 user.firstname = Some(firstname);
2111 user.lastname = Some(lastname);
2112 user.grade = grade;
2113 user.sex = sex;
2114
2115 if street.is_some() {
2116 user.street = street;
2117 }
2118 if zip.is_some() {
2119 user.zip = zip;
2120 }
2121 if city.is_some() {
2122 user.city = city;
2123 }
2124
2125 if let Some((password, salt)) = password_and_salt {
2126 user.password = Some(password);
2127 user.salt = Some(salt);
2128 }
2129
2130 conn.save_session(user);
2131 }
2132 }
2133
2134 Ok(result)
2135}
2136
2137pub fn check_profile<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str,
2138 (firstname, lastname): (String, String))
2139 -> JsonValueResult {
2140 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2141
2142 if session.csrf_token != csrf_token {
2143 return Err(MedalError::AccessDenied);
2144 }
2145
2146 let mut data = json_val::Map::new();
2147
2148 if session.firstname.map(|n| n.len() > 0).unwrap_or(false) && session.lastname.map(|n| n.len() > 0).unwrap_or(false)
2150 {
2151 data.insert("exists".to_string(), to_json(&false));
2152 return Ok(data);
2153 }
2154
2155 let group =
2156 conn.get_group_complete(session.managed_by.ok_or(MedalError::AccessDenied)?).ok_or(MedalError::AccessDenied)?;
2157
2158 for member in group.members {
2159 if member.lastname.map(|l| l.to_lowercase()) == Some(lastname.to_lowercase())
2160 && member.firstname.map(|l| l.to_lowercase()) == Some(firstname.to_lowercase())
2161 && member.id != session.id
2162 {
2163 data.insert("exists".to_string(), to_json(&true));
2164 return Ok(data);
2165 }
2166 }
2167
2168 data.insert("exists".to_string(), to_json(&false));
2169 Ok(data)
2170}
2171
2172pub fn teacher_infos<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2173 let session = conn.get_session(&session_token).ensure_logged_in().ok_or(MedalError::NotLoggedIn)?;
2174 if !session.is_teacher {
2175 return Err(MedalError::AccessDenied);
2176 }
2177
2178 let mut data = json_val::Map::new();
2179 fill_user_data(&session, &mut data);
2180
2181 Ok(("teacher".to_string(), data))
2182}
2183
2184pub fn admin_index<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2185 let session = conn.get_session(&session_token)
2186 .ensure_logged_in()
2187 .ok_or(MedalError::NotLoggedIn)?
2188 .ensure_admin()
2189 .ok_or(MedalError::AccessDenied)?;
2190
2191 let mut data = json_val::Map::new();
2192 fill_user_data(&session, &mut data);
2193
2194 Ok(("admin".to_string(), data))
2195}
2196
2197pub fn admin_search_users<T: MedalConnection>(conn: &T, session_token: &str,
2198 s_data: (Option<i32>,
2199 Option<String>,
2200 Option<String>,
2201 Option<String>,
2202 Option<String>,
2203 Option<String>))
2204 -> MedalValueResult {
2205 let session = conn.get_session(&session_token)
2206 .ensure_logged_in()
2207 .ok_or(MedalError::NotLoggedIn)?
2208 .ensure_admin()
2209 .ok_or(MedalError::AccessDenied)?;
2210
2211 let mut data = json_val::Map::new();
2212 fill_user_data(&session, &mut data);
2213
2214 match conn.get_search_users(s_data) {
2215 Ok(users) => {
2216 data.insert("users".to_string(), to_json(&users));
2217 data.insert("max_results".to_string(), to_json(&200));
2218 data.insert("num_results".to_string(), to_json(&users.len()));
2219 data.insert("no_results".to_string(), to_json(&(users.len() == 0)));
2220 if users.len() > 200 {
2221 data.insert("more_users".to_string(), to_json(&true));
2222 data.insert("more_results".to_string(), to_json(&true));
2223 }
2224 }
2225 Err(groups) => {
2226 data.insert("groups".to_string(), to_json(&groups));
2227 data.insert("max_results".to_string(), to_json(&200));
2228 data.insert("num_results".to_string(), to_json(&groups.len()));
2229 data.insert("no_results".to_string(), to_json(&(groups.len() == 0)));
2230 if groups.len() > 200 {
2231 data.insert("more_groups".to_string(), to_json(&true));
2232 data.insert("more_results".to_string(), to_json(&true));
2233 }
2234 }
2235 };
2236
2237 Ok(("admin_search_results".to_string(), data))
2238}
2239
2240pub fn admin_show_user<T: MedalConnection>(conn: &T, user_id: i32, session_token: &str) -> MedalValueResult {
2241 let session = conn.get_session(&session_token)
2242 .ensure_logged_in()
2243 .ok_or(MedalError::NotLoggedIn)?
2244 .ensure_teacher_or_admin()
2245 .ok_or(MedalError::AccessDenied)?;
2246
2247 let mut data = json_val::Map::new();
2248
2249 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2250
2251 if !session.is_admin() {
2252 if let Some(group) = opt_group.clone() {
2254 if !group.admins.contains(&session.id) {
2255 return Err(MedalError::AccessDenied);
2256 }
2257 } else if user_id != session.id {
2258 return Err(MedalError::AccessDenied);
2259 }
2260 }
2261
2262 fill_user_data(&session, &mut data);
2263 fill_user_data_prefix(&user, &mut data, "user_");
2264 data.insert("user_logincode".to_string(), to_json(&user.logincode));
2265 data.insert("user_id".to_string(), to_json(&user.id));
2266 let grade = if user.grade >= 200 {
2267 "Kein Schüler mehr".to_string()
2268 } else if user.grade >= 11 {
2269 format!("{} ({})", user.grade % 100, if user.grade >= 100 { "G9" } else { "G8" })
2270 } else {
2271 format!("{}", user.grade)
2272 };
2273 data.insert("user_grade".to_string(), to_json(&grade));
2274 data.insert("user_oauthid".to_string(), to_json(&user.oauth_foreign_id));
2275 data.insert("user_oauthprovider".to_string(), to_json(&user.oauth_provider));
2276
2277 if let Some(group) = opt_group {
2278 data.insert("user_group_id".to_string(), to_json(&group.id));
2279 data.insert("user_group_name".to_string(), to_json(&group.name));
2280 }
2281
2282 let groups: Vec<GroupInfo> =
2283 conn.get_groups(user_id)
2284 .iter()
2285 .map(|g| GroupInfo { id: g.id.unwrap(),
2286 name: g.name.clone(),
2287 tag: g.tag.clone(),
2288 code: g.groupcode.clone() })
2289 .collect();
2290 data.insert("user_group".to_string(), to_json(&groups));
2291
2292 let parts = conn.get_all_participations_complete(user_id);
2293 let has_protected_participations = parts.iter().any(|p| p.1.protected);
2294 let contest_stars = conn.count_all_stars_by_contest(user_id);
2295
2296 let pi: Vec<(i32, String, String, i32)> =
2297 parts.into_iter()
2298 .map(|(p, c)| {
2299 (c.id.unwrap(),
2300 format!("{}{}{}",
2301 &c.name,
2302 if c.protected { " (geschützt)" } else { "" },
2303 if p.team.is_some() { " (Teamteilnahme)" } else { "" }),
2304 self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(p.start)).unwrap(),
2305 contest_stars.iter()
2306 .filter_map(|(id, stars)| if *id == c.id.unwrap() { Some(*stars) } else { None })
2307 .next()
2308 .unwrap_or(0))
2309 })
2310 .collect();
2311
2312 data.insert("user_participations".to_string(), to_json(&pi));
2313 data.insert("has_protected_participations".to_string(), to_json(&has_protected_participations));
2314 data.insert("can_delete".to_string(),
2315 to_json(&((!has_protected_participations || session.is_admin()) && groups.len() == 0)));
2316
2317 Ok(("admin_user".to_string(), data))
2318}
2319
2320pub fn admin_delete_user<T: MedalConnection>(conn: &T, user_id: i32, session_token: &str, csrf_token: &str)
2321 -> JsonValueResult {
2322 let session = conn.get_session(&session_token)
2323 .ensure_logged_in()
2324 .ok_or(MedalError::NotLoggedIn)?
2325 .ensure_teacher_or_admin()
2326 .ok_or(MedalError::AccessDenied)?;
2327
2328 if session.csrf_token != csrf_token {
2329 return Err(MedalError::CsrfCheckFailed);
2330 }
2331
2332 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2333
2334 if !session.is_admin() {
2335 if let Some(group) = opt_group {
2337 if !group.admins.contains(&session.id) {
2338 return Err(MedalError::AccessDenied);
2339 }
2340 } else {
2341 return Err(MedalError::AccessDenied);
2342 }
2343 }
2344
2345 let parts = conn.get_all_participations_complete(user_id);
2346 let has_protected_participations = parts.iter().any(|p| p.1.protected);
2347 let groups = conn.get_groups(user_id);
2348
2349 let mut data = json_val::Map::new();
2350 if has_protected_participations && !session.is_admin() {
2351 data.insert("reason".to_string(), to_json(&"Benutzer hat Teilnahmen an geschützten Wettbewerben."));
2352 Err(MedalError::ErrorWithJson(data))
2353 } else if groups.len() > 0 {
2354 data.insert("reason".to_string(), to_json(&"Benutzer ist Administrator von Gruppen."));
2355 Err(MedalError::ErrorWithJson(data))
2356 } else {
2357 conn.delete_user(user_id);
2358 Ok(data)
2359 }
2360}
2361
2362pub fn admin_move_user_to_group<T: MedalConnection>(conn: &T, user_id: i32, group_id: i32, session_token: &str,
2363 csrf_token: &str)
2364 -> JsonValueResult {
2365 let session = conn.get_session(&session_token)
2366 .ensure_logged_in()
2367 .ok_or(MedalError::NotLoggedIn)?
2368 .ensure_admin()
2369 .ok_or(MedalError::AccessDenied)?;
2370
2371 if session.csrf_token != csrf_token {
2372 return Err(MedalError::CsrfCheckFailed);
2373 }
2374
2375 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2376
2377 if !session.is_admin() {
2378 if let Some(group) = opt_group {
2380 if !group.admins.contains(&session.id) {
2381 return Err(MedalError::AccessDenied);
2382 }
2383 } else {
2384 return Err(MedalError::AccessDenied);
2385 }
2386 }
2387
2388 let mut data = json_val::Map::new();
2389 if conn.get_group(group_id).is_some() {
2390 if let Some(mut user) = conn.get_user_by_id(user_id) {
2391 user.managed_by = Some(group_id);
2392 conn.save_session(user);
2393 Ok(data)
2394 } else {
2395 data.insert("reason".to_string(), to_json(&"Benutzer existiert nicht."));
2396 Err(MedalError::ErrorWithJson(data))
2397 }
2398 } else {
2399 data.insert("reason".to_string(), to_json(&"Gruppe existiert nicht."));
2400 Err(MedalError::ErrorWithJson(data))
2401 }
2402}
2403
2404#[derive(Serialize, Deserialize)]
2405pub struct AdminInfo {
2406 pub id: i32,
2407 pub firstname: String,
2408 pub lastname: String,
2409}
2410
2411pub fn admin_show_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
2412 let session = conn.get_session(&session_token)
2413 .ensure_logged_in()
2414 .ok_or(MedalError::NotLoggedIn)?
2415 .ensure_teacher_or_admin()
2416 .ok_or(MedalError::AccessDenied)?;
2417
2418 let group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2421 if !group.admins.contains(&session.id) {
2423 return Err(MedalError::AccessDenied);
2424 }
2425 }
2426
2427 let mut data = json_val::Map::new();
2428 fill_user_data(&session, &mut data);
2429
2430 let gi = GroupInfo { id: group.id.unwrap(),
2431 name: group.name.clone(),
2432 tag: group.tag.clone(),
2433 code: group.groupcode.clone() };
2434
2435 let v: Vec<MemberInfo> =
2436 group.members
2437 .iter()
2438 .filter(|m| session.is_admin() || m.firstname.is_some() || m.lastname.is_some())
2439 .map(|m| MemberInfo { id: m.id,
2440 firstname: m.firstname.clone().unwrap_or_else(|| "".to_string()),
2441 lastname: m.lastname.clone().unwrap_or_else(|| "".to_string()),
2442 sex: (match m.sex {
2443 Some(0) | None => "/",
2444 Some(1) => "m",
2445 Some(2) => "w",
2446 Some(3) => "d",
2447 Some(4) => "…",
2448 _ => "?",
2449 }).to_string(),
2450 grade: grade_to_string(m.grade),
2451 logincode: m.logincode.clone().unwrap_or_else(|| "".to_string()),
2452 anonymous: m.anonymous })
2453 .collect();
2454
2455 let has_anonmous_members = group.members.iter().any(|m| m.anonymous);
2456
2457 let has_protected_participations = conn.group_has_protected_participations(group_id);
2458
2459 data.insert("group".to_string(), to_json(&gi));
2460 data.insert("member".to_string(), to_json(&v));
2461 data.insert("groupname".to_string(), to_json(&gi.name));
2462 data.insert("has_anonymous_members".to_string(), to_json(&has_anonmous_members));
2463 data.insert("has_protected_participations".to_string(), to_json(&has_protected_participations));
2464 data.insert("can_delete".to_string(), to_json(&(!has_protected_participations || session.is_admin())));
2465
2466 let admins: Vec<AdminInfo> =
2467 group.admins
2468 .iter()
2469 .map(|a| {
2470 let admin = conn.get_user_by_id(*a).ok_or(MedalError::AccessDenied)?;
2471 Ok(AdminInfo { id: admin.id,
2472 firstname: admin.firstname.clone().unwrap_or_else(|| "".to_string()),
2473 lastname: admin.lastname.clone().unwrap_or_else(|| "".to_string()) })
2474 })
2475 .collect::<Result<Vec<_>, _>>()?;
2476
2477 data.insert("group_admin".to_string(), to_json(&admins));
2478 data.insert("morethanoneadmin".to_string(), to_json(&(admins.len() > 1)));
2479
2480 Ok(("admin_group".to_string(), data))
2481}
2482
2483pub fn group_add_admin<T: MedalConnection>(conn: &T, group_id: i32, new_admin_id: i32, session_token: &str,
2484 csrf_token: &str)
2485 -> JsonValueResult {
2486 let session = conn.get_session(&session_token)
2487 .ensure_logged_in()
2488 .ok_or(MedalError::NotLoggedIn)?
2489 .ensure_teacher_or_admin()
2490 .ok_or(MedalError::AccessDenied)?;
2491
2492 if session.csrf_token != csrf_token {
2493 return Err(MedalError::CsrfCheckFailed);
2494 }
2495
2496 let mut group = conn.get_group(group_id).unwrap(); if !session.is_admin() {
2499 if !group.admins.contains(&session.id) {
2501 return Err(MedalError::AccessDenied);
2502 }
2503 }
2504
2505 if group.admins.contains(&new_admin_id) {
2506 let mut data = json_val::Map::new();
2507 data.insert("reason".to_string(), to_json(&"Benutzer ist bereits Admin."));
2508 return Err(MedalError::ErrorWithJson(data));
2509 }
2510
2511 let new_admin = conn.get_user_by_id(new_admin_id).ok_or(MedalError::NotFound)?;
2512 let first_admin_id = group.admins.first().ok_or(MedalError::NotFound)?;
2513 let first_admin = conn.get_user_by_id(*first_admin_id).ok_or(MedalError::NotFound)?;
2514 if new_admin.oauth_provider == first_admin.oauth_provider {
2515 if let Some((_, new_admin_school)) = new_admin.oauth_foreign_id.ok_or(MedalError::AccessDenied)?.split_once('/')
2516 {
2517 if let Some((_, first_admin_school)) =
2518 first_admin.oauth_foreign_id.ok_or(MedalError::AccessDenied)?.split_once('/')
2519 {
2520 if new_admin_school == first_admin_school && new_admin_school.len() >= 1 {
2521 conn.add_admin_to_group(&mut group, new_admin_id);
2522
2523 let data = json_val::Map::new();
2524 return Ok(data);
2525 }
2526 }
2527 }
2528 }
2529
2530 let mut data = json_val::Map::new();
2531 data.insert("reason".to_string(),
2532 to_json(&"Benutzer gehört nicht zur gleichen Schule oder Benutzer nicht als Lehrkraft angemeldet."));
2533 Err(MedalError::ErrorWithJson(data))
2534}
2535
2536pub fn group_delete_admin<T: MedalConnection>(conn: &T, group_id: i32, admin_id: i32, session_token: &str,
2537 csrf_token: &str)
2538 -> JsonValueResult {
2539 let session = conn.get_session(&session_token)
2540 .ensure_logged_in()
2541 .ok_or(MedalError::NotLoggedIn)?
2542 .ensure_teacher_or_admin()
2543 .ok_or(MedalError::AccessDenied)?;
2544
2545 if session.csrf_token != csrf_token {
2546 return Err(MedalError::CsrfCheckFailed);
2547 }
2548
2549 let mut group = conn.get_group(group_id).unwrap(); if !session.is_admin() {
2552 if !group.admins.contains(&session.id) {
2554 return Err(MedalError::AccessDenied);
2555 }
2556 }
2557
2558 if group.admins.len() == 1 {
2559 let mut data = json_val::Map::new();
2560 data.insert("reason".to_string(), to_json(&"Kann letzten Admin nicht entfernen."));
2561 return Err(MedalError::ErrorWithJson(data));
2562 }
2563
2564 if !group.admins.contains(&admin_id) {
2565 let mut data = json_val::Map::new();
2566 data.insert("reason".to_string(), to_json(&"Benutzer ist kein Admin."));
2567 return Err(MedalError::ErrorWithJson(data));
2568 }
2569
2570 conn.remove_admin_from_group(&mut group, admin_id);
2571 let data = json_val::Map::new();
2572 Ok(data)
2573}
2574
2575pub fn admin_delete_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str, csrf_token: &str)
2576 -> JsonValueResult {
2577 let session = conn.get_session(&session_token)
2578 .ensure_logged_in()
2579 .ok_or(MedalError::NotLoggedIn)?
2580 .ensure_teacher_or_admin()
2581 .ok_or(MedalError::AccessDenied)?;
2582
2583 if session.csrf_token != csrf_token {
2584 return Err(MedalError::CsrfCheckFailed);
2585 }
2586
2587 let group = conn.get_group(group_id).unwrap(); if !session.is_admin() {
2590 if !group.admins.contains(&session.id) {
2592 return Err(MedalError::AccessDenied);
2593 }
2594 }
2595
2596 let mut data = json_val::Map::new();
2597 if conn.group_has_protected_participations(group_id) && !session.is_admin() {
2598 data.insert("reason".to_string(), to_json(&"Gruppe hat Mitglieder mit geschützten Teilnahmen."));
2599 Err(MedalError::ErrorWithJson(data))
2600 } else {
2601 conn.delete_all_users_for_group(group_id);
2602 conn.delete_group(group_id);
2603 Ok(data)
2604 }
2605}
2606
2607pub fn admin_show_edit_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str) -> MedalValueResult {
2608 let session = conn.get_session(&session_token)
2609 .ensure_logged_in()
2610 .ok_or(MedalError::NotLoggedIn)?
2611 .ensure_teacher_or_admin()
2612 .ok_or(MedalError::AccessDenied)?;
2613
2614 let group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2617 if !group.admins.contains(&session.id) {
2619 return Err(MedalError::AccessDenied);
2620 }
2621 }
2622
2623 let mut data = json_val::Map::new();
2624 fill_user_data(&session, &mut data);
2625
2626 let gi = GroupInfo { id: group.id.unwrap(),
2627 name: group.name.clone(),
2628 tag: group.tag.clone(),
2629 code: group.groupcode.clone() };
2630
2631 data.insert("group".to_string(), to_json(&gi));
2632 data.insert("groupname".to_string(), to_json(&gi.name));
2633 data.insert("grouptag".to_string(), to_json(&gi.name));
2634
2635 Ok(("admin_edit_group".to_string(), data))
2636}
2637
2638pub fn admin_edit_group<T: MedalConnection>(conn: &T, group_id: i32, session_token: &str, csrf_token: &str,
2639 name: String, tag: String)
2640 -> MedalResult<()> {
2641 let session = conn.get_session(&session_token)
2642 .ensure_logged_in()
2643 .ok_or(MedalError::NotLoggedIn)?
2644 .ensure_teacher_or_admin()
2645 .ok_or(MedalError::AccessDenied)?;
2646
2647 if session.csrf_token != csrf_token {
2648 return Err(MedalError::CsrfCheckFailed);
2649 }
2650
2651 let mut group = conn.get_group_complete(group_id).unwrap(); if !session.is_admin() {
2654 if !group.admins.contains(&session.id) {
2656 return Err(MedalError::AccessDenied);
2657 }
2658 }
2659
2660 group.name = name;
2661 group.tag = tag;
2662
2663 conn.save_group(&mut group);
2664
2665 Ok(())
2666}
2667
2668#[derive(Serialize, Deserialize, Debug)]
2669struct SubmissionResult {
2670 id: i32,
2671 grade: i32,
2672 date: String,
2673}
2674#[derive(Serialize, Deserialize, Debug)]
2675struct TaskResult {
2676 id: i32,
2677 stars: i32,
2678 submissions: Vec<SubmissionResult>,
2679}
2680#[derive(Serialize, Deserialize, Debug)]
2681struct TaskgroupResult {
2682 id: i32,
2683 name: String,
2684 tasks: Vec<TaskResult>,
2685}
2686
2687pub fn admin_show_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str)
2688 -> MedalValueResult {
2689 let session = conn.get_session(&session_token)
2690 .ensure_logged_in()
2691 .ok_or(MedalError::NotLoggedIn)?
2692 .ensure_teacher_or_admin()
2693 .ok_or(MedalError::AccessDenied)?;
2694
2695 let user = conn.get_user_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2696 let participation = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
2697
2698 let user_or_team = if let Some(team) = participation.team { team } else { user_id };
2699
2700 let (_, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2701
2702 if !session.is_admin() {
2703 if let Some(ref group) = opt_group {
2705 if !group.admins.contains(&session.id) {
2706 return Err(MedalError::AccessDenied);
2707 }
2708 } else {
2709 return Err(MedalError::AccessDenied);
2710 }
2711 }
2712
2713 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2714 let grades = conn.get_contest_user_grades(user_or_team, contest_id);
2715
2716 let mut totalgrade = 0;
2717 let mut max_totalgrade = 0;
2718
2719 for taskgroup in &contest.taskgroups {
2720 max_totalgrade += taskgroup.tasks.iter().map(|x| x.stars).max().unwrap_or(0);
2721 }
2722 for grade in grades {
2723 totalgrade += grade.grade.unwrap_or(0);
2724 }
2725
2726 #[rustfmt::skip]
2727 let subms: Vec<TaskgroupResult> =
2728 contest.taskgroups
2729 .into_iter()
2730 .map(|tg| TaskgroupResult {
2731 id: tg.id.unwrap(),
2732 name: tg.name,
2733 tasks: tg.tasks
2734 .into_iter()
2735 .map(|t| TaskResult {
2736 id: t.id.unwrap(),
2737 stars: t.stars,
2738 submissions: conn.get_all_submissions(user_or_team, t.id.unwrap(), None)
2739 .into_iter()
2740 .map(|s| SubmissionResult {
2741 id: s.id.unwrap(),
2742 grade: s.grade,
2743 date: self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(s.date)).unwrap(),
2744 })
2745 .collect(),
2746 })
2747 .collect(),
2748 })
2749 .collect();
2750
2751 let mut data = json_val::Map::new();
2752
2753 data.insert("submissions".to_string(), to_json(&subms));
2754 data.insert("contestid".to_string(), to_json(&contest.id));
2755 data.insert("contestname".to_string(), to_json(&contest.name));
2756 data.insert("has_timelimit".to_string(), to_json(&(contest.duration > 0)));
2757
2758 data.insert("total_grade".to_string(), to_json(&totalgrade));
2759 data.insert("max_total_grade".to_string(), to_json(&max_totalgrade));
2760
2761 if let Some(group) = opt_group {
2762 data.insert("group_id".to_string(), to_json(&group.id));
2763 data.insert("group_name".to_string(), to_json(&group.name));
2764 }
2765
2766 fill_user_data(&session, &mut data);
2767 fill_user_data_prefix(&user, &mut data, "user_");
2768 data.insert("user_id".to_string(), to_json(&user.id));
2769
2770 data.insert("start_date".to_string(),
2771 to_json(&self::time::strftime("%e. %b %Y, %H:%M", &self::time::at(participation.start)).unwrap()));
2772
2773 data.insert("can_delete".to_string(), to_json(&(!contest.protected || session.is_admin.unwrap_or(false))));
2774 Ok(("admin_participation".to_string(), data))
2775}
2776
2777pub fn admin_delete_participation<T: MedalConnection>(conn: &T, user_id: i32, contest_id: i32, session_token: &str,
2778 csrf_token: &str)
2779 -> JsonValueResult {
2780 let session = conn.get_session(&session_token)
2781 .ensure_logged_in()
2782 .ok_or(MedalError::NotLoggedIn)?
2783 .ensure_teacher_or_admin()
2784 .ok_or(MedalError::AccessDenied)?;
2785
2786 if session.csrf_token != csrf_token {
2787 return Err(MedalError::CsrfCheckFailed);
2788 }
2789
2790 let (user, opt_group) = conn.get_user_and_group_by_id(user_id).ok_or(MedalError::AccessDenied)?;
2791 let _part = conn.get_participation(user.id, contest_id).ok_or(MedalError::AccessDenied)?;
2792 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2793
2794 if !session.is_admin() {
2795 if contest.protected {
2797 return Err(MedalError::AccessDenied);
2798 }
2799
2800 if let Some(group) = opt_group {
2801 if !group.admins.contains(&session.id) {
2802 return Err(MedalError::AccessDenied);
2803 }
2804 } else {
2805 return Err(MedalError::AccessDenied);
2806 }
2807 }
2808
2809 let mut data = json_val::Map::new();
2810 fill_user_data(&session, &mut data);
2811
2812 conn.delete_participation(user_id, contest_id);
2813 Ok(data)
2814}
2815
2816pub fn admin_show_contests<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2817 let session = conn.get_session(&session_token)
2818 .ensure_logged_in()
2819 .ok_or(MedalError::NotLoggedIn)?
2820 .ensure_admin()
2821 .ok_or(MedalError::AccessDenied)?;
2822
2823 let mut data = json_val::Map::new();
2824 fill_user_data(&session, &mut data);
2825
2826 let mut contests: Vec<_> = conn.get_contest_list().into_iter().map(|contest| (contest.id, contest.name)).collect();
2827 contests.sort(); contests.reverse();
2829
2830 data.insert("contests".to_string(), to_json(&contests));
2831
2832 Ok(("admin_contests".to_string(), data))
2833}
2834
2835pub fn admin_contest_export<T: MedalConnection>(conn: &T, contest_id: i32, session_token: &str) -> MedalResult<String> {
2836 conn.get_session(&session_token)
2837 .ensure_logged_in()
2838 .ok_or(MedalError::NotLoggedIn)?
2839 .ensure_admin()
2840 .ok_or(MedalError::AccessDenied)?;
2841
2842 let contest = conn.get_contest_by_id_complete(contest_id).ok_or(MedalError::UnknownId)?;
2843
2844 let taskgroup_ids: Vec<(i32, String)> =
2845 contest.taskgroups.into_iter().map(|tg| (tg.id.unwrap(), tg.name)).collect();
2846 let filename = format!("contest_{}__{}__{}.csv",
2847 contest_id,
2848 self::time::strftime("%F_%H-%M-%S", &self::time::now()).unwrap(),
2849 helpers::make_filename_secret());
2850
2851 conn.export_contest_results_to_file(contest_id, &taskgroup_ids, &format!("./export/{}", filename));
2852
2853 Ok(filename)
2854}
2855
2856pub fn admin_show_cleanup<T: MedalConnection>(conn: &T, session_token: &str) -> MedalValueResult {
2857 let session = conn.get_session(&session_token)
2858 .ensure_logged_in()
2859 .ok_or(MedalError::NotLoggedIn)?
2860 .ensure_admin()
2861 .ok_or(MedalError::AccessDenied)?;
2862
2863 let mut data = json_val::Map::new();
2864 fill_user_data(&session, &mut data);
2865
2866 let now = time::get_time();
2867 let maxage = now - time::Duration::days(30); let n_temporary_session = conn.count_temporary_sessions(maxage);
2869 data.insert("temporary_session_count".to_string(), to_json(&n_temporary_session));
2870
2871 Ok(("admin_cleanup".to_string(), data))
2872}
2873
2874pub fn admin_do_cleanup<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str) -> JsonValueResult {
2875 let session = conn.get_session(&session_token)
2876 .ensure_logged_in()
2877 .ok_or(MedalError::NotLoggedIn)?
2878 .ensure_admin()
2879 .ok_or(MedalError::AccessDenied)?;
2880
2881 if session.csrf_token != csrf_token {
2882 return Err(MedalError::CsrfCheckFailed);
2883 }
2884
2885 let now = time::get_time();
2886 let maxstudentage = now - time::Duration::days(180); let maxteacherage = now - time::Duration::days(1095); let maxage = now - time::Duration::days(3650); let result = conn.remove_old_users_and_groups(maxstudentage, Some(maxteacherage), Some(maxage));
2891
2892 let mut data = json_val::Map::new();
2893 if let Ok((n_user, n_group, n_teacher, n_other)) = result {
2894 data.insert("n_user".to_string(), to_json(&n_user));
2895 data.insert("n_group".to_string(), to_json(&n_group));
2896 data.insert("n_teacher".to_string(), to_json(&n_teacher));
2897 data.insert("n_other".to_string(), to_json(&n_other));
2898 Ok(data)
2899 } else {
2900 data.insert("reason".to_string(), to_json(&"Datenbank-Fehler."));
2901 Err(MedalError::ErrorWithJson(data))
2902 }
2903}
2904
2905pub fn do_session_cleanup<T: MedalConnection>(conn: &T) -> JsonValueResult {
2906 let now = time::get_time();
2907 let maxage = now - time::Duration::days(30); conn.remove_temporary_sessions(maxage, Some(1000));
2910
2911 let data = json_val::Map::new();
2912 Ok(data)
2913}
2914
2915pub fn move_task_location<T: MedalConnection>(conn: &T, session_token: &str, csrf_token: &str, old_location: &str,
2916 new_location: &str, contest: Option<i32>)
2917 -> JsonValueResult {
2918 let session = conn.get_session(&session_token)
2919 .ensure_logged_in()
2920 .ok_or(MedalError::NotLoggedIn)?
2921 .ensure_admin()
2922 .ok_or(MedalError::AccessDenied)?;
2923
2924 if session.csrf_token != csrf_token {
2925 return Err(MedalError::CsrfCheckFailed);
2926 }
2927
2928 let mut data = json_val::Map::new();
2929 if old_location == new_location {
2930 data.insert("reason".to_string(), to_json(&"old and new location identical"));
2931 return Err(MedalError::ErrorWithJson(data));
2932 }
2933
2934 let n_contest = conn.move_task_location(old_location, new_location, contest);
2935
2936 data.insert("contests_modified".to_string(), to_json(&n_contest));
2937
2938 Ok(data)
2939}
2940
2941#[derive(PartialEq, Eq, Clone, Copy)]
2942pub enum UserType {
2943 User,
2944 Teacher,
2945 Admin,
2946}
2947
2948pub enum UserSex {
2949 Female,
2950 Male,
2951 Unknown,
2952}
2953
2954pub struct ForeignUserData {
2955 pub foreign_id: String,
2956 pub foreign_type: UserType,
2957 pub sex: UserSex,
2958 pub firstname: String,
2959 pub lastname: String,
2960 pub school_name: Option<String>,
2961}
2962
2963pub fn login_oauth<T: MedalConnection>(conn: &T, user_data: ForeignUserData, oauth_provider_id: String,
2964 autoclean_submissions: bool)
2965 -> Result<(String, bool), (String, json_val::Map<String, json_val::Value>)> {
2966 match conn.login_foreign(None,
2967 &oauth_provider_id,
2968 &user_data.foreign_id,
2969 (user_data.foreign_type != UserType::User,
2970 user_data.foreign_type == UserType::Admin,
2971 &user_data.firstname,
2972 &user_data.lastname,
2973 match user_data.sex {
2974 UserSex::Male => Some(1),
2975 UserSex::Female => Some(2),
2976 UserSex::Unknown => Some(0),
2977 },
2978 &user_data.school_name))
2979 {
2980 Ok((session_token, last_activity)) => {
2981 let redirect_profile = if let Some(last_activity) = last_activity {
2982 let now = time::get_time();
2983 now - last_activity > time::Duration::days(60)
2984 } else {
2985 true
2986 };
2987
2988 let now = time::get_time();
2992 let maxage = now - time::Duration::days(30);
2993 conn.remove_temporary_sessions(maxage, Some(200)); if autoclean_submissions {
2996 let maxage = now - time::Duration::days(90);
3001 conn.remove_all_but_latest_submissions(maxage, Some(10_000)); }
3003
3004 Ok((session_token, redirect_profile))
3005 }
3006 Err(()) => {
3007 let mut data = json_val::Map::new();
3008 data.insert("reason".to_string(), to_json(&"OAuth-Login failed.".to_string()));
3009 Err(("login".to_owned(), data))
3010 }
3011 }
3012}