From 73846fd86f8897377463872c6107994b1f95f505 Mon Sep 17 00:00:00 2001 From: Toufic Batache <taffou2a@gmail.com> Date: Fri, 1 Sep 2023 17:42:30 +0200 Subject: [PATCH] =?UTF-8?q?Automatiquement=20d=C3=A9tecter=20si=20une=20si?= =?UTF-8?q?mulation=20publique=20est=20disponible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/BudgetConnexionModal.svelte | 131 ++++++++++++------ .../BudgetSimulationSharingModal.svelte | 11 +- src/lib/hash.ts | 17 +++ src/lib/simulations.ts | 1 + src/routes/simulations/+server.ts | 10 +- src/routes/simulations_budget/+server.ts | 16 +-- 6 files changed, 124 insertions(+), 62 deletions(-) create mode 100644 src/lib/hash.ts diff --git a/src/lib/components/BudgetConnexionModal.svelte b/src/lib/components/BudgetConnexionModal.svelte index 5bc8170fd..afb27f2f1 100644 --- a/src/lib/components/BudgetConnexionModal.svelte +++ b/src/lib/components/BudgetConnexionModal.svelte @@ -6,19 +6,43 @@ Transition, TransitionChild, } from "@rgossiaux/svelte-headlessui" + import { getContext } from "svelte" + import type { Writable } from "svelte/types/runtime/store" import { browser } from "$app/environment" import { page } from "$app/stores" + import { hashObject } from "$lib/hash" + import type { ParametricReform } from "$lib/reforms" import type { CachedSimulation } from "$lib/simulations" + import { budgetEditableParametersName } from "$lib/variables" export let isOpen = false let cachedSimulations: CachedSimulation[] = [] + let currentSimulationCache: CachedSimulation | undefined + const parametricReform = getContext( + "parametricReform", + ) as Writable<ParametricReform> - $: if (browser) { + $: if (browser && isOpen) { fetchCachedSimulations() } + $: currentSimulationCache = cachedSimulations.find( + (simulation) => simulation.parametersHash === parametricReformDigest, + ) + + $: parametricReformDigest = hashObject( + Object.entries($parametricReform) + .sort(([keyA], [keyB]) => keyA.localeCompare(keyB)) + .reduce((filtered: ParametricReform, [parameterName, value]) => { + if (budgetEditableParametersName.has(parameterName)) { + filtered[parameterName] = value + } + return filtered + }, {}), + ) + async function fetchCachedSimulations() { const res = await fetch("/simulations_budget/index", { method: "POST", @@ -125,51 +149,78 @@ >.</span > </div> - <div class="bg-gray-100 px-5 pb-10 pt-5 border-t mt-10"> - <h2 class="w-full text-left text-2xl font-bold"> - Demandez le calcul de votre réforme au service LexImpact : - </h2> - <p class="w-full text-base font-normal leading-6 mt-1.5 mb-5"> - Après vérification par nos services, si elle est calculable avec - les données dont nous disposons et répond au secret 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 - > + + {#if currentSimulationCache !== undefined} <div - class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5" + class="flex justify-between items-center bg-le-jaune-light bg-opacity-50 px-5 py-3 mt-10" > - <div - class="flex rounded-t-md border-b-2 border-b-black bg-white px-2 grow max-w-lg" + <h2 class="text-2xl font-bold"> + 🎉 Votre réforme est déjà calculée ! + </h2> + <a + class="flex items-center gap-2 p-2 hover:bg-gray-500 hover:bg-opacity-10 active:bg-gray-500 active:bg-opacity-20 rounded-md underline text-gray-600 text-sm font-bold tracking-[0.085em] uppercase" + data-sveltekit-reload + href="/simulations_budget/{currentSimulationCache.hash}" + title="Voir la simulation" > - <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" + Voir la simulation<iconify-icon + class="align-[-0.25rem] text-xl" + icon="ri-arrow-right-line" /> - </div> + </a> + </div> + {/if} + + <div class="flex flex-col gap-10 bg-gray-100 p-5 border-t mt-10"> + {#if currentSimulationCache === undefined} <div> - <a - 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" - data-sveltekit-reload - href={`/auth/login?redirect=${encodeURIComponent( - $page.url.toString(), - )}`} - title="Envoyer votre réforme budgétaire avec cet e-mail" + <h2 class="w-full text-left text-2xl font-bold"> + Demandez le calcul de votre réforme au service + LexImpact : + </h2> + <p class="w-full text-base font-normal leading-6 mt-1.5 mb-5"> + Après vérification par nos services, si elle est calculable + avec les données dont nous disposons et répond au secret + 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 > - Demander le calcul <iconify-icon - class="ml-2 align-[-0.25rem] text-xl" - icon="ri-send-plane-2-line" - /> - </a> + <div + class="flex md:flex-row flex-col w-full px-0 md:px-10 items-center gap-5" + > + <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" + id="search" + placeholder="e-mail@email.fr" + type="search" + /> + </div> + <div> + <a + 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" + data-sveltekit-reload + href={`/auth/login?redirect=${encodeURIComponent( + $page.url.toString(), + )}`} + title="Envoyer votre réforme budgétaire avec cet e-mail" + > + Demander le calcul <iconify-icon + class="ml-2 align-[-0.25rem] text-xl" + icon="ri-send-plane-2-line" + /> + </a> + </div> + </div> </div> - </div> + {/if} {#if cachedSimulations !== undefined && cachedSimulations.length > 0} - <div class="mt-10 flex flex-col items-center"> + <div class="flex flex-col items-center"> <h2 class="w-full text-left text-xl font-bold"> Consultez la liste des simulations budgétaires déjà disponibles : @@ -196,13 +247,13 @@ {/each} </table> <a - class="mt-2 flex items-center gap-2 py-2 px-5 hover:bg-gray-200 active:bg-gray-300 rounded-md underline text-gray-600 text-sm font-bold tracking-[0.085em] uppercase" + class="mt-2 flex items-center gap-3 py-2 px-5 hover:bg-gray-200 active:bg-gray-300 rounded-md underline text-gray-600 text-sm font-bold tracking-[0.085em] uppercase" data-sveltekit-reload href="/" title="Voir toutes les simulations" > Voir toutes les simulations<iconify-icon - class="ml-2 align-[-0.25rem] text-xl" + class="align-[-0.25rem] text-xl" icon="ri-arrow-right-line" /> </a> diff --git a/src/lib/components/BudgetSimulationSharingModal.svelte b/src/lib/components/BudgetSimulationSharingModal.svelte index c247fa95c..e05e4b099 100644 --- a/src/lib/components/BudgetSimulationSharingModal.svelte +++ b/src/lib/components/BudgetSimulationSharingModal.svelte @@ -11,8 +11,8 @@ import { page } from "$app/stores" import type { BudgetSimulation } from "$lib/budgets" - import type { DisplayMode } from "$lib/displays" import CopyClipboard from "$lib/components/CopyClipboard.svelte" + import type { DisplayMode } from "$lib/displays" import type { ParametricReform } from "$lib/reforms" import { budgetEditableParametersName } from "$lib/variables" @@ -68,15 +68,14 @@ body: JSON.stringify({ budgetSimulation: $budgetSimulation?.result, displayMode, - parametricReform: Object.entries($parametricReform).reduce( - (filtered, [parameterName, value]) => { + 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", diff --git a/src/lib/hash.ts b/src/lib/hash.ts new file mode 100644 index 000000000..a0f845b8e --- /dev/null +++ b/src/lib/hash.ts @@ -0,0 +1,17 @@ +import { XXH64 } from "xxh3-ts" + +// Sometimes, the magnitude of the BigInt value generated by XXH64() +// is not large enough to require 16 characters when represented in base 16. +// As a result, the base 16 representation has leading zeros that are not necessary, +// and JavaScript omits them when converting the BigInt to a string. To fix this, +// we add zeros in the beginning until we reach a length of 16. + +export function hashString(str: string): string { + return XXH64(Buffer.from(str)).toString(16).padStart(16, "0") +} + +export function hashObject(obj: object): string { + return XXH64(Buffer.from(JSON.stringify(obj))) + .toString(16) + .padStart(16, "0") +} diff --git a/src/lib/simulations.ts b/src/lib/simulations.ts index 1722aca0b..e5951ad36 100644 --- a/src/lib/simulations.ts +++ b/src/lib/simulations.ts @@ -2,6 +2,7 @@ export interface CachedSimulation { date: string hash: string parameters: string[] + parametersHash: string title: string } diff --git a/src/routes/simulations/+server.ts b/src/routes/simulations/+server.ts index 7faefcd57..3788808c2 100644 --- a/src/routes/simulations/+server.ts +++ b/src/routes/simulations/+server.ts @@ -8,8 +8,7 @@ import { error, json } from "@sveltejs/kit" import fs from "fs-extra" import path from "path" -import { XXH64 } from "xxh3-ts" - +import { hashString } from "$lib/hash" import config from "$lib/server/config" import type { RequestHandler } from "./$types" @@ -83,12 +82,7 @@ export const POST: RequestHandler = async ({ request, url }) => { } const bodyJson = JSON.stringify(body, null, 2) - // Sometimes, the magnitude of the BigInt value generated by XXH64() - // is not large enough to require 16 characters when represented in base 16. - // As a result, the base 16 representation has leading zeros that are not necessary, - // and JavaScript omits them when converting the BigInt to a string. To fix this, - // we add zeros in the beginning until we reach a length of 16. - const digest = XXH64(Buffer.from(bodyJson)).toString(16).padStart(16, "0") + const digest = hashString(bodyJson) const simulationDir = path.join(simulationsDir, digest.substring(0, 2)) const simulationFilePath = path.join(simulationDir, `${digest}.json`) diff --git a/src/routes/simulations_budget/+server.ts b/src/routes/simulations_budget/+server.ts index 78e675b01..003a1d657 100644 --- a/src/routes/simulations_budget/+server.ts +++ b/src/routes/simulations_budget/+server.ts @@ -2,7 +2,6 @@ import { auditRequire, cleanAudit, type Audit } from "@auditors/core" import { error, json } from "@sveltejs/kit" import fs from "fs-extra" import path from "path" -import { XXH64 } from "xxh3-ts" import { getParameter, rootParameter } from "$lib/parameters" import type { ParametricReform } from "$lib/reforms" @@ -10,6 +9,7 @@ import config from "$lib/server/config" import type { CachedSimulation } from "$lib/simulations" import type { RequestHandler } from "./$types" +import { hashObject, hashString } from "$lib/hash" const { simulationsBudgetDir } = config @@ -81,12 +81,11 @@ export const POST: RequestHandler = async ({ request, url }) => { const bodyJson = JSON.stringify(body, null, 2) - // Sometimes, the magnitude of the BigInt value generated by XXH64() - // is not large enough to require 16 characters when represented in base 16. - // As a result, the base 16 representation has leading zeros that are not necessary, - // and JavaScript omits them when converting the BigInt to a string. To fix this, - // we add zeros in the beginning until we reach a length of 16. - const digest = XXH64(Buffer.from(bodyJson)).toString(16).padStart(16, "0") + const digest = hashString(bodyJson) + + const parametricReformDigest = hashObject( + (body as { parametricReform: ParametricReform }).parametricReform, + ) const simulationDir = path.join(simulationsBudgetDir, digest.substring(0, 2)) const simulationFilePath = path.join(simulationDir, `${digest}.json`) @@ -105,11 +104,12 @@ export const POST: RequestHandler = async ({ request, url }) => { date: new Intl.DateTimeFormat("fr-FR").format(new Date()), hash: digest, parameters: modifiedParametersNames, + parametersHash: parametricReformDigest, title: modifiedParametersTitles.join(" | "), } as CachedSimulation, ].filter( (value, index, self) => - index === self.findIndex((el) => el.hash === value.hash), + index === self.findLastIndex((el) => el.hash === value.hash), ) await fs.writeFile(indexDir, JSON.stringify(contents)) -- GitLab