From 59d89f4a3b3824c3c6f27327f1daf6371e392887 Mon Sep 17 00:00:00 2001
From: Toufic Batache <taffou2a@gmail.com>
Date: Thu, 21 Sep 2023 12:29:30 +0200
Subject: [PATCH] Finalisation

---
 example.env                                   |   3 +
 .../components/BudgetConnexionModal.svelte    | 100 +++++++----------
 src/lib/server/auditors/config.ts             |   1 +
 src/lib/server/config.ts                      |   2 +
 src/routes/+layout.server.ts                  |   4 +-
 src/routes/+layout.svelte                     | 103 ++++++++++++++++++
 src/routes/+page.svelte                       |   5 +-
 src/routes/budget/demande/+server.ts          |  87 +++++++--------
 8 files changed, 193 insertions(+), 112 deletions(-)

diff --git a/example.env b/example.env
index 422403a34..2ca2ab2be 100644
--- a/example.env
+++ b/example.env
@@ -20,6 +20,9 @@ BASE_URL="http://localhost:5173"
 # URL of GitLab pipeline to run when a non-authentication user requests a budget simulation
 # BUDGET_DEMAND_PIPELINE_URL="https://GITLAB.DOMAIN.NAME/api/v4/projects/PROJECT_ID/pipeline?ref=main"
 
+# Token used for calling the budget simulation demand GitLab pipeline
+#BUDGET_DEMAND_PIPELINE_TOKEN="SECRET"
+
 # Secret key used to sign JSON web tokens sent to Budget API
 # BUDGET_JWT_SECRET="SECRET"
 
diff --git a/src/lib/components/BudgetConnexionModal.svelte b/src/lib/components/BudgetConnexionModal.svelte
index d33f41104..c85e16143 100644
--- a/src/lib/components/BudgetConnexionModal.svelte
+++ b/src/lib/components/BudgetConnexionModal.svelte
@@ -12,27 +12,29 @@
   import { browser } from "$app/environment"
   import { goto } from "$app/navigation"
   import { page } from "$app/stores"
-  import type { DisplayMode } from "$lib/displays"
   import {
     trackBudgetPublicSimulation,
     trackBudgetSignInButton,
   } from "$lib/matomo"
-  import type { ParametricReform } from "$lib/reforms"
   import type { CachedSimulation } from "$lib/simulations"
-  import { budgetEditableParametersName } from "$lib/variables"
 
-  export let displayMode: DisplayMode
   export let isOpen = false
 
   let cachedSimulations: CachedSimulation[] = []
-  const parametricReform = getContext(
-    "parametricReform",
-  ) as Writable<ParametricReform>
+  const requestedSimulationEmail = getContext(
+    "requestedSimulationEmail",
+  ) as Writable<string | undefined>
+  let email = ""
+  let isRequestSent = false
 
   $: if (browser && isOpen) {
     fetchCachedSimulations()
   }
 
+  $: if ($requestedSimulationEmail !== undefined) {
+    isRequestSent = true
+  }
+
   async function fetchCachedSimulations() {
     const res = await fetch("/simulations_budget/index", {
       method: "POST",
@@ -50,33 +52,7 @@
   }
 
   async function sendSimulationRequest() {
-    const urlString = "/budget/demande"
-    const res = await fetch(urlString, {
-      body: JSON.stringify({
-        displayMode,
-        parametricReform: Object.entries($parametricReform)
-          .sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
-          .reduce((filtered: ParametricReform, [parameterName, value]) => {
-            if (budgetEditableParametersName.has(parameterName)) {
-              filtered[parameterName] = value
-            }
-            return filtered
-          }, {}),
-      }),
-      headers: {
-        Accept: "application/json",
-        "Content-Type": "application/json; charset=utf-8",
-      },
-      method: "POST",
-    })
-    if (!res.ok) {
-      console.error(
-        `Error ${
-          res.status
-        } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
-      )
-      return
-    }
+    $requestedSimulationEmail = email
   }
 </script>
 
@@ -188,34 +164,40 @@
                     statistique, la simulation sera rendue publique. Vous serez
                     alors informé par e-mail :
                   </p>
-                  <span class="font-bold text-sm py-2 pl-10"
-                    >Votre adresse e-mail :</span
-                  >
-                  <div
-                    class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5"
-                  >
+                  {#if !isRequestSent}
+                    <span class="font-bold text-sm py-2 pl-10"
+                      >Votre adresse e-mail :</span
+                    >
                     <div
-                      class="flex rounded-t-md border-b-2 border-b-black bg-white px-2 grow max-w-lg"
+                      class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5"
                     >
-                      <input
-                        autocomplete="off"
-                        class="w-full px-3 py-2 border-none bg-transparent text-sm text-gray-900 placeholder-gray-400 !ring-transparent focus:outline-none 2xl:text-base"
-                        id="search"
-                        placeholder="e-mail@email.fr"
-                        type="search"
-                      />
+                      <div
+                        class="flex rounded-t-md border-b-2 border-b-black bg-white px-2 grow max-w-lg"
+                      >
+                        <input
+                          autocomplete="off"
+                          class="w-full px-3 py-2 border-none bg-transparent text-sm text-gray-900 placeholder-gray-400 !ring-transparent focus:outline-none 2xl:text-base"
+                          placeholder="e-mail@email.fr"
+                          type="email"
+                          bind:value={email}
+                        />
+                      </div>
+                      <button
+                        class="flex items-center gap-2 py-2 px-5 shadow-lg bg-white hover:bg-gray-100 active:bg-gray-200 rounded-md border-2 border-le-bleu text-le-bleu text-sm font-bold tracking-[0.085em] uppercase"
+                        title="Envoyer votre réforme budgétaire avec cet e-mail"
+                        on:click={sendSimulationRequest}
+                      >
+                        Demander&nbsp;le&nbsp;calcul <iconify-icon
+                          class="ml-2 align-[-0.25rem] text-xl"
+                          icon="ri-send-plane-2-line"
+                        />
+                      </button>
                     </div>
-                    <button
-                      class="flex items-center gap-2 py-2 px-5 shadow-lg bg-white hover:bg-gray-100 active:bg-gray-200 rounded-md border-2 border-le-bleu text-le-bleu text-sm font-bold tracking-[0.085em] uppercase"
-                      title="Envoyer votre réforme budgétaire avec cet e-mail"
-                      type="submit"
-                    >
-                      Demander&nbsp;le&nbsp;calcul <iconify-icon
-                        class="ml-2 align-[-0.25rem] text-xl"
-                        icon="ri-send-plane-2-line"
-                      />
-                    </button>
-                  </div>
+                  {:else}
+                    <div class="w-full p-3 text-center bg-yellow-100 font-bold">
+                      Votre demande de simulation a bien été prise en compte !
+                    </div>
+                  {/if}
                 </section>
               {/if}
 
diff --git a/src/lib/server/auditors/config.ts b/src/lib/server/auditors/config.ts
index 913dca17a..587ff0c3d 100644
--- a/src/lib/server/auditors/config.ts
+++ b/src/lib/server/auditors/config.ts
@@ -89,6 +89,7 @@ export function auditConfig(
     )
   }
   for (const key of [
+    "budgetDemandPipelineToken",
     "budgetJwtSecret",
     "githubPersonalAccessToken",
     "reformName",
diff --git a/src/lib/server/config.ts b/src/lib/server/config.ts
index c23d480b0..e6a7ebc21 100644
--- a/src/lib/server/config.ts
+++ b/src/lib/server/config.ts
@@ -11,6 +11,7 @@ export interface Config {
   baseUrl: string
   budgetApiUrl?: string
   budgetDemandPipelineUrl?: string
+  budgetDemandPipelineToken?: string
   budgetJwtSecret?: string
   childrenKey: string
   familyEntityKey: string
@@ -50,6 +51,7 @@ const [validConfig, error] = validateConfig({
   baseUrl: process.env["BASE_URL"],
   budgetApiUrl: process.env["BUDGET_API_URL"],
   budgetDemandPipelineUrl: process.env["BUDGET_DEMAND_PIPELINE_URL"],
+  budgetDemandPipelineToken: process.env["BUDGET_DEMAND_PIPELINE_TOKEN"],
   budgetJwtSecret: process.env["BUDGET_JWT_SECRET"],
   childrenKey: process.env["CHILDREN_KEY"],
   familyEntityKey: process.env["FAMILY_KEY"],
diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts
index e92d6ed32..ac2dff08c 100644
--- a/src/routes/+layout.server.ts
+++ b/src/routes/+layout.server.ts
@@ -45,7 +45,9 @@ export const load: LayoutServerLoad = async (
     apiWebSocketBaseUrls: config.apiWebSocketBaseUrls,
     authenticationEnabled: config.openIdConnect !== undefined,
     baseUrl: config.baseUrl,
-    canDemandBudgetSimulation: config.budgetDemandPipelineUrl !== undefined,
+    canDemandBudgetSimulation:
+      config.budgetDemandPipelineUrl !== undefined &&
+      config.budgetDemandPipelineToken !== undefined,
     childrenKey: config.childrenKey,
     familyEntityKey: config.familyEntityKey,
     hasGithubPersonalAccessToken:
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index c8506d0a2..d2da7959d 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -162,6 +162,10 @@
   })
   setContext("requestedCalculations", requestedCalculations)
 
+  const requestedSimulationEmail: Writable<string | undefined> =
+    writable(undefined)
+  setContext("requestedSimulationEmail", requestedSimulationEmail)
+
   /*
    * Search
    * */
@@ -253,6 +257,10 @@
     calculateBudget(budgetVariableName /*, budgetCalculationNames */) // Don't await
   }
 
+  $: if ($requestedSimulationEmail !== undefined) {
+    sendBudgetSimulationRequest()
+  }
+
   $: if (
     Object.keys($requestedCalculations.situationIndexByCalculationName).length >
     0
@@ -383,6 +391,101 @@
     }
   }
 
+  async function sendBudgetSimulationRequest() {
+    const budgetVariableName = $displayMode?.parametersVariableName
+    const budgetParametricReform = Object.fromEntries(
+      Object.entries($parametricReform).filter(([parameterName]) =>
+        budgetEditableParametersName.has(parameterName),
+      ),
+    )
+    const simulationBody =
+      budgetVariableName === "irpp_economique"
+        ? {
+            amendement: budgetParametricReform,
+            base: 2024,
+            metadata,
+            output_variables: ["rfr_par_part", "irpp"],
+            quantile_base_variable: ["rfr_par_part"],
+            quantile_compare_variables: ["irpp"],
+            quantile_nb: 10,
+            plf: $billName === undefined ? undefined : 2024,
+          }
+        : [
+            "csg_deductible_retraite",
+            "csg_imposable_retraite",
+            "csg_retraite",
+          ].includes(budgetVariableName)
+        ? {
+            amendement: budgetParametricReform,
+            base: 2024,
+            metadata,
+            output_variables: [
+              "rfr_par_part", // "assiette_csg_abattue",
+              "csg_deductible_retraite",
+              "csg_imposable_retraite",
+            ],
+            quantile_base_variable: ["rfr_par_part"], // ["assiette_csg_abattue"],
+            quantile_compare_variables: [
+              "csg_deductible_retraite",
+              "csg_imposable_retraite",
+            ],
+            quantile_nb: 10,
+            plf: $billName === undefined ? undefined : 2024,
+          }
+        : [
+            "csg_deductible_salaire",
+            "csg_imposable_salaire",
+            "csg_salaire",
+          ].includes(budgetVariableName)
+        ? {
+            amendement: budgetParametricReform,
+            base: 2024,
+            metadata,
+            output_variables: [
+              "rfr_par_part", // "assiette_csg_abattue",
+              "csg_deductible_salaire",
+              "csg_imposable_salaire",
+            ],
+            quantile_base_variable: ["rfr_par_part"], // ["assiette_csg_abattue"],
+            quantile_compare_variables: [
+              "csg_deductible_salaire",
+              "csg_imposable_salaire",
+            ],
+            quantile_nb: 10,
+            plf: $billName === undefined ? undefined : 2024,
+          }
+        : {
+            // Should never occur.
+          }
+    const urlString = "/budget/demande"
+    const res = await fetch(urlString, {
+      body: JSON.stringify(
+        {
+          email: $requestedSimulationEmail,
+          simulation: simulationBody,
+          displayMode: $displayMode,
+        },
+        null,
+        2,
+      ),
+      headers: {
+        Accept: "application/json",
+        "Content-Type": "application/json; charset=utf-8",
+      },
+      method: "POST",
+    })
+    if (!res.ok) {
+      $requestedSimulationEmail = undefined
+      console.error(
+        `Error ${
+          res.status
+        } while sending a simulation request at ${urlString}\n\n${await res.text()}`,
+      )
+      return
+    }
+    $requestedSimulationEmail = undefined
+  }
+
   function calculateSituations(
     situationIndexByCalculationName: RequestedSituationIndexByCalculationName,
   ) {
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 1c1a4276f..ebd94e0fe 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1762,10 +1762,7 @@
       </div>
     </div>
 
-    <BudgetConnexionModal
-      bind:isOpen={isBudgetConnexionModalOpen}
-      {displayMode}
-    />
+    <BudgetConnexionModal bind:isOpen={isBudgetConnexionModalOpen} />
   {/if}
 
   <!--Bouton flottant "simulation publique" -->
diff --git a/src/routes/budget/demande/+server.ts b/src/routes/budget/demande/+server.ts
index cd8921818..875433648 100644
--- a/src/routes/budget/demande/+server.ts
+++ b/src/routes/budget/demande/+server.ts
@@ -1,17 +1,10 @@
 import { auditRequire, cleanAudit, type Audit } from "@auditors/core"
 import { error, json } from "@sveltejs/kit"
-import fs from "fs-extra"
-import path from "path"
 
-import { getParameter, rootParameter } from "$lib/parameters"
-import type { ParametricReform } from "$lib/reforms"
+import type { DisplayMode } from "$lib/displays"
 import config from "$lib/server/config"
-import type { CachedSimulation } from "$lib/simulations"
 
 import type { RequestHandler } from "./$types"
-import { hashObject } from "$lib/hash"
-
-const { simulationsBudgetDir } = config
 
 function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
   if (dataUnknown == null) {
@@ -27,16 +20,27 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
 
   audit.attribute(
     data,
-    "displayMode",
+    "email",
     true,
     errors,
     remainingKeys,
     // TODO
     auditRequire,
   )
+
   audit.attribute(
     data,
-    "parametricReform",
+    "simulation",
+    true,
+    errors,
+    remainingKeys,
+    // TODO
+    auditRequire,
+  )
+
+  audit.attribute(
+    data,
+    "displayMode",
     true,
     errors,
     remainingKeys,
@@ -48,7 +52,7 @@ function auditBody(audit: Audit, dataUnknown: unknown): [unknown, unknown] {
 }
 
 export const POST: RequestHandler = async ({ fetch, request, url }) => {
-  const [body, bodyError] = auditBody(cleanAudit, await request.json())
+  const [body, bodyError] = auditBody(cleanAudit, await request.clone().json())
   if (bodyError !== null) {
     console.error(
       `Error in ${url.pathname} body:\n${JSON.stringify(
@@ -60,45 +64,32 @@ export const POST: RequestHandler = async ({ fetch, request, url }) => {
     throw error(400, `Invalid body: ${JSON.stringify(bodyError, null, 2)}`)
   }
 
-  const modifiedParametersTitles = Object.keys(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  ).map((parameterName) => getParameter(rootParameter, parameterName)?.title)
-
-  const modifiedParametersNames = Object.keys(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  ).map((parameterName) => getParameter(rootParameter, parameterName)?.name)
-
-  const response = await fetch()
-  const bodyJson = JSON.stringify(body, null, 2)
+  const { email, simulation, displayMode } = body as {
+    email: string
+    simulation: any
+    displayMode: DisplayMode
+  }
 
-  const digest = hashObject(
-    (body as { parametricReform: ParametricReform }).parametricReform,
-  )
+  const response = await fetch(config.budgetDemandPipelineUrl!, {
+    body: JSON.stringify({
+      variables: [
+        { key: "EMAIL", value: email },
+        { key: "SIMULATION", value: JSON.stringify(simulation) },
+        { key: "DISPLAY_MODE", value: JSON.stringify(displayMode) },
+      ],
+    }),
+    headers: {
+      "Content-Type": "application/json; charset=utf-8",
+      "PRIVATE-TOKEN": config.budgetDemandPipelineToken!,
+    },
+    method: "POST",
+  })
 
-  const simulationDir = path.join(simulationsBudgetDir, digest.substring(0, 2))
-  const simulationFilePath = path.join(simulationDir, `${digest}.json`)
-  if (!(await fs.pathExists(simulationFilePath))) {
-    await fs.ensureDir(simulationDir)
-    await fs.writeFile(simulationFilePath, bodyJson)
+  if (!response.ok) {
+    const message = "Error while demanding a budget simulation calculation"
+    console.error(message)
+    throw error(500, message)
   }
 
-  const indexDir = path.join(simulationsBudgetDir, "index.json")
-  const indexContents: CachedSimulation[] = (await fs.pathExists(indexDir))
-    ? await fs.readJson(indexDir)
-    : []
-  const contents: CachedSimulation[] = [
-    ...indexContents,
-    {
-      date: new Intl.DateTimeFormat("fr-FR").format(new Date()),
-      hash: digest,
-      parameters: modifiedParametersNames,
-      title: modifiedParametersTitles.join(" | "),
-    } as CachedSimulation,
-  ].filter(
-    (value, index, self) =>
-      index === self.findLastIndex((el) => el.hash === value.hash),
-  )
-  await fs.writeFile(indexDir, JSON.stringify(contents))
-
-  return json({ token: digest })
+  return json({})
 }
-- 
GitLab