[Classroom] Get course list for an user
This commit is contained in:
parent
b5c6f9caca
commit
60e461f3c0
@ -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 = "<data>")]
|
||||
pub async fn create_user(data: Json<ClassroomPersonCreate>) -> (Status, Json<JsonResult<()>>) {
|
||||
@ -12,3 +17,11 @@ pub async fn create_user(data: Json<ClassroomPersonCreate>) -> (Status, Json<Jso
|
||||
Err(err) => return (Status::InternalServerError, JsonResult::err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/classroom/course/<user_id>")]
|
||||
pub async fn get_courses(user_id: i32) -> (Status, Json<JsonResult<Vec<ClassroomCourse>>>) {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
pub mod classroom;
|
||||
pub mod course;
|
||||
pub mod custom_label;
|
||||
pub mod person;
|
||||
pub mod register;
|
||||
pub mod classroom;
|
||||
|
@ -129,21 +129,23 @@ pub async fn get_by_dni(dni: i32) -> (Status, Json<JsonResult<Person>>) {
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
#[options("/person/link")]
|
||||
pub fn options_p() -> Status { Status::Ok }
|
||||
pub fn options_p() -> Status {
|
||||
Status::Ok
|
||||
}
|
||||
|
||||
#[put("/person/link", format = "json", data = "<data>")]
|
||||
pub async fn link_person(data: Json<PersonLink>) -> (Status, Json<JsonResult<()>>) {
|
||||
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 = "<person>")]
|
||||
pub async fn create_person(person: Json<PersonCreate>) -> Status {
|
||||
|
@ -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,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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(()),
|
||||
|
@ -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<String, String> {
|
||||
|
||||
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())
|
||||
|
99
backend/src/online_classroom/get_courses.rs
Normal file
99
backend/src/online_classroom/get_courses.rs
Normal file
@ -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<Vec<ClassroomCourse>, 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<ClassroomCourse> = 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::<Vec<_>>();
|
||||
|
||||
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::<i32>() {
|
||||
Ok(id) => id == user_id,
|
||||
Err(_) => false,
|
||||
}
|
||||
}
|
@ -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")]
|
||||
|
@ -44,13 +44,12 @@ pub async fn request(url: String) -> Result<String, String> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// 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<String, String> {
|
||||
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<String, St
|
||||
|
||||
if response.status() == isahc::http::StatusCode::FOUND {
|
||||
println!("Redirected!");
|
||||
return Ok("".into())
|
||||
return Ok("".into());
|
||||
}
|
||||
|
||||
match response.text() {
|
||||
@ -86,7 +85,6 @@ pub async fn create_user_request(url: String, body: String) -> Result<String, St
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Makes sure that the session cookie is set, and that it is valid
|
||||
pub async fn ensure_session() -> Result<(), String> {
|
||||
let last_usage_time = SESSION_TIME.read().unwrap().clone();
|
||||
|
@ -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<Vec<ClassroomPerson>
|
||||
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<Vec<ClassroomPerson>
|
||||
Err(reason)
|
||||
}
|
||||
},
|
||||
Err(reason) => Err(reason)
|
||||
Err(reason) => Err(reason),
|
||||
}
|
||||
}
|
||||
|
||||
|
58
frontend/src/OnlineClassroom/ClassroomUserCourses.tsx
Normal file
58
frontend/src/OnlineClassroom/ClassroomUserCourses.tsx
Normal file
@ -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<Array<ClassroomCourse>>([]);
|
||||
const {setError, status, setStatus} = useLoading();
|
||||
|
||||
const loadCourses = async() => {
|
||||
setStatus(LoadingStatus.Loading);
|
||||
setError("");
|
||||
|
||||
if (import.meta.env.DEV) await wait(1500);
|
||||
|
||||
backend.get<JsonResult<Array<ClassroomCourse>>>(`/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 (
|
||||
<div class="w-full">
|
||||
<h2 class="text-2xl">
|
||||
Cursos matriculados:
|
||||
</h2>
|
||||
|
||||
<div class="py-2 text-center">
|
||||
<Show when={status() === LoadingStatus.Loading}>
|
||||
<span class="inline-block scale-[200%]">
|
||||
<LoadingIcon
|
||||
class="animate-spin mr-2"
|
||||
fill="var(--c-primary)"
|
||||
size={16}
|
||||
/>
|
||||
</span>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<For each={courses()}>
|
||||
{(c) => (<p>{c.name}</p>)}
|
||||
</For>
|
||||
<Show when={status() === LoadingStatus.Ok && courses().length === 0}>
|
||||
<p>Esta persona no está matriculada en ningún curso</p>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -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() {
|
||||
/>
|
||||
</Show>
|
||||
<Show when={person() !== null && person()!.person_classroom_id !== null}>
|
||||
<p>
|
||||
Esta persona tiene un usuario en el aula virtual.
|
||||
</p>
|
||||
<ClassroomUserCourses userid={person()!.person_classroom_id!} />
|
||||
</Show>
|
||||
|
||||
</div>
|
||||
|
3
frontend/src/types/ClassroomCourse.ts
Normal file
3
frontend/src/types/ClassroomCourse.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export type ClassroomCourse = {
|
||||
name: string
|
||||
}
|
Loading…
Reference in New Issue
Block a user