use actix_web::{get, HttpResponse}; use anyhow::Result; use deunicode::deunicode; use fancy_regex::Regex; use lazy_static::lazy_static; use serde::Serialize; use std::collections::HashMap; use crate::config; lazy_static! { static ref CLASS_REGEX: Regex = Regex::new(r"(9|10)\s?(?!R)[a-z]").unwrap(); } #[derive(Serialize, Debug)] pub struct Class { pub name: String, pub students: Vec, } #[derive(Serialize, Debug)] pub struct User { pub username: String, pub first_name: String, pub last_name: String, } #[derive(Serialize, Debug)] pub struct GetStudentsResponse { pub error: Option, pub classes: Vec, pub teachers: Vec, } fn escape_username(s: &str) -> String { deunicode( &s.to_lowercase() .chars() .filter(|c| !c.is_whitespace()) .collect::(), ) } async fn students( client: &mut untis::Client, usernames: &mut HashMap, ) -> Result> { Ok(futures::future::try_join_all( client .classes() .await? .iter() .filter(|c| CLASS_REGEX.is_match(&c.name).unwrap()) .map(|c| async { Ok::<_, anyhow::Error>(Class { name: c.name.to_owned(), students: client .student_reports(c.id) .await? .into_iter() .map(|s| User { username: escape_username(&s.name), first_name: s.fore_name, last_name: s.long_name, }) .collect(), }) }), ) .await? .into_iter() .map(|c| Class { name: c.name, students: c .students .into_iter() .map(|s| User { username: { let escaped = escape_username(&s.username); match usernames.get_mut(&escaped) { Some(x) => { *x += 1; format!("{}{}", escaped, x) } None => { usernames.insert(escaped.to_owned(), 1); escaped } } }, first_name: s.first_name, last_name: s.last_name, }) .collect(), }) .collect()) } async fn teachers( client: &mut untis::Client, usernames: &mut HashMap, ) -> Result> { Ok(client .teacher_reports() .await? .into_iter() .filter_map(|t| { if t.long_name.starts_with("Abi-Aufsicht") || t.long_name == "SBBZ" || t.long_name == "N.N." || t.long_name == "Werkrealschule" { None } else { Some(User { username: { let escaped = escape_username(&t.name); match usernames.get_mut(&escaped) { Some(x) => { *x += 1; format!("{}{}", escaped, x) } None => { usernames.insert(escaped.to_owned(), 1); escaped } } }, first_name: t.fore_name, last_name: t.long_name, }) } }) .collect()) } #[get("/v1/users")] pub async fn get_users() -> HttpResponse { let mut client = match config::untis_from_env() { Ok(x) => x, Err(x) => { return HttpResponse::InternalServerError().json(GetStudentsResponse { error: Some(x.to_string()), classes: vec![], teachers: vec![], }) } }; if let Err(x) = client.login().await { return HttpResponse::InternalServerError().json(GetStudentsResponse { error: Some(x.to_string()), classes: vec![], teachers: vec![], }); } let mut usernames = HashMap::::new(); let classes = match students(&mut client, &mut usernames).await { Ok(x) => x, Err(x) => { return HttpResponse::InternalServerError().json(GetStudentsResponse { error: Some(x.to_string()), classes: vec![], teachers: vec![], }) } }; let teachers = match teachers(&mut client, &mut usernames).await { Ok(x) => x, Err(x) => { return HttpResponse::InternalServerError().json(GetStudentsResponse { error: Some(x.to_string()), classes: vec![], teachers: vec![], }) } }; if let Err(x) = client.logout().await { return HttpResponse::InternalServerError().json(GetStudentsResponse { error: Some(x.to_string()), classes: vec![], teachers: vec![], }); } HttpResponse::Ok() .content_type(mime::APPLICATION_JSON) .json(GetStudentsResponse { error: None, classes, teachers, }) }