[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 rocket::{http::Status, serde::json::Json};
|
||||||
|
|
||||||
use crate::{json_result::JsonResult, model::classroom_user::ClassroomPersonCreate, online_classroom::create_user::create};
|
|
||||||
|
|
||||||
#[options("/classroom/user")]
|
#[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>")]
|
#[post("/classroom/user", format = "json", data = "<data>")]
|
||||||
pub async fn create_user(data: Json<ClassroomPersonCreate>) -> (Status, Json<JsonResult<()>>) {
|
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)),
|
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 course;
|
||||||
pub mod custom_label;
|
pub mod custom_label;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod register;
|
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")]
|
#[options("/person/link")]
|
||||||
pub fn options_p() -> Status { Status::Ok }
|
pub fn options_p() -> Status {
|
||||||
|
Status::Ok
|
||||||
|
}
|
||||||
|
|
||||||
#[put("/person/link", format = "json", data = "<data>")]
|
#[put("/person/link", format = "json", data = "<data>")]
|
||||||
pub async fn link_person(data: Json<PersonLink>) -> (Status, Json<JsonResult<()>>) {
|
pub async fn link_person(data: Json<PersonLink>) -> (Status, Json<JsonResult<()>>) {
|
||||||
match data.0.insert().await {
|
match data.0.insert().await {
|
||||||
Ok(_) => (Status::Ok, JsonResult::ok(())),
|
Ok(_) => (Status::Ok, JsonResult::ok(())),
|
||||||
Err(reason) => (Status::Ok, JsonResult::err(reason))
|
Err(reason) => (Status::Ok, JsonResult::err(reason)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[options("/person")]
|
#[options("/person")]
|
||||||
pub fn options() -> Status { Status::Ok }
|
pub fn options() -> Status {
|
||||||
|
Status::Ok
|
||||||
|
}
|
||||||
|
|
||||||
#[post("/person", format = "json", data = "<person>")]
|
#[post("/person", format = "json", data = "<person>")]
|
||||||
pub async fn create_person(person: Json<PersonCreate>) -> Status {
|
pub async fn create_person(person: Json<PersonCreate>) -> Status {
|
||||||
|
@ -61,6 +61,7 @@ async fn rocket() -> _ {
|
|||||||
online_classroom::user::get_users,
|
online_classroom::user::get_users,
|
||||||
controller::classroom::create_user_options,
|
controller::classroom::create_user_options,
|
||||||
controller::classroom::create_user,
|
controller::classroom::create_user,
|
||||||
|
controller::classroom::get_courses,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ClassroomPerson {
|
pub struct ClassroomPerson {
|
||||||
@ -8,7 +8,6 @@ pub struct ClassroomPerson {
|
|||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
pub struct ClassroomPersonCreate {
|
pub struct ClassroomPersonCreate {
|
||||||
pub person_email: String,
|
pub person_email: String,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
pub mod classroom_user;
|
||||||
pub mod course;
|
pub mod course;
|
||||||
pub mod custom_label;
|
pub mod custom_label;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
pub mod register;
|
pub mod register;
|
||||||
pub mod reniec_person;
|
pub mod reniec_person;
|
||||||
pub mod classroom_user;
|
|
||||||
|
@ -64,7 +64,6 @@ impl PersonCreate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct PersonLink {
|
pub struct PersonLink {
|
||||||
pub person_id: i32,
|
pub person_id: i32,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use scraper::{Selector, Html};
|
use super::session::{create_user_request, request};
|
||||||
use crate::model::{classroom_user::ClassroomPersonCreate, person::PersonLink};
|
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";
|
const CREATION_ERR: &str = "Creation successful, but linking failed";
|
||||||
|
|
||||||
@ -28,30 +28,33 @@ pub async fn create(data: &ClassroomPersonCreate) -> Result<(), String> {
|
|||||||
|
|
||||||
match users {
|
match users {
|
||||||
Ok(user) if user.len() == 1 => {
|
Ok(user) if user.len() == 1 => {
|
||||||
let user_id: i32 = user[0].user_id.parse()
|
let user_id: i32 = user[0].user_id.parse().or_else(|err| {
|
||||||
.or_else(|err| Err(format!("{}: Error parsing user_id: {:?}", CREATION_ERR, err)))?;
|
Err(format!(
|
||||||
|
"{}: Error parsing user_id: {:?}",
|
||||||
|
CREATION_ERR, err
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
|
||||||
let result = PersonLink {
|
let result = PersonLink {
|
||||||
person_id: data.person_id,
|
person_id: data.person_id,
|
||||||
person_classroom_id: user_id,
|
person_classroom_id: user_id,
|
||||||
}.insert().await;
|
}
|
||||||
|
.insert()
|
||||||
|
.await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => return Ok(()),
|
Ok(_) => return Ok(()),
|
||||||
Err(reason) => {
|
Err(reason) => return Err(format!("{}: {}", CREATION_ERR, reason)),
|
||||||
return Err(format!("{}: {}", CREATION_ERR, reason))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(u) if u.is_empty() => {
|
Ok(u) if u.is_empty() => {
|
||||||
return Err(format!("{}: No users with username {} found", CREATION_ERR, data.person_username))
|
return Err(format!(
|
||||||
}
|
"{}: No users with username {} found",
|
||||||
Ok(_) => {
|
CREATION_ERR, data.person_username
|
||||||
return Err(format!("{}: More than 1 user found", CREATION_ERR))
|
))
|
||||||
}
|
|
||||||
Err(reason) => {
|
|
||||||
return Err(format!("{}: {}", CREATION_ERR, reason))
|
|
||||||
}
|
}
|
||||||
|
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") {
|
let sec_token_value = match input_element.value().attr("value") {
|
||||||
Some(val) => val,
|
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())
|
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;
|
use self::session::ensure_session;
|
||||||
|
|
||||||
|
pub mod create_user;
|
||||||
|
pub mod get_courses;
|
||||||
mod session;
|
mod session;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
pub mod create_user;
|
|
||||||
|
|
||||||
/// Tries to connect to the online classroom, and get a session cookie
|
/// Tries to connect to the online classroom, and get a session cookie
|
||||||
#[get("/classroom/connect")]
|
#[get("/classroom/connect")]
|
||||||
|
@ -44,7 +44,6 @@ pub async fn request(url: String) -> Result<String, String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Handles request for creating a user.
|
/// Handles request for creating a user.
|
||||||
///
|
///
|
||||||
/// Returns `Ok("")` if the request was redirected (i.e. the user was created successfully
|
/// Returns `Ok("")` if the request was redirected (i.e. the user was created successfully
|
||||||
@ -77,7 +76,7 @@ pub async fn create_user_request(url: String, body: String) -> Result<String, St
|
|||||||
|
|
||||||
if response.status() == isahc::http::StatusCode::FOUND {
|
if response.status() == isahc::http::StatusCode::FOUND {
|
||||||
println!("Redirected!");
|
println!("Redirected!");
|
||||||
return Ok("".into())
|
return Ok("".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
match response.text() {
|
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
|
/// Makes sure that the session cookie is set, and that it is valid
|
||||||
pub async fn ensure_session() -> Result<(), String> {
|
pub async fn ensure_session() -> Result<(), String> {
|
||||||
let last_usage_time = SESSION_TIME.read().unwrap().clone();
|
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 scraper::{ElementRef, Html, Selector};
|
||||||
use urlencoding::encode;
|
use urlencoding::encode;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Instead of requesting pages and managing session & cookies manually,
|
// Instead of requesting pages and managing session & cookies manually,
|
||||||
// create a wrapper that:
|
// create a wrapper that:
|
||||||
// - Checks if the session cookie is valid
|
// - 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!(
|
let html = request(format!(
|
||||||
"/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=",
|
"/main/admin/user_list.php?keyword={}&submit=&_qf__search_simple=",
|
||||||
encode(search_param.as_str())
|
encode(search_param.as_str())
|
||||||
)).await;
|
))
|
||||||
|
.await;
|
||||||
|
|
||||||
match html {
|
match html {
|
||||||
Ok(html) => match parse_users(&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) => 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 { FilledCard } from "../components/FilledCard";
|
||||||
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
import { ClassroomUserCreation } from "./ClassroomUserCreation";
|
||||||
import { ClassroomVinculation } from "./ClassroomVinculation";
|
import { ClassroomVinculation } from "./ClassroomVinculation";
|
||||||
|
import { ClassroomUserCourses } from "./ClassroomUserCourses";
|
||||||
|
|
||||||
type TabType = "Vinculate" | "Create";
|
type TabType = "Vinculate" | "Create";
|
||||||
|
|
||||||
@ -22,9 +23,7 @@ export function OnlineClassroom() {
|
|||||||
/>
|
/>
|
||||||
</Show>
|
</Show>
|
||||||
<Show when={person() !== null && person()!.person_classroom_id !== null}>
|
<Show when={person() !== null && person()!.person_classroom_id !== null}>
|
||||||
<p>
|
<ClassroomUserCourses userid={person()!.person_classroom_id!} />
|
||||||
Esta persona tiene un usuario en el aula virtual.
|
|
||||||
</p>
|
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
</div>
|
</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