diff --git a/backend/src/controller/classroom/mod.rs b/backend/src/controller/classroom/mod.rs index aa69020..8d455e2 100644 --- a/backend/src/controller/classroom/mod.rs +++ b/backend/src/controller/classroom/mod.rs @@ -1,9 +1,14 @@ +use crate::{ + json_result::JsonResult, + model::classroom_user::ClassroomPersonCreate, + online_classroom::{create_user::create, get_courses::ClassroomCourse}, +}; use rocket::{http::Status, serde::json::Json}; -use crate::{json_result::JsonResult, model::classroom_user::ClassroomPersonCreate, online_classroom::create_user::create}; - #[options("/classroom/user")] -pub fn create_user_options() -> Status { Status::Ok } +pub fn create_user_options() -> Status { + Status::Ok +} #[post("/classroom/user", format = "json", data = "")] pub async fn create_user(data: Json) -> (Status, Json>) { @@ -12,3 +17,11 @@ pub async fn create_user(data: Json) -> (Status, Json return (Status::InternalServerError, JsonResult::err(err)), } } + +#[get("/classroom/course/")] +pub async fn get_courses(user_id: i32) -> (Status, Json>>) { + match crate::online_classroom::get_courses::get_courses(user_id).await { + Ok(courses) => return (Status::Ok, JsonResult::ok(courses)), + Err(err) => return (Status::InternalServerError, JsonResult::err(err)), + } +} diff --git a/backend/src/controller/mod.rs b/backend/src/controller/mod.rs index 73a2962..eaf75b1 100644 --- a/backend/src/controller/mod.rs +++ b/backend/src/controller/mod.rs @@ -1,5 +1,5 @@ +pub mod classroom; pub mod course; pub mod custom_label; pub mod person; pub mod register; -pub mod classroom; diff --git a/backend/src/controller/person/mod.rs b/backend/src/controller/person/mod.rs index 7ef7570..f9d9950 100644 --- a/backend/src/controller/person/mod.rs +++ b/backend/src/controller/person/mod.rs @@ -129,21 +129,23 @@ pub async fn get_by_dni(dni: i32) -> (Status, Json>) { ) } - #[options("/person/link")] -pub fn options_p() -> Status { Status::Ok } +pub fn options_p() -> Status { + Status::Ok +} #[put("/person/link", format = "json", data = "")] pub async fn link_person(data: Json) -> (Status, Json>) { match data.0.insert().await { Ok(_) => (Status::Ok, JsonResult::ok(())), - Err(reason) => (Status::Ok, JsonResult::err(reason)) + Err(reason) => (Status::Ok, JsonResult::err(reason)), } } - #[options("/person")] -pub fn options() -> Status { Status::Ok } +pub fn options() -> Status { + Status::Ok +} #[post("/person", format = "json", data = "")] pub async fn create_person(person: Json) -> Status { diff --git a/backend/src/main.rs b/backend/src/main.rs index 9125f3b..06df1db 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -61,6 +61,7 @@ async fn rocket() -> _ { online_classroom::user::get_users, controller::classroom::create_user_options, controller::classroom::create_user, + controller::classroom::get_courses, ], ) } diff --git a/backend/src/model/classroom_user.rs b/backend/src/model/classroom_user.rs index 04f2ecc..a1f2318 100644 --- a/backend/src/model/classroom_user.rs +++ b/backend/src/model/classroom_user.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct ClassroomPerson { @@ -8,7 +8,6 @@ pub struct ClassroomPerson { pub user_id: String, } - #[derive(Debug, Deserialize)] pub struct ClassroomPersonCreate { pub person_email: String, diff --git a/backend/src/model/mod.rs b/backend/src/model/mod.rs index ee26e64..c96c4da 100644 --- a/backend/src/model/mod.rs +++ b/backend/src/model/mod.rs @@ -1,6 +1,6 @@ +pub mod classroom_user; pub mod course; pub mod custom_label; pub mod person; pub mod register; pub mod reniec_person; -pub mod classroom_user; diff --git a/backend/src/model/person.rs b/backend/src/model/person.rs index 9629054..d08fdc7 100644 --- a/backend/src/model/person.rs +++ b/backend/src/model/person.rs @@ -64,7 +64,6 @@ impl PersonCreate { } } - #[derive(Deserialize)] pub struct PersonLink { pub person_id: i32, @@ -80,8 +79,8 @@ impl PersonLink { self.person_classroom_id, self.person_id, ) - .execute(db) - .await; + .execute(db) + .await; match res { Ok(_) => Ok(()), diff --git a/backend/src/online_classroom/create_user.rs b/backend/src/online_classroom/create_user.rs index 2cc9e86..c54fd4e 100644 --- a/backend/src/online_classroom/create_user.rs +++ b/backend/src/online_classroom/create_user.rs @@ -1,6 +1,6 @@ -use scraper::{Selector, Html}; +use super::session::{create_user_request, request}; use crate::model::{classroom_user::ClassroomPersonCreate, person::PersonLink}; -use super::session::{request, create_user_request}; +use scraper::{Html, Selector}; const CREATION_ERR: &str = "Creation successful, but linking failed"; @@ -28,30 +28,33 @@ pub async fn create(data: &ClassroomPersonCreate) -> Result<(), String> { match users { Ok(user) if user.len() == 1 => { - let user_id: i32 = user[0].user_id.parse() - .or_else(|err| Err(format!("{}: Error parsing user_id: {:?}", CREATION_ERR, err)))?; + let user_id: i32 = user[0].user_id.parse().or_else(|err| { + Err(format!( + "{}: Error parsing user_id: {:?}", + CREATION_ERR, err + )) + })?; let result = PersonLink { person_id: data.person_id, person_classroom_id: user_id, - }.insert().await; + } + .insert() + .await; match result { Ok(_) => return Ok(()), - Err(reason) => { - return Err(format!("{}: {}", CREATION_ERR, reason)) - } + Err(reason) => return Err(format!("{}: {}", CREATION_ERR, reason)), } } Ok(u) if u.is_empty() => { - return Err(format!("{}: No users with username {} found", CREATION_ERR, data.person_username)) - } - Ok(_) => { - return Err(format!("{}: More than 1 user found", CREATION_ERR)) - } - Err(reason) => { - return Err(format!("{}: {}", CREATION_ERR, reason)) + return Err(format!( + "{}: No users with username {} found", + CREATION_ERR, data.person_username + )) } + Ok(_) => return Err(format!("{}: More than 1 user found", CREATION_ERR)), + Err(reason) => return Err(format!("{}: {}", CREATION_ERR, reason)), } } @@ -73,7 +76,11 @@ async fn get_form_sec_token() -> Result { let sec_token_value = match input_element.value().attr("value") { Some(val) => val, - None => return Err(format!("Error getting sec_token value from input. Not found")), + None => { + return Err(format!( + "Error getting sec_token value from input. Not found" + )) + } }; Ok(sec_token_value.into()) diff --git a/backend/src/online_classroom/get_courses.rs b/backend/src/online_classroom/get_courses.rs new file mode 100644 index 0000000..0c13d00 --- /dev/null +++ b/backend/src/online_classroom/get_courses.rs @@ -0,0 +1,99 @@ +use super::session::request; +use scraper::{Html, Selector}; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ClassroomCourse { + name: String, +} + +pub async fn get_courses(user_id: i32) -> Result, String> { + let html = request(format!( + "/main/admin/user_information.php?user_id={}", + user_id + )) + .await?; + + let fragment = Html::parse_document(&html); + + // Courses selector + let courses_selector = + Selector::parse(".table.table-hover.table-striped.table-bordered.data_table") + .or_else(|err| Err(format!("Error creating table selector: {:?}", err)))?; + // tr + let tr_selector = Selector::parse("tr") + .or_else(|err| Err(format!("Error creating tr selector: {:?}", err)))?; + // td + let td_selector = Selector::parse("td") + .or_else(|err| Err(format!("Error creating td selector: {:?}", err)))?; + + let table = match fragment.select(&courses_selector).next() { + Some(t) => t, + None => { + // The courses table was not found. + // If we are in the user page, it means that the user has no courses + if ensure_is_user_page(&fragment, user_id) { + return Ok(Vec::new()); + } else { + // If we are not in the user page, it means that there was an error + return Err(format!("Error selecting courses table. Not found")); + } + } + }; + + let mut courses: Vec = Vec::new(); + + let mut tr_iter = table.select(&tr_selector); + // Consume table header + tr_iter.next(); + + for table_row in tr_iter { + let td_els = table_row.select(&td_selector).collect::>(); + + if td_els.len() != 6 { + return Err(format!( + "Error parsing tr: td elements count is not 6, but {}", + td_els.len() + )); + } + + let course_name = td_els[1].inner_html(); + courses.push(ClassroomCourse { name: course_name }) + } + + Ok(courses) +} + +/// Searches in the document for the user toolbar, and checks if the user id matches +fn ensure_is_user_page(document: &Html, user_id: i32) -> bool { + // toolbar-user-information + + let user_toolbar = Selector::parse("#toolbar-user-information").unwrap(); + + let a_selector = Selector::parse("a").unwrap(); + + let toolbar_el = match document.select(&user_toolbar).next() { + Some(t) => t, + None => return false, + }; + + let a_element = match toolbar_el.select(&a_selector).next() { + Some(a) => a, + None => return false, + }; + + // Get href attribute + let href_value = a_element.value().attr("href").unwrap_or(""); + + // href should be like: https://testing.aulavirtual.eegsac.com/main/mySpace/myStudents.php?student=2142 + let user_id_start = match href_value.find("student=") { + Some(i) => i + 8, + None => return false, + }; + let user_id_str = href_value[user_id_start..].to_string(); + + match user_id_str.parse::() { + Ok(id) => id == user_id, + Err(_) => false, + } +} diff --git a/backend/src/online_classroom/mod.rs b/backend/src/online_classroom/mod.rs index 669b986..eaf70c7 100644 --- a/backend/src/online_classroom/mod.rs +++ b/backend/src/online_classroom/mod.rs @@ -4,9 +4,10 @@ use crate::json_result::JsonResult; use self::session::ensure_session; +pub mod create_user; +pub mod get_courses; mod session; pub mod user; -pub mod create_user; /// Tries to connect to the online classroom, and get a session cookie #[get("/classroom/connect")] diff --git a/backend/src/online_classroom/session.rs b/backend/src/online_classroom/session.rs index 7ccce81..f87100b 100644 --- a/backend/src/online_classroom/session.rs +++ b/backend/src/online_classroom/session.rs @@ -44,13 +44,12 @@ pub async fn request(url: String) -> Result { } } - /// Handles request for creating a user. -/// +/// /// Returns `Ok("")` if the request was redirected (i.e. the user was created successfully -/// +/// /// Returns `Ok(html)` if the request was not redirected (i.e. the user was not created successfully) -/// +/// /// Returns `Err(err)` if there was an error pub async fn create_user_request(url: String, body: String) -> Result { let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); @@ -77,7 +76,7 @@ pub async fn create_user_request(url: String, body: String) -> Result Result Result<(), String> { let last_usage_time = SESSION_TIME.read().unwrap().clone(); diff --git a/backend/src/online_classroom/user.rs b/backend/src/online_classroom/user.rs index 8dc916d..29716ff 100644 --- a/backend/src/online_classroom/user.rs +++ b/backend/src/online_classroom/user.rs @@ -5,8 +5,6 @@ use rocket::{http::Status, serde::json::Json}; use scraper::{ElementRef, Html, Selector}; use urlencoding::encode; - - // Instead of requesting pages and managing session & cookies manually, // create a wrapper that: // - Checks if the session cookie is valid @@ -26,7 +24,8 @@ pub async fn get_users_impl(search_param: String) -> Result let html = request(format!( "/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=", encode(search_param.as_str()) - )).await; + )) + .await; match html { Ok(html) => match parse_users(&html) { @@ -36,7 +35,7 @@ pub async fn get_users_impl(search_param: String) -> Result Err(reason) } }, - Err(reason) => Err(reason) + Err(reason) => Err(reason), } } diff --git a/frontend/src/OnlineClassroom/ClassroomUserCourses.tsx b/frontend/src/OnlineClassroom/ClassroomUserCourses.tsx new file mode 100644 index 0000000..1408e4b --- /dev/null +++ b/frontend/src/OnlineClassroom/ClassroomUserCourses.tsx @@ -0,0 +1,58 @@ +import { For, Show, createSignal, onMount } from "solid-js"; +import { LoadingStatus, backend, useLoading, wait } from "../utils/functions"; +import { JsonResult } from "../types/JsonResult"; +import { ClassroomCourse } from "../types/ClassroomCourse"; +import { LoadingIcon } from "../icons/LoadingIcon"; + +export function ClassroomUserCourses(props: {userid: number}) { + const [courses, setCourses] = createSignal>([]); + const {setError, status, setStatus} = useLoading(); + + const loadCourses = async() => { + setStatus(LoadingStatus.Loading); + setError(""); + + if (import.meta.env.DEV) await wait(1500); + + backend.get>>(`/api/classroom/course/${props.userid}`) + .then((res) => { + setCourses(res.data.Ok); + + setStatus(LoadingStatus.Ok); + }) + .catch((err) => { + console.log(err); + setError(err); + setStatus(LoadingStatus.Error); + }); + }; + + onMount(loadCourses); + + return ( +
+

+ Cursos matriculados: +

+ +
+ + + + + +
+ + + {(c) => (

{c.name}

)} +
+ +

Esta persona no está matriculada en ningún curso

+
+
+ ); +} diff --git a/frontend/src/OnlineClassroom/index.tsx b/frontend/src/OnlineClassroom/index.tsx index fd2e97d..7d23709 100644 --- a/frontend/src/OnlineClassroom/index.tsx +++ b/frontend/src/OnlineClassroom/index.tsx @@ -4,6 +4,7 @@ import { Person } from "../types/Person"; import { FilledCard } from "../components/FilledCard"; import { ClassroomUserCreation } from "./ClassroomUserCreation"; import { ClassroomVinculation } from "./ClassroomVinculation"; +import { ClassroomUserCourses } from "./ClassroomUserCourses"; type TabType = "Vinculate" | "Create"; @@ -22,9 +23,7 @@ export function OnlineClassroom() { /> -

- Esta persona tiene un usuario en el aula virtual. -

+
diff --git a/frontend/src/types/ClassroomCourse.ts b/frontend/src/types/ClassroomCourse.ts new file mode 100644 index 0000000..61ff968 --- /dev/null +++ b/frontend/src/types/ClassroomCourse.ts @@ -0,0 +1,3 @@ +export type ClassroomCourse = { + name: string +}