From c3d6498139df56b6a166851a75bfdc107bf17d27 Mon Sep 17 00:00:00 2001 From: dd060606 Date: Fri, 22 Nov 2024 11:49:55 +0100 Subject: [PATCH] feat: add viewstate cache to optimize requests --- src/api/NotesApi.ts | 28 +++--------- src/api/PlanningApi.ts | 99 +++++++++++++++++------------------------- src/api/Session.ts | 46 ++++++++++++++++++++ tests/Cache.test.ts | 47 ++++++++++++++++++++ 4 files changed, 139 insertions(+), 81 deletions(-) create mode 100644 tests/Cache.test.ts diff --git a/src/api/NotesApi.ts b/src/api/NotesApi.ts index b0b2103..562a2f2 100644 --- a/src/api/NotesApi.ts +++ b/src/api/NotesApi.ts @@ -12,29 +12,13 @@ class NotesApi { public fetchNotes(): Promise { return new Promise(async (resolve, reject) => { try { - // On part depuis la page planning pour pouvoir finalement accéder à la page des notes - const schedulePage = await this.session.sendGET( - "/faces/Planning.xhtml", + // On initialise le ViewState (obligatoire pour avoir une réponse correcteur du backend) + // Ici 1_1 correspond au sous-menu 'Mes notes' dans la sidebar + await this.session.getViewState("1_1"); + const response = await this.session.sendGET( + "faces/LearnerNotationListPage.xhtml", ); - let viewState = getViewState(schedulePage); - if (viewState) { - // Ici 291906 correspond au menu 'Scolarité' dans la sidebar - // Requête utile pour intialiser le ViewState (obligatoire pour effectuer une requête) - await this.session.sendSidebarRequest("291906", viewState); - - // Ici 1_1 correspond au sous-menu 'Mes notes' dans la sidebar - // On récupère le ViewState pour effectuer la prochaine requête - viewState = await this.session.sendSidebarSubmenuRequest( - "1_1", - viewState, - ); - const response = await this.session.sendGET( - "faces/LearnerNotationListPage.xhtml", - ); - resolve(getNotesFromResponse(response)); - } else { - reject(new Error("Viewstate not found")); - } + resolve(getNotesFromResponse(response)); } catch (error) { reject(error); } diff --git a/src/api/PlanningApi.ts b/src/api/PlanningApi.ts index 3423226..580e7bf 100644 --- a/src/api/PlanningApi.ts +++ b/src/api/PlanningApi.ts @@ -12,68 +12,49 @@ class PlanningApi { public fetchPlanning(startDate?: string): Promise { return new Promise(async (resolve, reject) => { try { - const schedulePage = await this.session.sendGET( - "/faces/Planning.xhtml", + // On récupère le ViewState pour effectuer la requête + // Ici 1_4 correspond au sous-menu 'Emploi du temps' dans la sidebar + let viewState = await this.session.getViewState("1_4"); + // On envoie enfin la requête pour obtenir l'emploi du temps + const params = getJSFFormParams( + "j_idt118", + "j_idt118", + viewState, ); - let viewState = getViewState(schedulePage); - if (viewState) { - // Ici 291906 correspond au menu 'Scolarité' dans la sidebar - // Requête utile pour intialiser le ViewState (obligatoire pour effectuer une requête) - await this.session.sendSidebarRequest("291906", viewState); - - // Ici 1_4 correspond au sous-menu 'Emploi du temps' dans la sidebar - // On récupère le ViewState pour effectuer la prochaine requête - viewState = await this.session.sendSidebarSubmenuRequest( - "1_4", - viewState, - ); - - // On envoie enfin la requête pour obtenir l'emploi du temps - const params = getJSFFormParams( - "j_idt118", - "j_idt118", - viewState, - ); - if (startDate) { - params.append("form:j_idt118_start", startDate); - // La date de fin est fixée à une semaine après la date de début - const endDate = startDate + 6 * 24 * 60 * 60 * 1000; - params.append("form:j_idt118_end", endDate); - } else { - const now = new Date(); - // Obtenir le jour actuel (0 = dimanche, 1 = lundi, ..., 6 = samedi) - let day = now.getDay(); - // Calculer la différence pour atteindre lundi (0 = dimanche => 1 jour pour atteindre lundi) - const daysToMonday = day === 0 ? 1 : 1 - day; - // Créer la date de début (lundi 6h00) - const startDate = new Date(now); - startDate.setDate(now.getDate() + daysToMonday); // Passer au lundi - startDate.setHours(6, 0, 0, 0); // Fixer à 6h00 - // Date de fin (6 jours après la date de début) - const endDate = new Date(startDate); - endDate.setDate(startDate.getDate() + 6); // Ajouter 6 jours - // Convertir les dates en timestamp - const startTimestamp = startDate.getTime(); - const endTimestamp = endDate.getTime(); - - params.append( - "form:j_idt118_start", - startTimestamp.toString(), - ); - params.append( - "form:j_idt118_end", - endTimestamp.toString(), - ); - } - - const response = await this.session.sendPOST( - "faces/Planning.xhtml", - params, - ); - resolve(planningResponseToEvents(response)); + if (startDate) { + params.append("form:j_idt118_start", startDate); + // La date de fin est fixée à une semaine après la date de début + const endDate = startDate + 6 * 24 * 60 * 60 * 1000; + params.append("form:j_idt118_end", endDate); } else { - reject(new Error("Viewstate not found")); + const now = new Date(); + // Obtenir le jour actuel (0 = dimanche, 1 = lundi, ..., 6 = samedi) + let day = now.getDay(); + // Calculer la différence pour atteindre lundi (0 = dimanche => 1 jour pour atteindre lundi) + const daysToMonday = day === 0 ? 1 : 1 - day; + // Créer la date de début (lundi 6h00) + const startDate = new Date(now); + startDate.setDate(now.getDate() + daysToMonday); // Passer au lundi + startDate.setHours(6, 0, 0, 0); // Fixer à 6h00 + // Date de fin (6 jours après la date de début) + const endDate = new Date(startDate); + endDate.setDate(startDate.getDate() + 6); // Ajouter 6 jours + // Convertir les dates en timestamp + const startTimestamp = startDate.getTime(); + const endTimestamp = endDate.getTime(); + + params.append( + "form:j_idt118_start", + startTimestamp.toString(), + ); + params.append("form:j_idt118_end", endTimestamp.toString()); } + + const response = await this.session.sendPOST( + "faces/Planning.xhtml", + params, + ); + resolve(planningResponseToEvents(response)); } catch (error) { reject(error); } diff --git a/src/api/Session.ts b/src/api/Session.ts index c022ac6..9943878 100644 --- a/src/api/Session.ts +++ b/src/api/Session.ts @@ -6,6 +6,11 @@ import NotesApi from "./NotesApi"; class Session { private client: AxiosInstance; + //Permet de sauvegarder le ViewState et le subMenuId pour les réutiliser dans les prochaines requêtes (optimisation) + //Cela a pour but d'éviter d'effectuer 3 requêtes lorsque l'on refait la même demande (emploi du temps de la semaine suivante par exemple) + private viewStateCache: string = ""; + private subMenuIdCache: string = ""; + constructor(baseURL: string, token: string) { this.client = axios.create({ baseURL, @@ -85,6 +90,47 @@ class Session { }); } + // Récupération du ViewState pour effectuer les différentes requêtes + public getViewState(subMenuId: string): Promise { + return new Promise(async (resolve, reject) => { + //On optimise l'accès au ViewState + if (this.viewStateCache && this.subMenuIdCache === subMenuId) { + return resolve(this.viewStateCache); + } + try { + const schedulePage = await this.sendGET( + "/faces/Planning.xhtml", + ); + let viewState = getViewState(schedulePage); + if (viewState) { + // Ici 291906 correspond au menu 'Scolarité' dans la sidebar + // Requête utile pour intialiser le ViewState (obligatoire pour effectuer une requête) + await this.sendSidebarRequest("291906", viewState); + + // On récupère le ViewState pour effectuer la prochaine requête + viewState = await this.sendSidebarSubmenuRequest( + subMenuId, + viewState, + ); + if (viewState) { + this.viewStateCache = viewState; + this.subMenuIdCache = subMenuId; + return resolve(viewState); + } + } + return reject(new Error("Viewstate not found")); + } catch (error) { + reject(new Error("Viewstate not found")); + } + }); + } + + //Permet de vider le cache du ViewState et du subMenuId (si besoin) + public clearViewStateCache(): void { + this.viewStateCache = ""; + this.subMenuIdCache = ""; + } + public sendGET(url: string): Promise { return this.client.get(url).then((response) => response.data); } diff --git a/tests/Cache.test.ts b/tests/Cache.test.ts new file mode 100644 index 0000000..0472b62 --- /dev/null +++ b/tests/Cache.test.ts @@ -0,0 +1,47 @@ +import { login } from "../src/api/Session"; +describe("CacheTests", () => { + it("should test for function performance", async () => { + const username = process.env.TEST_USERNAME; + const password = process.env.TEST_PASSWORD; + if (!username || !password) { + throw new Error( + "TEST_USERNAME or TEST_PASSWORD is not set in environment variables.", + ); + } + + const session = await login(username, password); + + //Première fois sans le cache on récupère l'emploi du temps + let start = performance.now(); + let planning = await session.getPlanningApi().fetchPlanning(); + let end = performance.now(); + let duration = end - start; + console.log(`fetchPlanning (sans cache): ${duration.toFixed(2)} ms`); + + //Deuxième fois avec le cache on récupère l'emploi du temps + start = performance.now(); + planning = await session.getPlanningApi().fetchPlanning(); + end = performance.now(); + duration = end - start; + console.log(`fetchPlanning (avec cache): ${duration.toFixed(2)} ms`); + + //Première fois sans le cache on récupère les notes + start = performance.now(); + let notes = await session.getNotesApi().fetchNotes(); + end = performance.now(); + duration = end - start; + console.log(`fetchNotes (sans cache): ${duration.toFixed(2)} ms`); + + //Deuxième fois avec le cache on récupère les notes + start = performance.now(); + notes = await session.getNotesApi().fetchNotes(); + end = performance.now(); + duration = end - start; + console.log(`fetchNotes (avec cache): ${duration.toFixed(2)} ms`); + + // console.log("Le planning", JSON.stringify(planning)); + // console.log("Les notes ", JSON.stringify(notes)); + expect(notes).toBeInstanceOf(Array); + expect(planning).toBeInstanceOf(Array); + }); +});