diff --git a/backend/src/controller/classroom/mod.rs b/backend/src/controller/classroom/mod.rs new file mode 100644 index 0000000..9496946 --- /dev/null +++ b/backend/src/controller/classroom/mod.rs @@ -0,0 +1,14 @@ +use rocket::{http::Status, serde::json::Json}; + +use crate::{json_result::JsonResult, model::classroom_user::ClassroomPersonCreate}; + +#[options("/classroom/user")] +pub fn create_user_options() -> Status { Status::Ok } + +#[post("/classroom/user", format = "json", data = "")] +pub fn create_user(data: Json) -> (Status, Json>) { + + + + (Status::InternalServerError, JsonResult::err("Error creando usuario".into())) +} diff --git a/backend/src/controller/mod.rs b/backend/src/controller/mod.rs index 2e8eba9..73a2962 100644 --- a/backend/src/controller/mod.rs +++ b/backend/src/controller/mod.rs @@ -2,3 +2,4 @@ pub mod course; pub mod custom_label; pub mod person; pub mod register; +pub mod classroom; diff --git a/backend/src/main.rs b/backend/src/main.rs index c0d3a7d..9125f3b 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -58,7 +58,9 @@ async fn rocket() -> _ { // Online classroom routes // online_classroom::connection, - online_classroom::users::get_users, + online_classroom::user::get_users, + controller::classroom::create_user_options, + controller::classroom::create_user, ], ) } diff --git a/backend/src/model/classroom_user.rs b/backend/src/model/classroom_user.rs new file mode 100644 index 0000000..04f2ecc --- /dev/null +++ b/backend/src/model/classroom_user.rs @@ -0,0 +1,21 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct ClassroomPerson { + pub name: String, + pub surname: String, + pub username: String, + pub user_id: String, +} + + +#[derive(Debug, Deserialize)] +pub struct ClassroomPersonCreate { + pub person_email: String, + pub person_expiration_date: String, + pub person_id: i32, + pub person_names: String, + pub person_surnames: String, + pub person_username: String, + pub person_password: String, +} diff --git a/backend/src/model/mod.rs b/backend/src/model/mod.rs index 929d84e..ee26e64 100644 --- a/backend/src/model/mod.rs +++ b/backend/src/model/mod.rs @@ -3,3 +3,4 @@ pub mod custom_label; pub mod person; pub mod register; pub mod reniec_person; +pub mod classroom_user; diff --git a/backend/src/online_classroom/create_user.rs b/backend/src/online_classroom/create_user.rs new file mode 100644 index 0000000..5c5805b --- /dev/null +++ b/backend/src/online_classroom/create_user.rs @@ -0,0 +1,225 @@ +use scraper::{Selector, Html}; + +use crate::model::classroom_user::ClassroomPersonCreate; + +use super::session::request; + + +/// Creates an online classroom user +pub async fn create(data: &ClassroomPersonCreate) -> Result<(), String> { + let sec_token = get_form_sec_token().await?; + + let body = get_request_body( + &data.person_surnames, + &data.person_names, + &data.person_email, + &data.person_username, + &data.person_password, + &data.person_expiration_date, + &sec_token, + ); + + Err("Not implemented".into()) +} + +async fn get_form_sec_token() -> Result { + let creation_form = request("/main/admin/user_add.php".into()).await?; + + let sec_token_selector = Selector::parse("#user_add_sec_token") + .or_else(|err| Err(format!("Error creating sec_token selector: {:?}", err)))?; + + let fragment = Html::parse_document(&creation_form); + + let input_element = match fragment.select(&sec_token_selector).next() { + Some(el) => el, + None => return Err(format!("Error selecting sec_token element. Not found")), + }; + + 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")), + }; + + Ok(sec_token_value.into()) +} + +fn get_request_body( + surnames: &String, + names: &String, + email: &String, + username: &String, + password: &String, + expiration_date: &String, + sec_token: &String, +) -> String { + format!( + r#" +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="lastname" + +{surnames} +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="firstname" + +{names} +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="official_code" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="email" + +{email} +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="phone" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="picture"; filename="" +Content-Type: application/octet-stream + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="username" + +{username} +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="password[password_auto]" + +0 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="password[password]" + +{password} +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="status" + +5 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="admin[platform_admin]" + +0 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="language" + +spanish +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="mail[send_mail]" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="radio_expiration_date" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="expiration_date" + +{expiration_date} 23:59 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="active" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_legal_accept" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_already_logged_in" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_update_type" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_rssfeeds" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_dashboard" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_timezone" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_mail_notify_invitation" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_mail_notify_message" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_mail_notify_group_message" + +1 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_user_chat_status" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_google_calendar_url" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_captcha_blocked_until_date" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_skype" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_linkedin_url" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal_justification" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_request_for_delete_account_justification" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_request_for_legal_agreement_consent_removal" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="extra_request_for_delete_account" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="submit" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="_qf__user_add" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="MAX_FILE_SIZE" + +536870912 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="picture_crop_result" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="picture_crop_image_base_64" + + +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="item_id" + +0 +-----------------------------83919643214156711801978607619 +Content-Disposition: form-data; name="sec_token" + +{sec_token} +-----------------------------83919643214156711801978607619-- +"# + ) +} diff --git a/backend/src/online_classroom/mod.rs b/backend/src/online_classroom/mod.rs index 2283e96..669b986 100644 --- a/backend/src/online_classroom/mod.rs +++ b/backend/src/online_classroom/mod.rs @@ -5,7 +5,8 @@ use crate::json_result::JsonResult; use self::session::ensure_session; mod session; -pub mod users; +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/users.rs b/backend/src/online_classroom/user.rs similarity index 94% rename from backend/src/online_classroom/users.rs rename to backend/src/online_classroom/user.rs index b158a3e..5f0fe05 100644 --- a/backend/src/online_classroom/users.rs +++ b/backend/src/online_classroom/user.rs @@ -1,18 +1,11 @@ -use crate::json_result::JsonResult; +use crate::{json_result::JsonResult, model::classroom_user::ClassroomPerson}; use super::session::request; use rocket::{http::Status, serde::json::Json}; use scraper::{ElementRef, Html, Selector}; -use serde::{Deserialize, Serialize}; use urlencoding::encode; -#[derive(Debug, Serialize, Deserialize)] -pub struct ClassroomPerson { - name: String, - surname: String, - username: String, - user_id: String, -} + // Instead of requesting pages and managing session & cookies manually, // create a wrapper that: @@ -127,7 +120,7 @@ fn get_person_data(td_vec: &Vec) -> Result // Get the username let username = username_ref.inner_html(); - // Parse userid from href + // Parse user_id from href // format: https://testing.aulavirtual.eegsac.com/main/admin/user_information.php?user_id=1087 // Get the position of 'user_id=' let user_id_start = href_value.find("user_id=").ok_or("Error parsing user_id")? + 8; diff --git a/frontend/src/OnlineClassroom/ClassroomUserCreation.tsx b/frontend/src/OnlineClassroom/ClassroomUserCreation.tsx index d36bcd7..8c08076 100644 --- a/frontend/src/OnlineClassroom/ClassroomUserCreation.tsx +++ b/frontend/src/OnlineClassroom/ClassroomUserCreation.tsx @@ -1,11 +1,68 @@ -import { createSignal } from "solid-js"; +import { Show, createSignal, onMount } from "solid-js"; import { FilledButton } from "../components/FilledButton"; import { UserPlusIcon } from "../icons/UserPlusIcon"; import { MaterialInput } from "../components/MaterialInput"; +import { Person } from "../types/Person"; +import { LoadingStatus, backend, useLoading, wait } from "../utils/functions"; +import { JsonResult } from "../types/JsonResult"; +import { LoadingIcon } from "../icons/LoadingIcon"; -export function ClassroomUserCreation() { +export function ClassroomUserCreation(props: {person: Person}) { const [email, setEmail] = createSignal("yuli.palo.apaza@gmail.com"); - const [loading, setLoading] = createSignal(false); + const [username, setUsername] = createSignal("USERNAME"); + const {setError, status, setStatus} = useLoading(); + const [names, setNames] = createSignal(props.person.person_names); + const [surnames, setSurnames] = createSignal(`${props.person.person_paternal_surname} ${props.person.person_maternal_surname}`); + const [password, setPassword] = createSignal(props.person.person_dni); + const [date, setDate] = createSignal("2023-10-03"); + + onMount(() => { + // Setup initial values + const [firstName, secondName] = props.person.person_names.split(" "); + const paternalSurname = props.person.person_paternal_surname; + const maternalSurname = props.person.person_maternal_surname; + + const user = `${firstName[0]}${secondName[0] ?? ""}${paternalSurname}${maternalSurname[0]}`; + + const next_date = new Date(); + next_date.setDate(next_date.getDate() + 60); + setDate(next_date.toISOString().split("T")[0]); + + setUsername(user.toUpperCase()); + }); + + const createClassroomUser = async() => { + setStatus(LoadingStatus.Loading); + console.log("creating..."); + + if (import.meta.env.DEV) await wait(1500); + + backend.post>("/api/classroom/user", { + person_id: props.person.person_id, + person_names: names(), + person_surnames: surnames(), + person_email: email(), + person_username: username(), + person_password: password(), + person_expiration_date: date(), + }) + .then((response) => { + if (response.status === 200) { + alert("Usuario creado con éxito"); + setStatus(LoadingStatus.Ok); + } else { + console.error(response.data); + setError(response.data.Err.reason); + setStatus(LoadingStatus.Error); + } + }) + .catch((err) => { + console.error(err); + setError(`Error fatal: ${err.message}`); + alert(`Error fatal: ${err.message}`); + setStatus(LoadingStatus.Error); + }); + }; return (
@@ -13,19 +70,69 @@ export function ClassroomUserCreation() { O cree un nuevo usuario:

-
+ { + ev.preventDefault(); + createClassroomUser(); + }} + > + + +
+ setUsername(v.toUpperCase())} + disabled={status() === LoadingStatus.Loading} + /> + +
+ - - - - Crear usuario - + + + + + + + + Crear usuario + +
); } diff --git a/frontend/src/OnlineClassroom/ClassroomVinculation.tsx b/frontend/src/OnlineClassroom/ClassroomVinculation.tsx new file mode 100644 index 0000000..be78e9a --- /dev/null +++ b/frontend/src/OnlineClassroom/ClassroomVinculation.tsx @@ -0,0 +1,148 @@ +import { ClassroomRegistrationUser } from "../types/ClassroomRegistrationUser"; +import { QuestionIcon } from "../icons/QuestionIcon"; +import { JsonResult } from "../types/JsonResult"; +import { XcircleIcon } from "../icons/XCircleIcon"; +import { LoadingStatus, backend, useLoading, wait } from "../utils/functions"; +import { LinkIcon } from "../icons/LinkIcon"; +import { For, Show, createSignal, onMount } from "solid-js"; +import { LoadingIcon } from "../icons/LoadingIcon"; + +type Status = "Init" | "Ok" | "Loading" | "Error"; +export function ClassroomVinculation(props: {person_surname: string, personId: number, onLink: (classroom_id: number) => void,}) { + const [classroomUsers, setClassroomUsers] = createSignal([]); + const [error, setError] = createSignal(""); + const [status, setStatus] = createSignal("Init"); + + const loadUsers = async() => { + setStatus("Loading"); + const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/classroom/users/${encodeURIComponent(props.person_surname)}`); + const json: JsonResult> = await response.json(); + + if (response.ok) { + setClassroomUsers(json.Ok); + setStatus("Ok"); + } else { + console.error("Error loading users", json); + setError(json.Err.reason); + setStatus("Error"); + } + }; + + onMount(loadUsers); + + return ( +
+

+ Vincule un usuario existente: +

+ + +
+ +
+
+ + + +
+
+ +
+

No se encontraron usuarios en el aula virtual.

+
+
+ + {(u) => ( + + )} + +
+ + +
+
+ +
+

Error buscando usuarios: {error()}.

+
+
+
+ ); +} + + + +function ClassroomSingleUser(props: { + user: ClassroomRegistrationUser, + onLink: (classroom_id: number) => void, + personId: number, +}) { + const {setError, status, setStatus} = useLoading(); + + const linkUser = async() => { + setStatus(LoadingStatus.Loading); + + if (import.meta.env.DEV) await wait(1500); + + backend.put>( + "/api/person/link", + { + person_id: props.personId, + person_classroom_id: parseInt(props.user.user_id, 10), + }, + ) + .then((response) => { + if (response.status === 200) { + setStatus(LoadingStatus.Ok); + props.onLink(parseInt(props.user.user_id, 10)); + } else { + console.error(response.data); + setError(response.data.Err.reason); + setStatus(LoadingStatus.Error); + } + }) + .catch((err) => { + setError(`Error fatal: ${err.message}`); + console.error(err); + setStatus(LoadingStatus.Error); + }); + }; + + return ( +
+
+ {props.user.name} {props.user.surname} +
+
+ + {props.user.username} + +
+ registrado:  + + ??/??/???? + +
+
+
+ +
+ ); +} + + + diff --git a/frontend/src/OnlineClassroom/index.tsx b/frontend/src/OnlineClassroom/index.tsx index e9b7ab0..fe78d12 100644 --- a/frontend/src/OnlineClassroom/index.tsx +++ b/frontend/src/OnlineClassroom/index.tsx @@ -1,15 +1,9 @@ -import { For, Show, createSignal, onMount } from "solid-js"; +import { Show, createSignal } from "solid-js"; import { Search } from "../certs/Search"; import { Person } from "../types/Person"; import { FilledCard } from "../components/FilledCard"; -import { LinkIcon } from "../icons/LinkIcon"; import { ClassroomUserCreation } from "./ClassroomUserCreation"; -import { ClassroomRegistrationUser } from "../types/ClassroomRegistrationUser"; -import { SpinnerGapIcon } from "../icons/SpinnerGapIcon"; -import { QuestionIcon } from "../icons/QuestionIcon"; -import { JsonResult } from "../types/JsonResult"; -import { XcircleIcon } from "../icons/XCircleIcon"; -import { LoadingStatus, backend, useLoading, wait } from "../utils/functions"; +import { ClassroomVinculation } from "./ClassroomVinculation"; type TabType = "Vinculate" | "Create"; @@ -21,13 +15,13 @@ export function OnlineClassroom() {
- + setPerson((p) => ({...p!, person_classroom_id: classroom_id}))} /> - +

Person has classroom_id

@@ -58,7 +52,7 @@ function ClassroomUser(props: {person: Person, onLink: (classroom_id: number) => />
- +
@@ -67,144 +61,6 @@ function ClassroomUser(props: {person: Person, onLink: (classroom_id: number) => ); } -type Status = "Init" | "Ok" | "Loading" | "Error"; -function ClassroomVinculation(props: {person_surname: string, personId: number, onLink: (classroom_id: number) => void,}) { - const [classroomUsers, setClassroomUsers] = createSignal([]); - const [error, setError] = createSignal(""); - const [status, setStatus] = createSignal("Init"); - - const loadUsers = async() => { - setStatus("Loading"); - const response = await fetch(`${import.meta.env.VITE_BACKEND_URL}/api/classroom/users/${encodeURIComponent(props.person_surname)}`); - const json: JsonResult> = await response.json(); - - if (response.ok) { - setClassroomUsers(json.Ok); - setStatus("Ok"); - } else { - console.error("Error loading users", json); - setError(json.Err.reason); - setStatus("Error"); - } - }; - - onMount(loadUsers); - - return ( -
-

- Vincule un usuario existente: -

- - -
- -
-
- - - -
-
- -
-

No se encontraron usuarios en el aula virtual.

-
-
- - {(u) => ( - - )} - -
- - -
-
- -
-

Error buscando usuarios: {error()}.

-
-
-
- ); -} - - - -function ClassroomSingleUser(props: { - user: ClassroomRegistrationUser, - onLink: (classroom_id: number) => void, - personId: number, -}) { - const {setError, status, setStatus} = useLoading(); - - const linkUser = async() => { - setStatus(LoadingStatus.Loading); - - if (import.meta.env.DEV) await wait(1500); - - backend.put>( - "/api/person/link", - { - person_id: props.personId, - person_classroom_id: parseInt(props.user.user_id, 10), - }, - ) - .then((response) => { - if (response.status === 200) { - setStatus(LoadingStatus.Ok); - props.onLink(parseInt(props.user.user_id, 10)); - } else { - console.error(response.data); - setError(response.data.Err.reason); - setStatus(LoadingStatus.Error); - } - }) - .catch((err) => { - setError(`Error fatal: ${err.message}`); - console.error(err); - setStatus(LoadingStatus.Error); - }); - }; - - return ( -
-
- {props.user.name} {props.user.surname} -
-
- - {props.user.username} - -
- registrado:  - - ??/??/???? - -
-
-
- -
- ); -} - - function ClassroomTabs(props: {active: TabType, setActive: (v: TabType) => void}) { const presetsClasses = () => ((props.active === "Vinculate") ? "font-bold border-c-primary" : "border-c-transparent"); const manualClasses = () => ((props.active === "Create") ? "font-bold border-c-primary" : "border-c-transparent"); diff --git a/frontend/src/components/FilledButton.tsx b/frontend/src/components/FilledButton.tsx index 567ed65..11cf556 100644 --- a/frontend/src/components/FilledButton.tsx +++ b/frontend/src/components/FilledButton.tsx @@ -3,12 +3,14 @@ import { JSX } from "solid-js"; export function FilledButton(props: { children?: Array | JSX.Element, class?: string, - type?: "button" | "input", + type?: "button" | "submit", + disabled?: boolean, }) { return ( diff --git a/frontend/src/components/MaterialInput.tsx b/frontend/src/components/MaterialInput.tsx index 189da97..17ef2cf 100644 --- a/frontend/src/components/MaterialInput.tsx +++ b/frontend/src/components/MaterialInput.tsx @@ -3,19 +3,21 @@ export function MaterialInput(props: { disabled: boolean, value: string, setValue: (v: string) => void, + type?: string, + class?: string }) { let inputElement: HTMLInputElement | undefined; return ( -
+
-
diff --git a/frontend/src/types/Person.ts b/frontend/src/types/Person.ts index 062ca6d..94f647f 100644 --- a/frontend/src/types/Person.ts +++ b/frontend/src/types/Person.ts @@ -5,6 +5,6 @@ export type Person = { person_names: string person_paternal_surname: string person_maternal_surname: string - person_classroom_id: number | undefined + person_classroom_id: number | null }