From 3ce6c58264dfb8207099f62dfb63f5e061d04932 Mon Sep 17 00:00:00 2001 From: Fufeck Date: Mon, 28 Oct 2024 15:08:23 +0100 Subject: [PATCH 1/5] migration: api-depot postgres --- src/lib/types/api-depot.types.ts | 141 +++++++++++++++++++++ src/modules/api_depot/api_depot.service.ts | 17 ++- 2 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 src/lib/types/api-depot.types.ts diff --git a/src/lib/types/api-depot.types.ts b/src/lib/types/api-depot.types.ts new file mode 100644 index 0000000..04da3fe --- /dev/null +++ b/src/lib/types/api-depot.types.ts @@ -0,0 +1,141 @@ +// HABILITATION + +export enum StatusHabilitationEnum { + ACCEPTED = 'accepted', + PENDING = 'pending', + REJECTED = 'rejected', +} + +export enum TypeStrategyEnum { + EMAIL = 'email', + FRANCECONNECT = 'franceconnect', + INTERNAL = 'internal', +} + +export type Mandat = { + nomMarital: string; + nomNaissance: string; + prenom: string; +}; + +export type Strategy = { + type: TypeStrategyEnum; + // EMAIL + pinCode?: string; + pinCodeExpiration?: Date | null; + createdAt?: Date | null; + remainingAttempts?: number; + // FRANCECONNECT + mandat?: Mandat; + authenticationError?: string; +}; + +export type Habilitation = { + id?: string; + clientId?: string; + codeCommune: string; + emailCommune: string; + franceconnectAuthenticationUrl?: string; + status: StatusHabilitationEnum; + strategy?: Strategy | null; + expiresAt?: Date; + acceptedAt?: Date; + rejectedAt?: Date; + createdAt?: Date; + updatedAt?: Date; +}; + +// FILE + +export enum TypeFileEnum { + BAL = 'bal', +} +export type File = { + id?: string; + revisionId?: string; + size?: number; + hash?: string; + type?: TypeFileEnum; + createdAt?: Date; +}; + +export enum StatusRevisionEnum { + PENDING = 'pending', + PUBLISHED = 'published', +} + +// REVISION + +export interface ParseError { + type: string; + code: string; + message: string; + row: number; +} + +export type Validation = { + valid: boolean; + validatorVersion?: string; + parseErrors?: ParseError[]; + errors?: string[]; + warnings?: string[]; + infos?: string[]; + rowsCount?: number; +}; + +export type Context = { + nomComplet?: string; + organisation?: string; + extras?: Record | null; +}; + +export type PublicClient = { + id: string; + specId?: string; + nom: string; + mandataire: string; + chefDeFile?: string; + chefDeFileEmail?: string; +}; + +export type Revision = { + id?: string; + clientId?: string; + codeCommune: string; + ready: boolean; + current: boolean; + status: StatusRevisionEnum; + context?: Context; + validation?: Validation | null; + habilitation?: Habilitation | null; + files?: File[]; + client?: PublicClient; + publishedAt?: Date; + createdAt: Date; + updatedAt: Date; +}; + +// CHEF DE FILE + +export enum TypePerimeterEnum { + COMMUNE = 'commune', + DEPARTEMENT = 'departement', + EPCI = 'epci', +} + +export class Perimeter { + id?: string; + chefDeFileId?: string; + type: TypePerimeterEnum; + code: string; +} + +export type ChefDeFile = { + id?: string; + nom: string; + email?: string; + isEmailPublic: boolean; + perimeters?: Perimeter[]; + createdAt: Date; + updatedAt: Date; +}; diff --git a/src/modules/api_depot/api_depot.service.ts b/src/modules/api_depot/api_depot.service.ts index b52d3e9..3e1b129 100644 --- a/src/modules/api_depot/api_depot.service.ts +++ b/src/modules/api_depot/api_depot.service.ts @@ -6,6 +6,7 @@ import { Revision, StatusPublicationEnum, } from '../revision/revision.entity'; +import { Revision as RevisionApiDepot } from '../../lib/types/api-depot.types'; import { Organization } from '../organization/organization.entity'; import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; import { catchError, firstValueFrom, of } from 'rxjs'; @@ -25,7 +26,9 @@ export class ApiDepotService { ); } - private async getCurrentRevision(codeCommune: string) { + private async getCurrentRevision( + codeCommune: string, + ): Promise { const url: string = `/communes/${codeCommune}/current-revision`; const options: AxiosRequestConfig = { responseType: 'json' }; const { data: revision } = await firstValueFrom( @@ -50,7 +53,7 @@ export class ApiDepotService { codeCommune: string, extras: any, organisation: string, - ) { + ): Promise { const url: string = `/communes/${codeCommune}/revisions`; const options: AxiosRequestConfig = { responseType: 'json' }; const body = { context: { extras, organisation } }; @@ -102,7 +105,7 @@ export class ApiDepotService { } } - private async publishRevision(revisionId: string) { + private async publishRevision(revisionId: string): Promise { const url: string = `/revisions/${revisionId}/publish`; const { data }: AxiosResponse = await firstValueFrom( @@ -131,11 +134,11 @@ export class ApiDepotService { if ( !options.force && currentPublishedRevision?.client && - currentPublishedRevision?.client?.id !== this.API_DEPOT_CLIENT_ID + currentPublishedRevision?.client?.specId !== this.API_DEPOT_CLIENT_ID ) { return { status: StatusPublicationEnum.PROVIDED_BY_OTHER_CLIENT, - currentClientId: currentPublishedRevision.client._id, + currentClientId: currentPublishedRevision.client.id, }; } // CHECK SI IL EXISTE UNE AUTRE SOURCE QUI MOISSONE CETTE COMMUNE @@ -161,7 +164,7 @@ export class ApiDepotService { uniqueErrors: validation.uniqueErrors, }; // ON CREER UNE REVISION POUR LA COMMUNE - const { _id: revisionId } = await this.createRevision( + const { id: revisionId } = await this.createRevision( codeCommune, extras, organization.name, @@ -174,7 +177,7 @@ export class ApiDepotService { const publishedRevision = await this.publishRevision(revisionId); return { status: StatusPublicationEnum.PUBLISHED, - publishedRevisionId: publishedRevision._id, + publishedRevisionId: publishedRevision.id, }; } catch (error) { this.logger.error( From 233bd1a551aa6c685c367a50ebece0f1d2198eba Mon Sep 17 00:00:00 2001 From: Fufeck Date: Mon, 28 Oct 2024 16:30:56 +0100 Subject: [PATCH 2/5] correct test --- src/modules/worker/tests/harvesting.spec.ts | 25 ++++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/modules/worker/tests/harvesting.spec.ts b/src/modules/worker/tests/harvesting.spec.ts index 9acad1f..151100c 100644 --- a/src/modules/worker/tests/harvesting.spec.ts +++ b/src/modules/worker/tests/harvesting.spec.ts @@ -204,6 +204,7 @@ describe('HARVESTING WORKER', () => { }; expect(harvestRes).toMatchObject(harvestExpected); }); + it('First harvesting', async () => { // CREATE ORGA const orgaInit = { @@ -239,14 +240,14 @@ describe('HARVESTING WORKER', () => { const revisionId = new ObjectId().toHexString(); axiosMock .onPost(`/communes/31591/revisions`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); axiosMock.onPut(`/revisions/${revisionId}/files/bal`).replyOnce(200); axiosMock .onPost(`/revisions/${revisionId}/compute`) .replyOnce(200, { validation: { valid: true } }); axiosMock .onPost(`/revisions/${revisionId}/publish`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); // RUN WORKER await harvestingWorker.run(); // CHECK HARVEST @@ -277,6 +278,7 @@ describe('HARVESTING WORKER', () => { expect(revisionRes).toMatchObject(revisionExpected); expect(revisionRes.createdAt).toBeInstanceOf(Date); }); + it('Harvesting with last harvest', async () => { // CREATE ORGA const orgaInit = { @@ -317,16 +319,16 @@ describe('HARVESTING WORKER', () => { const revisionId = new ObjectId().toHexString(); axiosMock .onPost(`/communes/31591/revisions`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); axiosMock.onPut(`/revisions/${revisionId}/files/bal`).replyOnce(200); axiosMock .onPost(`/revisions/${revisionId}/compute`) .replyOnce(200, { validation: { valid: true } }); axiosMock .onPost(`/revisions/${revisionId}/publish`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); axiosMock.onGet(`/communes/31591/current-revision`).replyOnce(200, { - client: { _id: '_moissonneur-bal', id: 'moissonneur-bal' }, + client: { id: 'id_moissonneur-bal', specId: 'moissonneur-bal' }, }); // RUN WORKER await harvestingWorker.run(); @@ -360,6 +362,7 @@ describe('HARVESTING WORKER', () => { expect(revisionRes).toMatchObject(revisionExpected); expect(revisionRes.createdAt).toBeInstanceOf(Date); }); + it('Harvesting with last harvest (file no change)', async () => { // CREATE ORGA const orgaInit = { @@ -497,7 +500,7 @@ describe('HARVESTING WORKER', () => { axiosMock.onGet(url).replyOnce(200, readFile('1.3-valid.csv')); // MOCK PUBLICATION API DEPOT axiosMock.onGet(`/communes/31591/current-revision`).replyOnce(200, { - client: { _id: '_other-client', id: 'other-client' }, + client: { id: 'id_other-client', specId: 'spec-other-client' }, }); // RUN WORKER await harvestingWorker.run(); @@ -523,7 +526,7 @@ describe('HARVESTING WORKER', () => { }, publication: { status: StatusPublicationEnum.PROVIDED_BY_OTHER_CLIENT, - currentClientId: '_other-client', + currentClientId: 'id_other-client', }, }; expect(revisionRes).toMatchObject(revisionExpected); @@ -636,14 +639,14 @@ describe('HARVESTING WORKER', () => { const revisionId = new ObjectId().toHexString(); axiosMock .onPost(`/communes/31591/revisions`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); axiosMock.onPut(`/revisions/${revisionId}/files/bal`).replyOnce(200); axiosMock .onPost(`/revisions/${revisionId}/compute`) .replyOnce(200, { validation: { valid: true } }); axiosMock .onPost(`/revisions/${revisionId}/publish`) - .replyOnce(200, { _id: revisionId }); + .replyOnce(200, { id: revisionId }); // MOCK PUBLICATION API DEPOT axiosMock .onGet(`/communes/67482/current-revision`) @@ -651,14 +654,14 @@ describe('HARVESTING WORKER', () => { const revisionId2 = new ObjectId().toHexString(); axiosMock .onPost(`/communes/67482/revisions`) - .replyOnce(200, { _id: revisionId2 }); + .replyOnce(200, { id: revisionId2 }); axiosMock.onPut(`/revisions/${revisionId2}/files/bal`).replyOnce(200); axiosMock .onPost(`/revisions/${revisionId2}/compute`) .replyOnce(200, { validation: { valid: true } }); axiosMock .onPost(`/revisions/${revisionId2}/publish`) - .replyOnce(200, { _id: revisionId2 }); + .replyOnce(200, { id: revisionId2 }); // RUN WORKER await harvestingWorker.run(); // CHECK HARVEST From 475e651383f25b0df81896045568a40148c7402d Mon Sep 17 00:00:00 2001 From: Fufeck Date: Tue, 29 Oct 2024 11:40:10 +0100 Subject: [PATCH 3/5] update field api-depot types --- src/lib/types/api-depot.types.ts | 51 ++++++++++++++++++---- src/modules/api_depot/api_depot.service.ts | 2 +- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/lib/types/api-depot.types.ts b/src/lib/types/api-depot.types.ts index 04da3fe..220b9be 100644 --- a/src/lib/types/api-depot.types.ts +++ b/src/lib/types/api-depot.types.ts @@ -66,12 +66,12 @@ export enum StatusRevisionEnum { // REVISION -export interface ParseError { +export type ParseError = { type: string; code: string; message: string; row: number; -} +}; export type Validation = { valid: boolean; @@ -91,7 +91,7 @@ export type Context = { export type PublicClient = { id: string; - specId?: string; + legacyId?: string; nom: string; mandataire: string; chefDeFile?: string; @@ -102,8 +102,8 @@ export type Revision = { id?: string; clientId?: string; codeCommune: string; - ready: boolean; - current: boolean; + isReady: boolean; + isCurrent: boolean; status: StatusRevisionEnum; context?: Context; validation?: Validation | null; @@ -123,19 +123,52 @@ export enum TypePerimeterEnum { EPCI = 'epci', } -export class Perimeter { +export type Perimeter = { id?: string; chefDeFileId?: string; type: TypePerimeterEnum; code: string; -} +}; export type ChefDeFile = { id?: string; - nom: string; + nom?: string; email?: string; - isEmailPublic: boolean; + isEmailPublic?: boolean; + isSignataireCharte?: boolean; perimeters?: Perimeter[]; + createdAt?: Date; + updatedAt?: Date; +}; + +// MANDATAIRE + +export type Mandataire = { + id?: string; + nom: string; + email: string; + createdAt: Date; + updatedAt: Date; +}; + +// CLIENT + +export enum AuthorizationStrategyEnum { + INTERNAL = 'internal', + CHEF_DE_FILE = 'chef-de-file', + HABILITATION = 'habilitation', +} + +export type Client = { + id?: string; + mandataireId?: string; + chefDeFileId?: string; + legacyId: string; + nom: string; + isActive: boolean; + isRelaxMode: boolean; + token?: string; + authorizationStrategy: AuthorizationStrategyEnum; createdAt: Date; updatedAt: Date; }; diff --git a/src/modules/api_depot/api_depot.service.ts b/src/modules/api_depot/api_depot.service.ts index 3e1b129..a455cbf 100644 --- a/src/modules/api_depot/api_depot.service.ts +++ b/src/modules/api_depot/api_depot.service.ts @@ -134,7 +134,7 @@ export class ApiDepotService { if ( !options.force && currentPublishedRevision?.client && - currentPublishedRevision?.client?.specId !== this.API_DEPOT_CLIENT_ID + currentPublishedRevision?.client?.legacyId !== this.API_DEPOT_CLIENT_ID ) { return { status: StatusPublicationEnum.PROVIDED_BY_OTHER_CLIENT, From 7c809e9274d0d2c746e19bf8419d3f25d8c2356e Mon Sep 17 00:00:00 2001 From: Fufeck Date: Tue, 29 Oct 2024 14:20:37 +0100 Subject: [PATCH 4/5] correct test --- src/modules/worker/tests/harvesting.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/worker/tests/harvesting.spec.ts b/src/modules/worker/tests/harvesting.spec.ts index 151100c..f86070e 100644 --- a/src/modules/worker/tests/harvesting.spec.ts +++ b/src/modules/worker/tests/harvesting.spec.ts @@ -328,7 +328,7 @@ describe('HARVESTING WORKER', () => { .onPost(`/revisions/${revisionId}/publish`) .replyOnce(200, { id: revisionId }); axiosMock.onGet(`/communes/31591/current-revision`).replyOnce(200, { - client: { id: 'id_moissonneur-bal', specId: 'moissonneur-bal' }, + client: { id: 'id_moissonneur-bal', legacyId: 'moissonneur-bal' }, }); // RUN WORKER await harvestingWorker.run(); @@ -500,7 +500,7 @@ describe('HARVESTING WORKER', () => { axiosMock.onGet(url).replyOnce(200, readFile('1.3-valid.csv')); // MOCK PUBLICATION API DEPOT axiosMock.onGet(`/communes/31591/current-revision`).replyOnce(200, { - client: { id: 'id_other-client', specId: 'spec-other-client' }, + client: { id: 'id_other-client', legacyId: 'spec-other-client' }, }); // RUN WORKER await harvestingWorker.run(); From bb99c2d348faff075f630658c65773b891f8719b Mon Sep 17 00:00:00 2001 From: Fufeck Date: Tue, 26 Nov 2024 16:00:24 +0100 Subject: [PATCH 5/5] id not optional --- src/lib/types/api-depot.types.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/types/api-depot.types.ts b/src/lib/types/api-depot.types.ts index 220b9be..69bb574 100644 --- a/src/lib/types/api-depot.types.ts +++ b/src/lib/types/api-depot.types.ts @@ -31,7 +31,7 @@ export type Strategy = { }; export type Habilitation = { - id?: string; + id: string; clientId?: string; codeCommune: string; emailCommune: string; @@ -51,7 +51,7 @@ export enum TypeFileEnum { BAL = 'bal', } export type File = { - id?: string; + id: string; revisionId?: string; size?: number; hash?: string; @@ -99,7 +99,7 @@ export type PublicClient = { }; export type Revision = { - id?: string; + id: string; clientId?: string; codeCommune: string; isReady: boolean; @@ -131,7 +131,7 @@ export type Perimeter = { }; export type ChefDeFile = { - id?: string; + id: string; nom?: string; email?: string; isEmailPublic?: boolean; @@ -144,7 +144,7 @@ export type ChefDeFile = { // MANDATAIRE export type Mandataire = { - id?: string; + id: string; nom: string; email: string; createdAt: Date; @@ -160,7 +160,7 @@ export enum AuthorizationStrategyEnum { } export type Client = { - id?: string; + id: string; mandataireId?: string; chefDeFileId?: string; legacyId: string;