From 7496c3108f69ab7535261d4182711736ce7c4d24 Mon Sep 17 00:00:00 2001 From: vmaubert <v.maubert@code-troopers.com> Date: Wed, 22 Sep 2021 16:23:18 +0200 Subject: [PATCH] =?UTF-8?q?feat(logs):=20ajoute=20les=20logs=20sur=20les?= =?UTF-8?q?=20diff=C3=A9rentes=20effectu=C3=A9es=20sur=20les=20titres=20(#?= =?UTF-8?q?798)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 152 +++++++++++++++++- package.json | 1 + src/api/graphql/resolvers/titre-demande.ts | 2 +- src/api/graphql/resolvers/titres-etapes.ts | 8 +- src/api/graphql/schemas/logs.graphql | 23 +++ src/api/graphql/schemas/points.graphql | 2 + src/api/graphql/schemas/titres-etapes.graphql | 4 + .../titres-etapes-heritage-contenu-update.ts | 12 +- .../titres-etapes-heritage-props-update.ts | 2 +- .../processes/titres-etapes-ordre-update.ts | 6 +- .../utils/titre-slug-and-relations-update.ts | 11 +- src/database/models/logs.ts | 44 +++++ src/database/models/titres-etapes.ts | 8 + src/database/queries/logs.ts | 110 +++++++++++++ src/database/queries/permissions/logs.ts | 28 ++++ .../queries/permissions/titres-etapes.ts | 6 + src/database/queries/titres-etapes.ts | 57 +++++-- src/knex/migrations/20210915144021_logs.ts | 16 ++ src/types.ts | 12 +- tests/titres-demarches.test.ts | 18 ++- tests/titres-etapes-modifier.test.ts | 18 ++- tsconfig.json | 2 +- 22 files changed, 497 insertions(+), 45 deletions(-) create mode 100644 src/api/graphql/schemas/logs.graphql create mode 100644 src/database/models/logs.ts create mode 100644 src/database/queries/logs.ts create mode 100644 src/database/queries/permissions/logs.ts create mode 100644 src/knex/migrations/20210915144021_logs.ts diff --git a/package-lock.json b/package-lock.json index a70dc393a..7b47346a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "requires": true, "packages": { "": { - "version": "0.24.97", + "version": "0.25.0", "license": "AGPL-3.0-or-later", "dependencies": { "@graphql-tools/graphql-file-loader": "^6.2.7", @@ -59,6 +59,7 @@ "graphql-type-json": "^0.3.2", "graphql-upload": "^12.0.0", "html-to-text": "^8.0.0", + "jsondiffpatch": "^0.4.1", "jsonwebtoken": "^8.5.1", "knex": "0.21.19", "knex-db-manager": "^0.7.0", @@ -5846,6 +5847,11 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "node_modules/diff-sequences": { "version": "26.6.2", "license": "MIT", @@ -10920,6 +10926,85 @@ "node": ">=6" } }, + "node_modules/jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "dependencies": { + "chalk": "^2.3.0", + "diff-match-patch": "^1.0.0" + }, + "bin": { + "jsondiffpatch": "bin/jsondiffpatch" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/jsondiffpatch/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/jsondiffpatch/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "node_modules/jsondiffpatch/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/jsondiffpatch/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "engines": { + "node": ">=4" + } + }, + "node_modules/jsondiffpatch/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/jsonfile": { "version": "4.0.0", "license": "MIT", @@ -22485,6 +22570,11 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, + "diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==" + }, "diff-sequences": { "version": "26.6.2" }, @@ -25930,6 +26020,66 @@ "minimist": "^1.2.5" } }, + "jsondiffpatch": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.4.1.tgz", + "integrity": "sha512-t0etAxTUk1w5MYdNOkZBZ8rvYYN5iL+2dHCCx/DpkFm/bW28M6y5nUS83D4XdZiHy35Fpaw6LBb+F88fHZnVCw==", + "requires": { + "chalk": "^2.3.0", + "diff-match-patch": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "jsonfile": { "version": "4.0.0", "requires": { diff --git a/package.json b/package.json index 8e1713564..12e882d71 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,7 @@ "graphql-type-json": "^0.3.2", "graphql-upload": "^12.0.0", "html-to-text": "^8.0.0", + "jsondiffpatch": "^0.4.1", "jsonwebtoken": "^8.5.1", "knex": "0.21.19", "knex-db-manager": "^0.7.0", diff --git a/src/api/graphql/resolvers/titre-demande.ts b/src/api/graphql/resolvers/titre-demande.ts index 569b078d3..35c52443a 100644 --- a/src/api/graphql/resolvers/titre-demande.ts +++ b/src/api/graphql/resolvers/titre-demande.ts @@ -119,7 +119,7 @@ const titreDemandeCreer = async ( titulaires: [{ id: titreDemande.entrepriseId }] } as ITitreEtape - titreEtape = await titreEtapeUpsert(titreEtape) + titreEtape = await titreEtapeUpsert(titreEtape, user) await titreEtapeUpdateTask(titreEtape.id, titreEtape.titreDemarcheId) diff --git a/src/api/graphql/resolvers/titres-etapes.ts b/src/api/graphql/resolvers/titres-etapes.ts index 3c7c75914..ba21c902a 100644 --- a/src/api/graphql/resolvers/titres-etapes.ts +++ b/src/api/graphql/resolvers/titres-etapes.ts @@ -305,7 +305,7 @@ const etapeCreer = async ( ) etape.contenu = contenu - const etapeUpdated = await titreEtapeUpsert(etape) + const etapeUpdated = await titreEtapeUpsert(etape, user!) await contenuElementFilesCreate(newFiles, 'demarches', etapeUpdated.id) @@ -438,7 +438,7 @@ const etapeModifier = async ( ) etape.contenu = contenu - const etapeUpdated = await titreEtapeUpsert(etape) + const etapeUpdated = await titreEtapeUpsert(etape, user!) await contenuElementFilesCreate(newFiles, 'demarches', etapeUpdated.id) @@ -515,7 +515,7 @@ const etapeDeposer = async ( const statutIdAndDate = statutIdAndDateGet(titreEtape, user!, true) - await titreEtapeUpdate(titreEtape.id, statutIdAndDate) + await titreEtapeUpdate(titreEtape.id, statutIdAndDate, user!) const etapeUpdated = await titreEtapeGet( titreEtape.id, { @@ -595,7 +595,7 @@ const etapeSupprimer = async ( throw new Error(rulesErrors.join(', ')) } - await titreEtapeDelete(id) + await titreEtapeDelete(id, user!) await fichiersRepertoireDelete(id, 'demarches') diff --git a/src/api/graphql/schemas/logs.graphql b/src/api/graphql/schemas/logs.graphql new file mode 100644 index 000000000..ac56e6b06 --- /dev/null +++ b/src/api/graphql/schemas/logs.graphql @@ -0,0 +1,23 @@ +# import * from 'scalars.graphql' +# import * from 'utilisateurs.graphql' + +enum Operation { + create + update + delete +} + +type Log { + """ + Id unique + """ + id: ID! + + utilisateur: Utilisateur + + date: String + + operation: Operation + + differences: Json +} diff --git a/src/api/graphql/schemas/points.graphql b/src/api/graphql/schemas/points.graphql index be9ceca22..7c1ba4e08 100644 --- a/src/api/graphql/schemas/points.graphql +++ b/src/api/graphql/schemas/points.graphql @@ -78,6 +78,7 @@ type PointReference { } input InputPoint { + id: ID groupe: Int! contour: Int! point: Int! @@ -90,6 +91,7 @@ input InputPoint { } input InputPointReference { + id: ID geoSystemeId: ID! coordonnees: InputCoordonnees! opposable: Boolean diff --git a/src/api/graphql/schemas/titres-etapes.graphql b/src/api/graphql/schemas/titres-etapes.graphql index 28a846c25..6f5f55664 100644 --- a/src/api/graphql/schemas/titres-etapes.graphql +++ b/src/api/graphql/schemas/titres-etapes.graphql @@ -7,6 +7,7 @@ # import * from 'territoires.graphql' # import * from 'documents.graphql' # import * from 'titres-demarches.graphql' +# import * from 'logs.graphql' "Étape d'une démarche effectuée sur un titre minier" type Etape { @@ -82,6 +83,9 @@ type Etape { "Justificatifs d'entreprises relatifs à l'étape" justificatifs: [Document] + "Logs de l’étape" + logs: [Log] + incertitudes: Incertitudes heritageProps: HeritageProps diff --git a/src/business/processes/titres-etapes-heritage-contenu-update.ts b/src/business/processes/titres-etapes-heritage-contenu-update.ts index 6fbbe3ebf..4c6e47fc3 100644 --- a/src/business/processes/titres-etapes-heritage-contenu-update.ts +++ b/src/business/processes/titres-etapes-heritage-contenu-update.ts @@ -56,10 +56,14 @@ const titresEtapesHeritageContenuUpdate = async ( if (hasChanged) { queue.add(async () => { - await titreEtapeUpdate(titreEtape.id, { - contenu, - heritageContenu - }) + await titreEtapeUpdate( + titreEtape.id, + { + contenu, + heritageContenu + }, + userSuper + ) const log = { type: 'titre / démarche / étape : héritage du contenu (mise à jour) ->', diff --git a/src/business/processes/titres-etapes-heritage-props-update.ts b/src/business/processes/titres-etapes-heritage-props-update.ts index fa9cacb5f..89ff2ed8c 100644 --- a/src/business/processes/titres-etapes-heritage-props-update.ts +++ b/src/business/processes/titres-etapes-heritage-props-update.ts @@ -50,7 +50,7 @@ const titresEtapesHeritagePropsUpdate = async ( if (hasChanged) { queue.add(async () => { - await titreEtapeUpsert(newTitreEtape) + await titreEtapeUpsert(newTitreEtape, userSuper) const log = { type: 'titre / démarche / étape : héritage des propriétés (mise à jour) ->', diff --git a/src/business/processes/titres-etapes-ordre-update.ts b/src/business/processes/titres-etapes-ordre-update.ts index 3e8da9e55..9d0b1f5c7 100644 --- a/src/business/processes/titres-etapes-ordre-update.ts +++ b/src/business/processes/titres-etapes-ordre-update.ts @@ -36,7 +36,11 @@ const titresEtapesOrdreUpdate = async (titresDemarchesIds?: string[]) => { ).forEach((titreEtape: ITitreEtape, index: number) => { if (titreEtape.ordre !== index + 1) { queue.add(async () => { - await titreEtapeUpdate(titreEtape.id, { ordre: index + 1 }) + await titreEtapeUpdate( + titreEtape.id, + { ordre: index + 1 }, + userSuper + ) const log = { type: 'titre / démarche / étape : ordre (mise à jour) ->', diff --git a/src/business/utils/titre-slug-and-relations-update.ts b/src/business/utils/titre-slug-and-relations-update.ts index 037e67cb3..060981a00 100644 --- a/src/business/utils/titre-slug-and-relations-update.ts +++ b/src/business/utils/titre-slug-and-relations-update.ts @@ -8,7 +8,8 @@ import { ITitreEtape, ITitrePoint, ITitrePointReference, - ITitreTravaux + ITitreTravaux, + IUtilisateur } from '../../types' import titreDemarcheOrTravauxSortAsc from './titre-elements-sort-asc' @@ -108,7 +109,11 @@ const titreActiviteSlugFind = (titreActivite: ITitreActivite, titre: ITitre) => interface ITitreRelation { name: string slugFind: (...args: any[]) => string - update: (id: string, element: { slug: string }) => Promise<any> + update: ( + id: string, + element: { slug: string }, + user: IUtilisateur + ) => Promise<any> relations?: ITitreRelation[] } @@ -167,7 +172,7 @@ const relationsSlugsUpdate = async ( for (const element of parent[relation.name]) { const slug = relation.slugFind(element, parent) if (slug !== element.slug) { - await relation.update(element.id, { slug }) + await relation.update(element.id, { slug }, userSuper) hasChanged = true } if (relation.relations) { diff --git a/src/database/models/logs.ts b/src/database/models/logs.ts new file mode 100644 index 000000000..9d2cedb05 --- /dev/null +++ b/src/database/models/logs.ts @@ -0,0 +1,44 @@ +import { Model } from 'objection' + +import { ILog } from '../../types' +import { idGenerate } from './_format/id-create' +import { join } from 'path' + +interface Logs extends ILog {} + +class Logs extends Model { + public static tableName = 'logs' + + public static jsonSchema = { + type: 'object', + + properties: { + id: { type: 'string' }, + utilisateurId: { type: 'string' }, + date: { type: 'date' }, + elementId: { type: 'string' }, + operation: { enum: ['create', 'update', 'delete'] }, + differences: { type: 'json' } + } + } + + public static relationMappings = { + utilisateur: { + relation: Model.BelongsToOneRelation, + modelClass: join(__dirname, 'utilisateurs'), + join: { + from: 'logs.utilisateurId', + to: 'utilisateurs.id' + } + } + } + + async $beforeInsert(queryContext: any) { + await super.$beforeInsert(queryContext) + + this.id = idGenerate() + this.date = new Date() + } +} + +export default Logs diff --git a/src/database/models/titres-etapes.ts b/src/database/models/titres-etapes.ts index 598c57b07..ed777d976 100644 --- a/src/database/models/titres-etapes.ts +++ b/src/database/models/titres-etapes.ts @@ -180,6 +180,14 @@ class TitresEtapes extends Model { }, to: 'forets.id' } + }, + logs: { + relation: Model.HasManyRelation, + modelClass: join(__dirname, 'logs'), + join: { + from: 'titresEtapes.id', + to: 'logs.elementId' + } } } diff --git a/src/database/queries/logs.ts b/src/database/queries/logs.ts new file mode 100644 index 000000000..73b0bb0c9 --- /dev/null +++ b/src/database/queries/logs.ts @@ -0,0 +1,110 @@ +import Logs from '../models/logs' +import { create } from 'jsondiffpatch' +import { + Model, + PartialModelGraph, + RelationExpression, + UpsertGraphOptions +} from 'objection' + +const diffPatcher = create({ + // on filtre certaines proprietés qu’on ne souhaite pas voir apparaitre dans les logs + propertyFilter: (name: string) => !['slug', 'ordre'].includes(name) +}) + +export const deleteLogCreate = async (id: string, userId: string) => { + await Logs.query().insert({ + elementId: id, + operation: 'delete', + utilisateurId: userId + }) +} + +export const createLogCreate = async (id: string, userId: string) => { + await Logs.query().insert({ + elementId: id, + operation: 'create', + utilisateurId: userId + }) +} + +export const patchLogCreate = async <T extends Model>( + id: string, + partialEntity: Partial<T>, + model: typeof Model, + userId: string +) => { + const oldValue = await model.query().findById(id) + + const oldPartialValue = ( + Object.keys(partialEntity) as Array<keyof Model> + ).reduce((result, key) => { + result[key] = oldValue[key] + + return result + }, {} as any) + + const result = await model.query().patchAndFetchById(id, { + ...partialEntity, + id + }) + + const differences = diffPatcher.diff(oldPartialValue, partialEntity) + + if (differences) { + await Logs.query().insert({ + elementId: id, + utilisateurId: userId, + operation: 'update', + differences + }) + } + + return result +} + +export const upsertLogCreate = async <T extends Model>( + id: string, + entity: PartialModelGraph<T>, + model: typeof Model, + options: UpsertGraphOptions, + relations: RelationExpression<T>, + userId: string +): Promise<T> => { + const oldValue = id + ? await model + .query() + .findById(id) + .withGraphFetched(relations) + .returning('*') + : undefined + + const newValue = await model + .query() + .upsertGraph(entity, options) + .withGraphFetched(relations) + .returning('*') + + let differences: any + let operation: 'create' | 'update' = 'create' + + if (oldValue) { + differences = diffPatcher.diff(oldValue, newValue) + + // si il n’y a pas de différences, alors on ne log plus cette modification + if (!differences || !Object.keys(differences).length) { + return newValue as T + } + operation = 'update' + } + + await Logs.query().insert({ + elementId: (newValue as any).id, + utilisateurId: userId, + date: new Date(), + operation, + differences + }) + + return newValue as T +} diff --git a/src/database/queries/permissions/logs.ts b/src/database/queries/permissions/logs.ts new file mode 100644 index 000000000..287fe112d --- /dev/null +++ b/src/database/queries/permissions/logs.ts @@ -0,0 +1,28 @@ +import { QueryBuilder } from 'objection' + +import { IUtilisateur } from '../../../types' + +import { permissionCheck } from '../../../tools/permission' + +import Logs from '../../models/logs' +import { utilisateursQueryModify } from './utilisateurs' +import Utilisateurs from '../../models/utilisateurs' + +export const logsQueryModify = ( + q: QueryBuilder<Logs, Logs | Logs[]>, + user: IUtilisateur | null +) => { + q.select('logs.*') + + // Les logs sont uniquement visibles par les super + if (!user || !permissionCheck(user.permissionId, ['super'])) { + q.where(false) + } + + q.modifyGraph('utilisateur', b => { + utilisateursQueryModify( + b as QueryBuilder<Utilisateurs, Utilisateurs | Utilisateurs[]>, + user + ) + }) +} diff --git a/src/database/queries/permissions/titres-etapes.ts b/src/database/queries/permissions/titres-etapes.ts index 48109aa83..a75f44ba9 100644 --- a/src/database/queries/permissions/titres-etapes.ts +++ b/src/database/queries/permissions/titres-etapes.ts @@ -20,6 +20,8 @@ import { import { entreprisesQueryModify, entreprisesTitresQuery } from './entreprises' import { titresDemarchesQueryModify } from './titres-demarches' import TitresDemarches from '../../models/titres-demarches' +import Logs from '../../models/logs' +import { logsQueryModify } from './logs' const titreEtapeModificationQueryBuild = (user: IUtilisateur | null) => { if (permissionCheck(user?.permissionId, ['super'])) { @@ -199,6 +201,10 @@ const titresEtapesQueryModify = ( ).select('titresAmodiataires.operateur') }) + q.modifyGraph('logs', b => { + logsQueryModify(b as QueryBuilder<Logs, Logs | Logs[]>, user) + }) + return q } diff --git a/src/database/queries/titres-etapes.ts b/src/database/queries/titres-etapes.ts index 8363f7b53..665c2f33b 100644 --- a/src/database/queries/titres-etapes.ts +++ b/src/database/queries/titres-etapes.ts @@ -19,6 +19,12 @@ import TitresEtapesJustificatifs from '../models/titres-etapes-justificatifs' import TitresAdministrationsLocales from '../models/titres-administrations-locales' import TitresForets from '../models/titres-forets' import { titresEtapesQueryModify } from './permissions/titres-etapes' +import { + createLogCreate, + deleteLogCreate, + patchLogCreate, + upsertLogCreate +} from './logs' const titresEtapesQueryBuild = ( { fields }: { fields?: IFields }, @@ -88,25 +94,48 @@ const titresEtapesGet = async ( return q } -const titreEtapeCreate = async (titreEtape: ITitreEtape) => - TitresEtapes.query() +const titreEtapeCreate = async ( + titreEtape: ITitreEtape, + user: IUtilisateur +) => { + const newValue = await TitresEtapes.query() .insertAndFetch(titreEtape) .withGraphFetched(options.titresEtapes.graph) -const titreEtapeUpdate = async (id: string, titreEtape: Partial<ITitreEtape>) => - TitresEtapes.query().patchAndFetchById(id, { ...titreEtape, id }) + await createLogCreate(titreEtape.id, user.id) -const titreEtapeDelete = async (id: string, trx?: Transaction) => - TitresEtapes.query(trx) - .deleteById(id) - .withGraphFetched(options.titresEtapes.graph) - .returning('*') + return newValue +} -const titreEtapeUpsert = async (titreEtape: ITitreEtape, trx?: Transaction) => - TitresEtapes.query(trx) - .upsertGraph(titreEtape, options.titresEtapes.update) - .withGraphFetched(options.titresEtapes.graph) - .returning('*') +const titreEtapeUpdate = async ( + id: string, + titreEtape: Partial<ITitreEtape>, + user: IUtilisateur +) => { + return patchLogCreate<TitresEtapes>(id, titreEtape, TitresEtapes, user.id) +} + +const titreEtapeDelete = async ( + id: string, + user: IUtilisateur, + trx?: Transaction +) => { + const result = await TitresEtapes.query(trx).delete().where('id', id) + + await deleteLogCreate(id, user.id) + + return result +} + +const titreEtapeUpsert = async (titreEtape: ITitreEtape, user: IUtilisateur) => + upsertLogCreate<TitresEtapes>( + titreEtape.id, + titreEtape, + TitresEtapes, + options.titresEtapes.update, + options.titresEtapes.graph, + user.id + ) const titresEtapesCommunesGet = async () => TitresCommunes.query() diff --git a/src/knex/migrations/20210915144021_logs.ts b/src/knex/migrations/20210915144021_logs.ts new file mode 100644 index 000000000..5529f5701 --- /dev/null +++ b/src/knex/migrations/20210915144021_logs.ts @@ -0,0 +1,16 @@ +import Knex from 'knex' + +export const up = async (knex: Knex): Promise<void> => { + await knex.schema.createTable('logs', table => { + table.string('id').primary() + table.string('utilisateurId').index().notNullable() + table.dateTime('date').notNullable() + table.string('elementId').notNullable() + table.enum('operation', ['create', 'update', 'delete']).notNullable() + table.jsonb('differences').nullable() + }) +} + +export const down = async (knex: Knex): Promise<void> => { + await knex.schema.dropTable('logs') +} diff --git a/src/types.ts b/src/types.ts index 3933222f0..709e2e58a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1023,6 +1023,15 @@ interface ITitreDemande { references?: ITitreReference[] } +interface ILog { + id: string + utilisateurId: string + date: Date + elementId: string + operation: 'create' | 'update' | 'delete' + differences: any +} + export { Index, IFields, @@ -1143,5 +1152,6 @@ export { ICache, ICacheId, IActiviteTypePays, - ITitreDemande + ITitreDemande, + ILog } diff --git a/tests/titres-demarches.test.ts b/tests/titres-demarches.test.ts index da8225043..0127beb69 100644 --- a/tests/titres-demarches.test.ts +++ b/tests/titres-demarches.test.ts @@ -3,6 +3,7 @@ import { graphQLCall, queryImport } from './_utils/index' import { titreCreate } from '../src/database/queries/titres' import { administrations } from './__mocks__/administrations' import { titreEtapeUpsert } from '../src/database/queries/titres-etapes' +import { userSuper } from '../src/database/user-super' console.info = jest.fn() console.error = jest.fn() @@ -225,13 +226,16 @@ describe('demarcheModifier', () => { test('ne peut pas modifier le type d’une démarche si elle a au moins une étape', async () => { const { demarcheId, titreId } = await demarcheCreate() - await titreEtapeUpsert({ - id: `${demarcheId}-mno01`, - typeId: 'mno', - titreDemarcheId: demarcheId, - statutId: 'acc', - date: '2020-01-01' - }) + await titreEtapeUpsert( + { + id: `${demarcheId}-mno01`, + typeId: 'mno', + titreDemarcheId: demarcheId, + statutId: 'acc', + date: '2020-01-01' + }, + userSuper + ) const res = await graphQLCall( demarcheModifierQuery, diff --git a/tests/titres-etapes-modifier.test.ts b/tests/titres-etapes-modifier.test.ts index 9631298f6..a5780e612 100644 --- a/tests/titres-etapes-modifier.test.ts +++ b/tests/titres-etapes-modifier.test.ts @@ -7,6 +7,7 @@ import { titreCreate } from '../src/database/queries/titres' import { titreEtapeCreate } from '../src/database/queries/titres-etapes' import { titreEtapePropsIds } from '../src/business/utils/titre-etape-heritage-props-find' import Titres from '../src/database/models/titres' +import { userSuper } from '../src/database/user-super' jest.mock('../src/tools/dir-create', () => ({ __esModule: true, @@ -63,13 +64,16 @@ async function etapeCreate() { }) const titreEtapeId = 'etape-test-id' - await titreEtapeCreate({ - id: titreEtapeId, - typeId: 'mfr', - statutId: 'fai', - titreDemarcheId, - date: '' - }) + await titreEtapeCreate( + { + id: titreEtapeId, + typeId: 'mfr', + statutId: 'fai', + titreDemarcheId, + date: '' + }, + userSuper + ) return { titreDemarcheId, titreEtapeId } } diff --git a/tsconfig.json b/tsconfig.json index cb012635a..c12b039b3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "alwaysStrict": true, "esModuleInterop": true, "inlineSources": true, - "lib": ["es2020"], + "lib": ["es2020", "dom"], "module": "commonjs", "moduleResolution": "node", "noFallthroughCasesInSwitch": true, -- GitLab