diff --git a/packages/api/src/business/_logs-update.ts b/packages/api/src/business/_logs-update.ts deleted file mode 100644 index bf49ca6296d65707cc8dfc964ac63406862c1f0c..0000000000000000000000000000000000000000 --- a/packages/api/src/business/_logs-update.ts +++ /dev/null @@ -1,179 +0,0 @@ -import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty } from 'camino-common/src/typescript-tools' -import { Index, IEntrepriseEtablissement, IEntreprise } from '../types' -export const dailySummaryMarker = 'tâches exécutées:' as const -export const logsUpdate = ({ - heritageWithUnknownEtapes, - etapesCompletesErreurs, - tdeErreurs, - demarcheDefinitionsErreurs, - fondamentaleIdUpdated, - consentementUpdated, - misesEnConcurrenceUpdated, - titresEtapesStatusUpdated, - titresEtapesOrdreUpdated, - titresEtapesHeritagePropsUpdated, - titresEtapesHeritageContenuUpdated, - titresDemarchesStatutUpdated, - titresDemarchesPublicUpdated, - titresDemarchesOrdreUpdated, - titresStatutIdUpdated, - titresPublicUpdated, - titresDemarchesDatesUpdated, - titresEtapesAdministrationsLocalesUpdated, - titresPropsEtapesIdsUpdated, - titresActivitesCreated, - titresActivitesRelanceSent, - titresActivitesStatutIdsUpdated, - titresActivitesPropsUpdated, - titresUpdatedIndex, - entreprisesUpdated, - etablissementsUpdated, - etablissementsDeleted, -}: { - heritageWithUnknownEtapes?: unknown[] - etapesCompletesErreurs?: { surTitreValide: string[]; autre: string[]; etapesRecentes: string[] } - tdeErreurs?: number - demarcheDefinitionsErreurs?: number - fondamentaleIdUpdated?: number - consentementUpdated?: unknown[] - misesEnConcurrenceUpdated?: unknown[] - titresEtapesStatusUpdated?: string[] - titresEtapesOrdreUpdated?: string[] - titresEtapesHeritagePropsUpdated?: string[] - titresEtapesHeritageContenuUpdated?: string[] - titresDemarchesStatutUpdated?: string[] - titresDemarchesPublicUpdated?: string[] - titresDemarchesOrdreUpdated?: string[] - titresStatutIdUpdated?: string[] - titresPublicUpdated?: string[] - titresDemarchesDatesUpdated?: string[] - titresEtapesAdministrationsLocalesUpdated?: string[] - titresPropsEtapesIdsUpdated?: string[] - titresActivitesCreated?: string[] - titresActivitesRelanceSent?: string[] - titresActivitesStatutIdsUpdated?: string[] - titresActivitesPropsUpdated?: string[] - titresUpdatedIndex?: Index<string> - entreprisesUpdated?: IEntreprise[] - etablissementsUpdated?: IEntrepriseEtablissement[] - etablissementsDeleted?: string[] -}): void => { - console.info() - console.info('-') - console.info(dailySummaryMarker) - - if (isNotNullNorUndefined(etapesCompletesErreurs)) { - if (etapesCompletesErreurs.etapesRecentes.length > 0) { - console.warn(`${etapesCompletesErreurs.etapesRecentes.length} étapes récentes ne sont pas complètes`) - } - if (etapesCompletesErreurs.surTitreValide.length > 0) { - console.warn(`${etapesCompletesErreurs.surTitreValide.length} étapes sur des titres valides ne sont pas complètes`) - } - if (etapesCompletesErreurs.autre.length > 0) { - console.info(`${etapesCompletesErreurs.autre.length} étapes sur des titres non valides ne sont pas complètes`) - } - } - - if (isNotNullNorUndefinedNorEmpty(heritageWithUnknownEtapes)) { - console.error(`${heritageWithUnknownEtapes.length} heritage de contenu qui pointe sur des étapes non existantes`) - } - if (isNotNullNorUndefined(tdeErreurs) && tdeErreurs > 0) { - console.warn(`${tdeErreurs} erreurs TDE`) - } - - if (isNotNullNorUndefined(demarcheDefinitionsErreurs) && demarcheDefinitionsErreurs > 0) { - console.warn(`${demarcheDefinitionsErreurs} erreurs sur les définitions des démarches`) - } - - if (isNotNullNorUndefined(fondamentaleIdUpdated) && fondamentaleIdUpdated > 0) { - console.info(`mise à jour: ${fondamentaleIdUpdated} étapes(s) (fondamentale)`) - } - - if (isNotNullNorUndefinedNorEmpty(consentementUpdated)) { - console.info(`mise à jour: ${consentementUpdated.length} étapes(s) (consentement)`) - } - - if (isNotNullNorUndefinedNorEmpty(misesEnConcurrenceUpdated)) { - console.info(`mise à jour: ${misesEnConcurrenceUpdated.length} étapes(s) (concurrence)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresEtapesStatusUpdated)) { - console.info(`mise à jour: ${titresEtapesStatusUpdated.length} étape(s) (statut)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresEtapesOrdreUpdated)) { - console.info(`mise à jour: ${titresEtapesOrdreUpdated.length} étape(s) (ordre)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresEtapesHeritagePropsUpdated)) { - console.info(`mise à jour: ${titresEtapesHeritagePropsUpdated.length} étape(s) (héritage des propriétés)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresEtapesHeritageContenuUpdated)) { - console.info(`mise à jour: ${titresEtapesHeritageContenuUpdated.length} étape(s) (héritage du contenu)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresDemarchesStatutUpdated)) { - console.info(`mise à jour: ${titresDemarchesStatutUpdated.length} démarche(s) (statut)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresDemarchesPublicUpdated)) { - console.info(`mise à jour: ${titresDemarchesPublicUpdated.length} démarche(s) (publicité)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresDemarchesOrdreUpdated)) { - console.info(`mise à jour: ${titresDemarchesOrdreUpdated.length} démarche(s) (ordre)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresStatutIdUpdated)) { - console.info(`mise à jour: ${titresStatutIdUpdated.length} titre(s) (statuts)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresPublicUpdated)) { - console.info(`mise à jour: ${titresPublicUpdated.length} titre(s) (publicité)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresDemarchesDatesUpdated)) { - console.info(`mise à jour: ${titresDemarchesDatesUpdated.length} titre(s) (phases mises à jour)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresEtapesAdministrationsLocalesUpdated)) { - console.info(`mise à jour: ${titresEtapesAdministrationsLocalesUpdated.length} administration(s) locale(s) modifiée(s) dans des étapes`) - } - - if (isNotNullNorUndefinedNorEmpty(titresPropsEtapesIdsUpdated)) { - console.info(`mise à jour: ${titresPropsEtapesIdsUpdated.length} titres(s) (propriétés-étapes)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresActivitesCreated)) { - console.info(`mise à jour: ${titresActivitesCreated.length} activité(s) créée(s)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresActivitesRelanceSent)) { - console.info(`mise à jour: ${titresActivitesRelanceSent.length} activité(s) relancée(s)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresActivitesStatutIdsUpdated)) { - console.info(`mise à jour: ${titresActivitesStatutIdsUpdated.length} activité(s) fermée(s)`) - } - - if (isNotNullNorUndefinedNorEmpty(titresActivitesPropsUpdated)) { - console.info(`mise à jour: ${titresActivitesPropsUpdated.length} activité(s) (propriété suppression)`) - } - - if (isNotNullNorUndefined(titresUpdatedIndex) && Object.keys(titresUpdatedIndex).length) { - console.info(`mise à jour: ${Object.keys(titresUpdatedIndex).length} titre(s) (slugs)`) - } - - if (isNotNullNorUndefinedNorEmpty(entreprisesUpdated)) { - console.info(`mise à jour: ${entreprisesUpdated.length} adresse(s) d'entreprise(s)`) - } - - if (isNotNullNorUndefinedNorEmpty(etablissementsUpdated)) { - console.info(`mise à jour: ${etablissementsUpdated.length} établissement(s) d'entreprise(s)`) - } - - if (isNotNullNorUndefinedNorEmpty(etablissementsDeleted)) { - console.info(`suppression: ${etablissementsDeleted.length} établissement(s) d'entreprise(s)`) - } -} diff --git a/packages/api/src/business/daily.ts b/packages/api/src/business/daily.ts index 6ca341289051c905fc375c0b1192ca1e14dc8110..5c523ecde25c8696513bd6095cf5ac1b866f8929 100644 --- a/packages/api/src/business/daily.ts +++ b/packages/api/src/business/daily.ts @@ -12,8 +12,7 @@ import { titresStatutIdsUpdate } from './processes/titres-statut-ids-update' import { titresEtapesHeritagePropsUpdate } from './processes/titres-etapes-heritage-props-update' import { checkEtapeInContenuHeritage, titresEtapesHeritageContenuUpdate } from './processes/titres-etapes-heritage-contenu-update' import { titresActivitesPropsUpdate } from './processes/titres-activites-props-update' -import { titresSlugsUpdate } from './processes/titres-slugs-update' -import { logsUpdate } from './_logs-update' +import { TitreSlugUpdate, titresSlugsUpdate } from './processes/titres-slugs-update' import { userSuper } from '../database/user-super' import { titresActivitesRelanceSend } from './processes/titres-activites-relance-send' import type { Pool } from 'pg' @@ -25,8 +24,208 @@ import { allEtapesMiseEnConcurrenceUpdate } from './processes/titres-etapes-mise import { etapesFondamentaleIdUpdateForAll } from './processes/titres-etapes-fondamentale-id-update' import { allEtapesConsentementUpdate } from './processes/titres-etapes-consentement' import { etapesCompletesCheck } from '../tools/etapes/etapes-complete-check' +import { isNotNullNorUndefined, isNotNullNorUndefinedNorEmpty, NonEmptyArray } from 'camino-common/src/typescript-tools' +import { config } from '../config' +import { createReadStream, createWriteStream, readFileSync, writeFileSync } from 'node:fs' +import { createInterface } from 'node:readline' +import { fetch } from 'undici' +import { mailjetSend } from '../tools/api-mailjet/emails' +import { EtapeId } from 'camino-common/src/etape' -export const daily = async (pool: Pool): Promise<void> => { +interface Daily { + heritageWithUnknownEtapes: unknown[] + etapesCompletesErreurs: { surTitreValide: string[]; autre: string[]; etapesRecentes: string[] } + tdeErreurs: number + demarcheDefinitionsErreurs: number + fondamentaleIdUpdated: number + consentementUpdated: unknown[] + misesEnConcurrenceUpdated: unknown[] + titresEtapesStatusUpdated: string[] + titresEtapesOrdreUpdated: string[] + titresEtapesHeritagePropsUpdated: string[] + titresEtapesHeritageContenuUpdated: { + updated: EtapeId[] + errors: EtapeId[] + } + titresDemarchesStatutUpdated: string[] + titresDemarchesPublicUpdated: string[] + titresDemarchesOrdreUpdated: string[] + titresStatutIdUpdated: string[] + titresPublicUpdated: string[] + titresDemarchesDatesUpdated: string[] + titresEtapesAdministrationsLocalesUpdated: string[] + titresPropsEtapesIdsUpdated: string[] + titresActivitesCreated: string[] + titresActivitesRelanceSent: string[] + titresActivitesStatutIdsUpdated: string[] + titresActivitesPropsUpdated: string[] + titresUpdatedIndex: TitreSlugUpdate[] +} + +// Mis à jour le 2025-02-26 +const NOMBRE_ERREURS_ETAPES = 10870 as const + +type DailyResult = { changes: false } | { changes: true; logs: NonEmptyArray<string> } +const dailyResult = (daily: Daily | null): DailyResult => { + if (daily === null) { + return { changes: true, logs: ['Une erreur est survenue durant le daily'] } + } + const logs: string[] = [] + if (isNotNullNorUndefined(daily.etapesCompletesErreurs)) { + if (daily.etapesCompletesErreurs.etapesRecentes.length + daily.etapesCompletesErreurs.surTitreValide.length + daily.etapesCompletesErreurs.surTitreValide.length > NOMBRE_ERREURS_ETAPES) { + logs.push("ATTENTION IL Y'A DE NOUVELLES ÉTAPES INCOMPLÈTES") + if (daily.etapesCompletesErreurs.etapesRecentes.length > 0) { + logs.push(`${daily.etapesCompletesErreurs.etapesRecentes.length} étapes récentes ne sont pas complètes`) + } + if (daily.etapesCompletesErreurs.surTitreValide.length > 0) { + logs.push(`${daily.etapesCompletesErreurs.surTitreValide.length} étapes sur des titres valides ne sont pas complètes`) + } + if (daily.etapesCompletesErreurs.surTitreValide.length > 0) { + logs.push(`${daily.etapesCompletesErreurs.autre.length} étapes sur des titres non valides ne sont pas complètes`) + } + } + } + + if (isNotNullNorUndefinedNorEmpty(daily.heritageWithUnknownEtapes)) { + logs.push(`${daily.heritageWithUnknownEtapes.length} heritage de contenu qui pointe sur des étapes non existantes`) + } + if (isNotNullNorUndefined(daily.tdeErreurs) && daily.tdeErreurs > 0) { + logs.push(`${daily.tdeErreurs} erreurs TDE`) + } + + if (isNotNullNorUndefined(daily.demarcheDefinitionsErreurs) && daily.demarcheDefinitionsErreurs > 0) { + logs.push(`${daily.demarcheDefinitionsErreurs} erreurs sur les définitions des démarches`) + } + + if (isNotNullNorUndefined(daily.fondamentaleIdUpdated) && daily.fondamentaleIdUpdated > 0) { + logs.push(`mise à jour: ${daily.fondamentaleIdUpdated} étapes(s) (fondamentale)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.consentementUpdated)) { + logs.push(`mise à jour: ${daily.consentementUpdated.length} étapes(s) (consentement)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.misesEnConcurrenceUpdated)) { + logs.push(`mise à jour: ${daily.misesEnConcurrenceUpdated.length} étapes(s) (concurrence)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesStatusUpdated)) { + logs.push(`mise à jour: ${daily.titresEtapesStatusUpdated.length} étape(s) (statut)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesOrdreUpdated)) { + logs.push(`mise à jour: ${daily.titresEtapesOrdreUpdated.length} étape(s) (ordre)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesHeritagePropsUpdated)) { + logs.push(`mise à jour: ${daily.titresEtapesHeritagePropsUpdated.length} étape(s) (héritage des propriétés)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesHeritageContenuUpdated.updated)) { + logs.push(`mise à jour: ${daily.titresEtapesHeritageContenuUpdated.updated.length} étape(s) (héritage du contenu)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesHeritageContenuUpdated.errors)) { + logs.push(`erreurs: ${daily.titresEtapesHeritageContenuUpdated.errors.length} étape(s) (héritage du contenu) en erreur`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresDemarchesStatutUpdated)) { + logs.push(`mise à jour: ${daily.titresDemarchesStatutUpdated.length} démarche(s) (statut)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresDemarchesPublicUpdated)) { + logs.push(`mise à jour: ${daily.titresDemarchesPublicUpdated.length} démarche(s) (publicité)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresDemarchesOrdreUpdated)) { + logs.push(`mise à jour: ${daily.titresDemarchesOrdreUpdated.length} démarche(s) (ordre)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresStatutIdUpdated)) { + logs.push(`mise à jour: ${daily.titresStatutIdUpdated.length} titre(s) (statuts)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresPublicUpdated)) { + logs.push(`mise à jour: ${daily.titresPublicUpdated.length} titre(s) (publicité)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresDemarchesDatesUpdated)) { + logs.push(`mise à jour: ${daily.titresDemarchesDatesUpdated.length} titre(s) (phases mises à jour)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresEtapesAdministrationsLocalesUpdated)) { + logs.push(`mise à jour: ${daily.titresEtapesAdministrationsLocalesUpdated.length} administration(s) locale(s) modifiée(s) dans des étapes`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresPropsEtapesIdsUpdated)) { + logs.push(`mise à jour: ${daily.titresPropsEtapesIdsUpdated.length} titres(s) (propriétés-étapes)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresActivitesCreated)) { + logs.push(`mise à jour: ${daily.titresActivitesCreated.length} activité(s) créée(s)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresActivitesRelanceSent)) { + logs.push(`mise à jour: ${daily.titresActivitesRelanceSent.length} activité(s) relancée(s)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresActivitesStatutIdsUpdated)) { + logs.push(`mise à jour: ${daily.titresActivitesStatutIdsUpdated.length} activité(s) fermée(s)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresActivitesPropsUpdated)) { + logs.push(`mise à jour: ${daily.titresActivitesPropsUpdated.length} activité(s) (propriété suppression)`) + } + + if (isNotNullNorUndefinedNorEmpty(daily.titresUpdatedIndex)) { + logs.push(`mise à jour: ${daily.titresUpdatedIndex.length} titre(s) (slugs)`) + } + + if (isNotNullNorUndefinedNorEmpty(logs)) { + return { changes: true, logs } + } + return { changes: false } +} + +export const daily = async (pool: Pool, logFile: string): Promise<void> => { + const output = createWriteStream(logFile, { flush: true, autoClose: true }) + + // Réinitialise les logs qui seront envoyés par email + writeFileSync(logFile, '') + let resume: DailyResult = { changes: true, logs: ["Le daily ne s'est pas lancé"] } + try { + resume = await rawDaily(pool) + } catch (e) { + console.error('Erreur durant le daily', e) + } + + if (isNotNullNorUndefined(config().CAMINO_STAGE)) { + await new Promise<void>(resolve => { + output.end(() => resolve()) + }) + const emailBody = `Résultats de ${config().ENV} \n${resume.changes ? resume.logs.join('\n') : 'Pas de changement\n'}\n${readFileSync(logFile).toString()}` + try { + const tchapHook = config().TCHAP_HOOK + if (isNotNullNorUndefined(tchapHook)) { + const markdown = await transformIntoMarkDown(resume, logFile) + await tchapSend(markdown, tchapHook) + } + } catch (e: unknown) { + let errorMessage = "Une erreur s'est produite pendant l'envoi du daily sur tchap" + if (e instanceof Error) { + errorMessage += `-> ${e.message}` + } + console.error(errorMessage) + } + + // TODO 2024-12-05 enlever le daily par email si il s'est bien envoyé via tchap + await mailjetSend([config().ADMIN_EMAIL], { + Subject: `[Camino][${config().ENV}] Résultats du daily`, + TextPart: emailBody, + }) + } +} +const rawDaily = async (pool: Pool): Promise<DailyResult> => { try { console.info() console.info('- - -') @@ -62,7 +261,7 @@ export const daily = async (pool: Pool): Promise<void> => { const tdeErreurs = await titreTypeDemarcheTypeEtapeTypeCheck() const etapesCompletesErreurs = await etapesCompletesCheck(pool) - logsUpdate({ + return dailyResult({ heritageWithUnknownEtapes, etapesCompletesErreurs: etapesCompletesErreurs, tdeErreurs, @@ -94,3 +293,44 @@ export const daily = async (pool: Pool): Promise<void> => { throw e } } + +const transformIntoMarkDown = async (resume: DailyResult, logFile: string): Promise<string> => { + const fileStream = createReadStream(logFile) + + const readLine = createInterface(fileStream) + let detail = '' + const summary = resume.changes ? resume.logs.map(line => `\n* ${line}`).join('') : '' + + for await (const line of readLine) { + detail += `\n${line}` + } + return `### Résultat du daily de ${config().ENV} +${ + summary !== '' + ? `<details> + <summary>Résumé du daily</summary> +${summary} +</details>` + : '**Pas de changement**' +} +<details> + <summary>Détail du daily</summary> + +\`\`\`bash +${detail} +\`\`\` + +</details>` + fileStream.close() +} + +const tchapSend = async (markdown: string, url: string): Promise<void> => { + await fetch(url, { + method: 'POST', + body: JSON.stringify({ message: markdown, message_format: 'markdown' }), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + }) +} diff --git a/packages/api/src/business/processes/titres-etapes-heritage-contenu-update.ts b/packages/api/src/business/processes/titres-etapes-heritage-contenu-update.ts index cd5b05c9c99063282498854023e73ac2983fe1e0..24dca5acc8b8786e579710492916f7f9b65f66b7 100644 --- a/packages/api/src/business/processes/titres-etapes-heritage-contenu-update.ts +++ b/packages/api/src/business/processes/titres-etapes-heritage-contenu-update.ts @@ -55,7 +55,7 @@ export const checkEtapeInContenuHeritage = (pool: Pool): Effect.Effect<ErrorInCo ) ) } -export const titresEtapesHeritageContenuUpdate = async (pool: Pool, user: UserNotNull, demarcheId?: DemarcheId): Promise<string[]> => { +export const titresEtapesHeritageContenuUpdate = async (pool: Pool, user: UserNotNull, demarcheId?: DemarcheId): Promise<{ updated: EtapeId[]; errors: EtapeId[] }> => { console.info() console.info('héritage des contenus des étapes…') @@ -65,7 +65,8 @@ export const titresEtapesHeritageContenuUpdate = async (pool: Pool, user: UserNo // l'objet heritageContenu reçu ne contient pas d'id d'étape // l'étape est donc toujours mise à jour - const titresEtapesIdsUpdated = [] as string[] + const titresEtapesIdsUpdated: EtapeId[] = [] + const titresEtapesIdsErrors: EtapeId[] = [] for (const titreDemarche of Object.values(titresDemarches)) { if (isNotNullNorUndefinedNorEmpty(titreDemarche.etapes)) { @@ -86,7 +87,7 @@ export const titresEtapesHeritageContenuUpdate = async (pool: Pool, user: UserNo // TODO 2025-02-24 : à décommenter et à lancer en prod, puis à supprimer // prettier-ignore // await titreEtapeUpdate(etapePerdantLesSections.id, { contenu: null, heritageContenu: null, }, user, titreDemarche.titreId) - titresEtapesIdsUpdated.push(etapePerdantLesSections.id) + titresEtapesIdsErrors.push(etapePerdantLesSections.id) } } // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions @@ -122,5 +123,5 @@ export const titresEtapesHeritageContenuUpdate = async (pool: Pool, user: UserNo } } - return titresEtapesIdsUpdated + return { updated: titresEtapesIdsUpdated, errors: titresEtapesIdsErrors } } diff --git a/packages/api/src/business/processes/titres-slugs-update.test.ts b/packages/api/src/business/processes/titres-slugs-update.test.ts index 5969e2181882fc7b3278f66c48832338eaaa750e..7fc778fa0d5f0f09db52fdf806f0b15f20696364 100644 --- a/packages/api/src/business/processes/titres-slugs-update.test.ts +++ b/packages/api/src/business/processes/titres-slugs-update.test.ts @@ -5,7 +5,6 @@ import { titreSlugAndRelationsUpdate } from '../utils/titre-slug-and-relations-u import { titresGet } from '../../database/queries/titres' import { vi, describe, expect, test } from 'vitest' import { titreSlugValidator } from 'camino-common/src/validators/titres' -import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' vi.mock('../utils/titre-slug-and-relations-update', () => ({ __esModule: true, titreSlugAndRelationsUpdate: vi.fn(), @@ -37,9 +36,15 @@ describe("mise à jour du slug d'un titre", () => { }) const titresUpdatedIndex = await titresSlugsUpdate() - const titreSlug = isNotNullNorUndefined(titresUpdatedIndex) && Object.keys(titresUpdatedIndex)[0] - expect(titreSlug).toEqual(slug) + expect(titresUpdatedIndex).toMatchInlineSnapshot(` + [ + { + "newSlug": "slug-new", + "oldSlug": "slug-old", + }, + ] + `) expect(titreSlugAndRelationsUpdate).toHaveBeenCalled() }) diff --git a/packages/api/src/business/processes/titres-slugs-update.ts b/packages/api/src/business/processes/titres-slugs-update.ts index 1b32c27798ad177cc62207ec7c8fdd95f7017ad3..f1c59154ed6f97f87ccb89289eb132c02d0d822b 100644 --- a/packages/api/src/business/processes/titres-slugs-update.ts +++ b/packages/api/src/business/processes/titres-slugs-update.ts @@ -4,9 +4,15 @@ import { titresGet } from '../../database/queries/titres' import { userSuper } from '../../database/user-super' import { titreSlugAndRelationsUpdate } from '../utils/titre-slug-and-relations-update' +import { TitreSlug } from 'camino-common/src/validators/titres' +import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' +export interface TitreSlugUpdate { + newSlug: TitreSlug + oldSlug: TitreSlug | undefined +} // met à jour les slugs de titre -const titreSlugsUpdate = async (titre: ITitre) => { +const titreSlugsUpdate = async (titre: ITitre): Promise<TitreSlugUpdate | null> => { const titreOldSlug = titre.slug try { @@ -16,7 +22,7 @@ const titreSlugsUpdate = async (titre: ITitre) => { console.info('titre : slug (mise à jour) ->', slug) - return { [slug]: titreOldSlug } + return { newSlug: slug, oldSlug: titreOldSlug } } catch (e) { console.error(`erreur: titreSlugsUpdate ${titreOldSlug}`, e) @@ -24,7 +30,7 @@ const titreSlugsUpdate = async (titre: ITitre) => { } } -export const titresSlugsUpdate = async (titresIds?: string[]): Promise<Record<string, string>> => { +export const titresSlugsUpdate = async (titresIds?: string[]): Promise<TitreSlugUpdate[]> => { console.info() console.info('slugs de titres, démarches, étapes et activités') @@ -43,13 +49,12 @@ export const titresSlugsUpdate = async (titresIds?: string[]): Promise<Record<st userSuper ) - const titresUpdatedIndex: Record<string, string> = {} + const titresUpdatedIndex: TitreSlugUpdate[] = [] for (const titre of titres) { const titreUpdatedIndex = await titreSlugsUpdate(titre) - - if (titreUpdatedIndex) { - Object.assign(titresUpdatedIndex, titreUpdatedIndex) + if (isNotNullNorUndefined(titreUpdatedIndex)) { + titresUpdatedIndex.push(titreUpdatedIndex) } } diff --git a/packages/api/src/business/titre-demarche-update.ts b/packages/api/src/business/titre-demarche-update.ts index 44a09371880d5f3e9fa9f92b38af7278fc551768..d7fc2489a0853cb7649443940fd0f28fd56c058a 100644 --- a/packages/api/src/business/titre-demarche-update.ts +++ b/packages/api/src/business/titre-demarche-update.ts @@ -8,7 +8,6 @@ import { titresDemarchesDatesUpdate } from './processes/titres-phases-update' import { titresDemarchesOrdreUpdate } from './processes/titres-demarches-ordre-update' import { titresPublicUpdate } from './processes/titres-public-update' import { titresSlugsUpdate } from './processes/titres-slugs-update' -import { logsUpdate } from './_logs-update' import { titresActivitesPropsUpdate } from './processes/titres-activites-props-update' import { userSuper } from '../database/user-super' import type { Pool } from 'pg' @@ -27,35 +26,21 @@ export const titreDemarcheUpdateTask = async (pool: Pool, titreDemarcheId: Demar throw new Error(`warning: le titre ${titreId} n'existe pas`) } - let titresDemarchesPublicUpdated - - const titresDemarchesOrdreUpdated = await titresDemarchesOrdreUpdate([titreId]) - const titresDemarchesDatesUpdated = await titresDemarchesDatesUpdate(pool, [titreId]) + await titresDemarchesOrdreUpdate([titreId]) + await titresDemarchesDatesUpdate(pool, [titreId]) // si c'est une création ou modification // pas une suppression if (titreDemarcheId) { - titresDemarchesPublicUpdated = await titresDemarchesPublicUpdate([titreId]) + await titresDemarchesPublicUpdate([titreId]) } - const titresStatutIdUpdated = await titresStatutIdsUpdate([titreId]) - const titresPublicUpdated = await titresPublicUpdate(pool, [titreId]) - const titresPropsEtapesIdsUpdated = await titresPropsEtapesIdsUpdate([titreId]) - const titresActivitesCreated = await titresActivitesUpdate(pool, [titreId]) - const titresActivitesPropsUpdated = await titresActivitesPropsUpdate([titreId]) - - const titresUpdatedIndex = await titresSlugsUpdate([titreId]) + await titresStatutIdsUpdate([titreId]) + await titresPublicUpdate(pool, [titreId]) + await titresPropsEtapesIdsUpdate([titreId]) + await titresActivitesUpdate(pool, [titreId]) + await titresActivitesPropsUpdate([titreId]) - logsUpdate({ - titresDemarchesPublicUpdated, - titresDemarchesOrdreUpdated, - titresStatutIdUpdated, - titresPublicUpdated, - titresDemarchesDatesUpdated, - titresPropsEtapesIdsUpdated, - titresActivitesCreated, - titresActivitesPropsUpdated, - titresUpdatedIndex, - }) + await titresSlugsUpdate([titreId]) } catch (e) { console.error(`erreur: titreDemarcheUpdate ${titreId}`) console.error(e) diff --git a/packages/api/src/business/titre-etape-update.ts b/packages/api/src/business/titre-etape-update.ts index e413a4b1641f731dee3bf70ad6d7e8f70aeafeb5..a669b992c08826f0477a541823dd1064d9c9c013 100644 --- a/packages/api/src/business/titre-etape-update.ts +++ b/packages/api/src/business/titre-etape-update.ts @@ -16,7 +16,6 @@ import { titresEtapesAdministrationsLocalesUpdate } from './processes/titres-eta import { titresPropsEtapesIdsUpdate } from './processes/titres-props-etapes-ids-update' import { titresSlugsUpdate } from './processes/titres-slugs-update' import { titresPublicUpdate } from './processes/titres-public-update' -import { logsUpdate } from './_logs-update' import { titresActivitesPropsUpdate } from './processes/titres-activites-props-update' import { userSuper } from '../database/user-super' import type { UserNotNull } from 'camino-common/src/roles' @@ -49,50 +48,31 @@ export const titreEtapeUpdateTask = async (pool: Pool, titreEtapeId: EtapeId | n await callAndExit(etapeConsentementUpdate(pool, titreEtapeId)) } - const titresEtapesOrdreUpdated = await titresEtapesOrdreUpdate(pool, user, titreDemarcheId) + await titresEtapesOrdreUpdate(pool, user, titreDemarcheId) await callAndExit(etapesFondamentaleIdUpdate(pool, titreDemarcheId)) - const titresEtapesHeritagePropsUpdated = await titresEtapesHeritagePropsUpdate(user, [titreDemarcheId]) - const titresEtapesHeritageContenuUpdated = await titresEtapesHeritageContenuUpdate(pool, user, titreDemarcheId) + await titresEtapesHeritagePropsUpdate(user, [titreDemarcheId]) + await titresEtapesHeritageContenuUpdate(pool, user, titreDemarcheId) const titreId = titreDemarche.titreId - const titresDemarchesStatutUpdated = await titresDemarchesStatutIdUpdate(pool, titreId) - const titresDemarchesOrdreUpdated = await titresDemarchesOrdreUpdate([titreId]) - const titresDemarchesDatesUpdated = await titresDemarchesDatesUpdate(pool, [titreId]) - const titresDemarchesPublicUpdated = await titresDemarchesPublicUpdate([titreId]) - const titresStatutIdUpdated = await titresStatutIdsUpdate([titreId]) - const titresPublicUpdated = await titresPublicUpdate(pool, [titreId]) + await titresDemarchesStatutIdUpdate(pool, titreId) + await titresDemarchesOrdreUpdate([titreId]) + await titresDemarchesDatesUpdate(pool, [titreId]) + await titresDemarchesPublicUpdate([titreId]) + await titresStatutIdsUpdate([titreId]) + await titresPublicUpdate(pool, [titreId]) // si l'étape est supprimée, pas de mise à jour if (titreEtapeId) { await titresEtapesAreasUpdate(pool, [titreEtapeId]) } - const titresEtapesAdministrationsLocalesUpdated = await titresEtapesAdministrationsLocalesUpdate(titreEtapeId ? [titreEtapeId] : undefined) + await titresEtapesAdministrationsLocalesUpdate(titreEtapeId ? [titreEtapeId] : undefined) - const titresPropsEtapesIdsUpdated = await titresPropsEtapesIdsUpdate([titreId]) - - const titresActivitesCreated = await titresActivitesUpdate(pool, [titreId]) - const titresActivitesPropsUpdated = await titresActivitesPropsUpdate([titreId]) - - const titresUpdatedIndex = await titresSlugsUpdate([titreId]) - - logsUpdate({ - titresEtapesOrdreUpdated, - titresEtapesHeritagePropsUpdated, - titresEtapesHeritageContenuUpdated, - titresDemarchesStatutUpdated, - titresDemarchesPublicUpdated, - titresDemarchesOrdreUpdated, - titresStatutIdUpdated, - titresPublicUpdated, - titresDemarchesDatesUpdated, - titresEtapesAdministrationsLocalesUpdated: titresEtapesAdministrationsLocalesUpdated.map(({ titreEtapeId }) => titreEtapeId), - titresPropsEtapesIdsUpdated, - titresActivitesCreated, - titresActivitesPropsUpdated, - titresUpdatedIndex, - }) + await titresPropsEtapesIdsUpdate([titreId]) + await titresActivitesUpdate(pool, [titreId]) + await titresActivitesPropsUpdate([titreId]) + await titresSlugsUpdate([titreId]) } catch (e) { console.error(`erreur: titreEtapeUpdate ${titreEtapeId}`) console.error(e) diff --git a/packages/api/src/business/titre-update.ts b/packages/api/src/business/titre-update.ts index cb2a1add39754eb830f031a5ebd9d0879e4bd3bd..51a707b4caf5fb791def4942b0381fbb080f21f8 100644 --- a/packages/api/src/business/titre-update.ts +++ b/packages/api/src/business/titre-update.ts @@ -1,7 +1,6 @@ import { titresActivitesUpdate } from './processes/titres-activites-update' import { titresPublicUpdate } from './processes/titres-public-update' import { titresSlugsUpdate } from './processes/titres-slugs-update' -import { logsUpdate } from './_logs-update' import { TitreId } from 'camino-common/src/validators/titres' import { Pool } from 'pg' @@ -11,16 +10,9 @@ export const titreUpdateTask = async (pool: Pool, titreId: TitreId): Promise<voi console.info('- - -') console.info(`mise à jour d'un titre : ${titreId}`) - const titresPublicUpdated = await titresPublicUpdate(pool, [titreId]) - - const titresActivitesCreated = await titresActivitesUpdate(pool, [titreId]) - const titresUpdatedIndex = await titresSlugsUpdate([titreId]) - - logsUpdate({ - titresPublicUpdated, - titresActivitesCreated, - titresUpdatedIndex, - }) + await titresPublicUpdate(pool, [titreId]) + await titresActivitesUpdate(pool, [titreId]) + await titresSlugsUpdate([titreId]) } catch (e) { console.error(`erreur: titreUpdate ${titreId}`) console.error(e) diff --git a/packages/api/src/database/init.ts b/packages/api/src/database/init.ts index 44b5e8407064306af4a3c734493d9e08ae0e3a35..7a65fd75067786b0d27393267971c0be22942bc1 100644 --- a/packages/api/src/database/init.ts +++ b/packages/api/src/database/init.ts @@ -1,13 +1,13 @@ import { knex } from '../knex' import { daily } from '../business/daily' import type { Pool } from 'pg' -import { config } from '../config/index' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' +import { config } from '../config' export const databaseInit = async (pool: Pool): Promise<void> => { await knex.migrate.latest() if (isNotNullNorUndefined(config().CAMINO_STAGE)) { // pas de await pour ne pas bloquer le démarrage de l’appli - daily(pool) // eslint-disable-line @typescript-eslint/no-floating-promises + daily(pool, '/tmp/unused') // eslint-disable-line @typescript-eslint/no-floating-promises } } diff --git a/packages/api/src/scripts/daily.ts b/packages/api/src/scripts/daily.ts index ab1e8259429b2cb172397ec75f530785ba2d8518..117f54a1c22575a622aea994bea30f6e8009f34d 100644 --- a/packages/api/src/scripts/daily.ts +++ b/packages/api/src/scripts/daily.ts @@ -1,21 +1,17 @@ import '../init' import { daily } from '../business/daily' import { consoleOverride } from '../config/logger' -import { mailjetSend } from '../tools/api-mailjet/emails' -import { readFileSync, writeFileSync, createWriteStream, createReadStream } from 'node:fs' +import { writeFileSync, createWriteStream } from 'node:fs' import * as Console from 'console' import pg from 'pg' import { config } from '../config/index' import { isNotNullNorUndefined } from 'camino-common/src/typescript-tools' import { setGlobalDispatcher, EnvHttpProxyAgent } from 'undici' -import { createInterface } from 'node:readline' -import { dailySummaryMarker } from '../business/_logs-update' -import { fetch } from 'undici' const envHttpProxyAgent = new EnvHttpProxyAgent() setGlobalDispatcher(envHttpProxyAgent) -const logFile = '/tmp/cron.log' +const logFile = '/tmp/cron.log' as const const output = createWriteStream(logFile, { flush: true, autoClose: true }) // eslint-disable-next-line const oldConsole = console.log @@ -34,92 +30,17 @@ const pool = new pg.Pool({ database: config().PGDATABASE, }) -const transformIntoMarkDown = async (): Promise<string> => { - const fileStream = createReadStream(logFile) - - const readLine = createInterface(fileStream) - let detail = '' - let summary = '' - - let summaryBegin = false - for await (const line of readLine) { - if (line.includes(dailySummaryMarker)) { - summaryBegin = true - } else { - if (summaryBegin) { - summary += `\n* ${line}` - } else { - detail += `\n${line}` - } - } - } - return `### Résultat du daily de ${config().ENV} -${ - summary !== '' - ? `<details> - <summary>Résumé du daily</summary> -${summary} -</details>` - : '**Pas de changement**' -} -<details> - <summary>Détail du daily</summary> - -\`\`\`bash -${detail} -\`\`\` - -</details>` -} - -const tchapSend = async (markdown: string, url: string): Promise<void> => { - await fetch(url, { - method: 'POST', - body: JSON.stringify({ message: markdown, message_format: 'markdown' }), - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - }) -} - const tasks = async () => { console.info('Tâches quotidiennes : démarrage') // Réinitialise les logs qui seront envoyés par email writeFileSync(logFile, '') try { - await daily(pool) + await daily(pool, logFile) } catch (e) { console.error('Erreur durant le daily', e) } - - if (isNotNullNorUndefined(config().CAMINO_STAGE)) { - await new Promise<void>(resolve => { - output.end(() => resolve()) - }) - // eslint-disable-next-line - console.log = oldConsole - const emailBody = `Résultats de ${config().ENV} \n${readFileSync(logFile).toString()}` - try { - const tchapHook = config().TCHAP_HOOK - if (isNotNullNorUndefined(tchapHook)) { - const markdown = await transformIntoMarkDown() - await tchapSend(markdown, tchapHook) - } - } catch (e: unknown) { - let errorMessage = "Une erreur s'est produite pendant l'envoi du daily sur tchap" - if (e instanceof Error) { - errorMessage += `-> ${e.message}` - } - console.error(errorMessage) - } - - // TODO 2024-12-05 enlever le daily par email si il s'est bien envoyé via tchap - await mailjetSend([config().ADMIN_EMAIL], { - Subject: `[Camino][${config().ENV}] Résultats du daily`, - TextPart: emailBody, - }) - } + // eslint-disable-next-line no-console + console.log = oldConsole console.info('Tâches quotidiennes : terminé') }