[Classroom] Disenroll user from a course

This commit is contained in:
Araozu 2023-12-23 10:42:34 -05:00
parent 54c61b8b2a
commit b4616e0ba5
8 changed files with 121 additions and 14 deletions

View File

@ -0,0 +1,27 @@
use rocket::{http::Status, serde::json::Json};
use serde::Deserialize;
use crate::json_result::JsonResult;
use crate::online_classroom::course_disenroll::disenroll;
#[derive(Debug, Deserialize)]
pub struct DisenrollData {
pub course_id: i32,
pub user_id: i32,
}
#[options("/classroom/disenroll")]
pub fn disenroll_user_options() -> Status {
Status::Ok
}
#[post("/classroom/disenroll", format = "json", data = "<data>")]
pub async fn disenroll_user(data: Json<DisenrollData>) -> Json<JsonResult<()>> {
match disenroll(data.course_id, data.user_id).await {
Ok(_) => JsonResult::ok(()),
Err(err) => {
log::error!("Error disenrolling user: {}", err);
JsonResult::err(err)
}
}
}

View File

@ -10,6 +10,11 @@ use crate::{
}; };
use rocket::{http::Status, serde::json::Json}; use rocket::{http::Status, serde::json::Json};
mod courses;
pub use courses::disenroll_user_options;
pub use courses::disenroll_user;
#[options("/classroom/user")] #[options("/classroom/user")]
pub fn create_user_options() -> Status { pub fn create_user_options() -> Status {
Status::Ok Status::Ok

View File

@ -143,6 +143,8 @@ async fn rocket() -> _ {
controller::classroom::create_user_options, controller::classroom::create_user_options,
controller::classroom::create_user, controller::classroom::create_user,
controller::classroom::get_courses, controller::classroom::get_courses,
controller::classroom::disenroll_user_options,
controller::classroom::disenroll_user,
controller::classroom::register_course_contr_options, controller::classroom::register_course_contr_options,
controller::classroom::register_course_contr, controller::classroom::register_course_contr,
controller::classroom::get_expiration_date, controller::classroom::get_expiration_date,

View File

@ -0,0 +1,9 @@
use super::session::classroom_get_redirect;
pub async fn disenroll(course_id: i32, user_id: i32) -> Result<(), String> {
classroom_get_redirect(format!(
"/main/admin/user_information.php?action=unsubscribe&course_id={}&user_id={}",
course_id, user_id
))
.await
}

View File

@ -4,6 +4,7 @@ use crate::json_result::JsonResult;
use self::session::ensure_session; use self::session::ensure_session;
pub mod course_disenroll;
pub mod create_user; pub mod create_user;
pub mod get_courses; pub mod get_courses;
pub mod get_expiration_date; pub mod get_expiration_date;
@ -11,7 +12,6 @@ pub mod register_course;
mod session; mod session;
pub mod update_expiration_date; pub mod update_expiration_date;
pub mod user; pub mod user;
pub mod course_disenroll;
/// Tries to connect to the online classroom, and gets a session cookie /// Tries to connect to the online classroom, and gets a session cookie
/// if neccesary. /// if neccesary.

View File

@ -58,6 +58,42 @@ pub async fn classroom_post_redirect(
} }
} }
/// Make a get request & expect an http 302 redirect
pub async fn classroom_get_redirect(url: String) -> Result<(), String> {
let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");
ensure_session().await?;
// Get the stored client
let jar = SESSION_COOKIE.read().unwrap().jar.clone();
let uri = format!("{}{}", classroom_url, url);
// Do the request
let response = Request::get(uri)
.cookie_jar(jar)
.body("")
.or_else(|err| Err(format!("Error creating request: {:?}", err)))?
.send();
let mut response = match response {
Ok(r) => r,
Err(err) => return Err(format!("Error sending request: {:?}", err)),
};
if response.status() == isahc::http::StatusCode::FOUND {
return Ok(());
}
match response.text() {
Ok(t) => {
log_html(&t);
Err(format!("Expected HTTP 302, got other code."))
}
Err(err) => Err(format!("Error getting text from response: {:?}", err)),
}
}
/// Makes a request to the online classroom, and returns the html string /// Makes a request to the online classroom, and returns the html string
pub async fn request(url: String) -> Result<String, String> { pub async fn request(url: String) -> Result<String, String> {
let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!"); let classroom_url = std::env::var("CLASSROOM_URL").expect("CLASSROOM_URL env var is not set!");

View File

@ -7,8 +7,8 @@ import { OutlinedCard } from "../../components/OutlinedCard";
import { XIcon } from "../../icons/XIcon"; import { XIcon } from "../../icons/XIcon";
import { QuestionIcon } from "../../icons/QuestionIcon"; import { QuestionIcon } from "../../icons/QuestionIcon";
export function CoursesList(props: {userid: number, updateSignal: number, courses: Array<ClassroomCourse>, setCourses: (c: Array<ClassroomCourse>) => void}) { export function CoursesList(props: { userid: number, updateSignal: number, courses: Array<ClassroomCourse>, setCourses: (c: Array<ClassroomCourse>) => void }) {
const {error, setError, status, setStatus} = useLoading(); const { error, setError, status, setStatus } = useLoading();
const loadCourses = async() => { const loadCourses = async() => {
setStatus(LoadingStatus.Loading); setStatus(LoadingStatus.Loading);
@ -58,7 +58,7 @@ export function CoursesList(props: {userid: number, updateSignal: number, course
</div> </div>
<For each={props.courses}> <For each={props.courses}>
{(c) => <Course course={c} />} {(c) => <Course course={c} user_id={props.userid} onDelete={loadCourses} />}
</For> </For>
<Show when={error() !== ""}> <Show when={error() !== ""}>
@ -79,7 +79,34 @@ export function CoursesList(props: {userid: number, updateSignal: number, course
); );
} }
function Course(props: {course: ClassroomCourse}) { function Course(props: {
course: ClassroomCourse,
user_id: number,
onDelete: () => void,
}) {
const { error, setError, status, setStatus } = useLoading();
const disenroll = () => {
console.log(`in the process of disenroll... ${props.course.course_id}`);
setStatus(LoadingStatus.Loading);
setError("");
backend.post("/api/classroom/disenroll", {
course_id: props.course.course_id,
user_id: props.user_id,
})
.then(() => {
setStatus(LoadingStatus.Ok);
props.onDelete();
})
.catch((err) => {
alert("Error desmatriculando. Ver consola");
console.log(err);
// setError(err);
setStatus(LoadingStatus.Error);
});
};
return ( return (
<div <div
class="px-4 py-3 transition-colors class="px-4 py-3 transition-colors
@ -89,7 +116,8 @@ function Course(props: {course: ClassroomCourse}) {
<p>{props.course.name}</p> <p>{props.course.name}</p>
<button <button
class="disabled:opacity-50 disabled:cursor-not-allowed" class="disabled:opacity-50 disabled:cursor-not-allowed"
disabled onclick={disenroll}
disabled={status() === LoadingStatus.Loading}
> >
<XIcon fill="var(--c-error)" /> <XIcon fill="var(--c-error)" />
</button> </button>

View File

@ -1,5 +1,5 @@
import { HomeIcon } from "../icons/HomeIcon"; import { HomeIcon } from "../icons/HomeIcon";
import {createSignal, Show, type JSX, For} from "solid-js"; import { createSignal, Show, type JSX, For } from "solid-js";
import { DocxIcon } from "../icons/DocxIcon"; import { DocxIcon } from "../icons/DocxIcon";
import { ScanIcon } from "../icons/ScanIcon"; import { ScanIcon } from "../icons/ScanIcon";
import { KeyIcon } from "../icons/KeyIcon"; import { KeyIcon } from "../icons/KeyIcon";
@ -18,7 +18,7 @@ export function NavRail() {
<NavRailButton path="/certs" name="Certs" icon={<DocxIcon />} /> <NavRailButton path="/certs" name="Certs" icon={<DocxIcon />} />
<NavRailButton path="/accesos" name="Accesos" icon={<KeyIcon />} /> <NavRailButton path="/accesos" name="Accesos" icon={<KeyIcon />} />
<NavRailButton path="/escaneo" name="Escaneo" icon={<ScanIcon />} /> <NavRailButton path="/escaneo" name="Escaneo" icon={<ScanIcon />} />
<NavRailButton path="/reportes" name="Reportes" icon={<ChartScatterIcon />} /> <NavRailButton path="/reportes" name="Reportes" icon={<ChartScatterIcon fill="var(--c-on-surface-variant)" />} />
<ColorSelector /> <ColorSelector />
</div> </div>
<div id="color-selector" /> <div id="color-selector" />
@ -54,7 +54,7 @@ function NavRailButton(props: {
function ColorSelector() { function ColorSelector() {
const [showSelector, setShowSelector] = createSignal(false); const [showSelector, setShowSelector] = createSignal(false);
const colors: Array<{name: string, color: string}> = [ const colors: Array<{ name: string, color: string }> = [
{ {
name: "Verde", name: "Verde",
color: "green", color: "green",
@ -111,7 +111,7 @@ function ColorSelector() {
<For <For
each={colors} each={colors}
> >
{({name, color}) => <ColorButton name={name} color={color} />} {({ name, color }) => <ColorButton name={name} color={color} />}
</For> </For>
</div> </div>
</Dialog> </Dialog>
@ -121,7 +121,7 @@ function ColorSelector() {
); );
} }
function ColorButton(props: {name: string, color: string}) { function ColorButton(props: { name: string, color: string }) {
const select = () => { const select = () => {
applyColors(props.color); applyColors(props.color);
}; };
@ -130,12 +130,12 @@ function ColorButton(props: {name: string, color: string}) {
<button <button
class="text-center bg-c-outline rounded pt-2 class="text-center bg-c-outline rounded pt-2
border border-c-transparent hover:border-c-outline transition-colors" border border-c-transparent hover:border-c-outline transition-colors"
style={{"background-color": `var(--c-${props.color}-surface-variant)`}} style={{ "background-color": `var(--c-${props.color}-surface-variant)` }}
onclick={select} onclick={select}
> >
<span <span
class="inline-block w-8 h-8 rounded-full" class="inline-block w-8 h-8 rounded-full"
style={{"background-color": `var(--c-${props.color}-primary)`}} style={{ "background-color": `var(--c-${props.color}-primary)` }}
/> />
<br /> <br />
{props.name} {props.name}