From 84e7baf2651f3afc170bc14ea1c10ee432525151 Mon Sep 17 00:00:00 2001 From: "Yassine R." Date: Tue, 19 Aug 2025 15:08:12 +0200 Subject: [PATCH 1/3] feat(backend): add sms history and pagination --- .../1755523274895-auto-migration.ts | 4 +- .../sms/sms.controller.security-tests.ts | 4 +- .../backend/src/modules/sms/sms.controller.ts | 39 ++++++++++------- .../src/pagination/PageOptions.class.ts | 13 ++++++ .../src/pagination/PageOptions.interface.ts | 7 ---- .../src/pagination/PageResults.class.ts | 18 ++++++++ .../src/pagination/PageResults.interface.ts | 6 --- packages/common/src/pagination/index.ts | 4 +- .../manage-user-usager.component.ts | 20 +-------- .../base-usager-notes.component.ts | 9 +--- .../profil-historique-courriers.component.ts | 21 +--------- ...ofil-historique-login-portail.component.ts | 19 ++------- .../profil-historique-sms.component.html | 40 ++++++++++++++++-- .../profil-historique-sms.component.ts | 42 +++++++++++++++---- .../services/usager-profil.service.ts | 17 ++++++-- 15 files changed, 156 insertions(+), 107 deletions(-) create mode 100644 packages/common/src/pagination/PageOptions.class.ts delete mode 100644 packages/common/src/pagination/PageOptions.interface.ts create mode 100644 packages/common/src/pagination/PageResults.class.ts delete mode 100644 packages/common/src/pagination/PageResults.interface.ts diff --git a/packages/backend/src/_migrations/1755523274895-auto-migration.ts b/packages/backend/src/_migrations/1755523274895-auto-migration.ts index e9ad99abc3..b29c79f6b0 100644 --- a/packages/backend/src/_migrations/1755523274895-auto-migration.ts +++ b/packages/backend/src/_migrations/1755523274895-auto-migration.ts @@ -30,7 +30,7 @@ export class AutoMigration1755523274895 implements MigrationInterface { UPDATE user_usager SET "passwordType" = 'RANDOM' WHERE "isTemporaryPassword" = true - AND "createdAt" >= $1 + AND "createdAt" < $1 `, [cutoffDate] ); @@ -45,7 +45,7 @@ export class AutoMigration1755523274895 implements MigrationInterface { UPDATE user_usager SET "passwordType" = 'BIRTH_DATE' WHERE "isTemporaryPassword" = true - AND "createdAt" < $1 + AND "createdAt" >= $1 `, [cutoffDate] ); diff --git a/packages/backend/src/modules/sms/sms.controller.security-tests.ts b/packages/backend/src/modules/sms/sms.controller.security-tests.ts index ab357f4b38..115f588d62 100644 --- a/packages/backend/src/modules/sms/sms.controller.security-tests.ts +++ b/packages/backend/src/modules/sms/sms.controller.security-tests.ts @@ -5,6 +5,7 @@ import { expectedResponseStatusBuilder, } from "../../_tests"; import { AppTestContext, AppTestHttpClient } from "../../util/test"; +import { PageOptions } from "@domifa/common"; ////////////////// IMPORTANT ////////////////// // @@ -19,8 +20,9 @@ export const SmsControllerSecurityTests: AppTestHttpClientSecurityTestDef[] = [ label: `${CONTROLLER}.getUsagerSms`, query: async (context: AppTestContext) => { return { - response: await AppTestHttpClient.get(`/sms/usager/4444444`, { + response: await AppTestHttpClient.post(`/sms/usager/4444444`, { context, + body: new PageOptions(), }), expectedStatus: expectedResponseStatusBuilder.allowStructureOnly( context.user, diff --git a/packages/backend/src/modules/sms/sms.controller.ts b/packages/backend/src/modules/sms/sms.controller.ts index d6a218de5a..bbd1b1df20 100644 --- a/packages/backend/src/modules/sms/sms.controller.ts +++ b/packages/backend/src/modules/sms/sms.controller.ts @@ -1,5 +1,5 @@ import { Usager } from "@domifa/common"; -import { Controller, UseGuards, Get } from "@nestjs/common"; +import { Controller, UseGuards, Post, Body } from "@nestjs/common"; import { AuthGuard } from "@nestjs/passport"; import { ApiTags, ApiBearerAuth } from "@nestjs/swagger"; import { USER_STRUCTURE_ROLE_ALL } from "../../_common/model"; @@ -10,6 +10,8 @@ import { } from "../../auth/decorators"; import { AppUserGuard, UsagerAccessGuard } from "../../auth/guards"; import { messageSmsRepository } from "../../database"; +import { PageMetaDto, PageOptionsDto, PageResultsDto } from "../../usagers/dto"; +import { ObjectLiteral } from "typeorm"; @Controller("sms") @UseGuards(AuthGuard("jwt"), AppUserGuard) @@ -19,18 +21,27 @@ import { messageSmsRepository } from "../../database"; export class SmsController { @ApiBearerAuth() @UseGuards(UsagerAccessGuard) - @Get("usager/:usagerRef") - public async getUsagerSms(@CurrentUsager() currentUsager: Usager) { - return await messageSmsRepository.find({ - where: { - usagerRef: currentUsager.ref, - structureId: currentUsager.structureId, - }, - order: { - createdAt: "DESC", - }, - skip: 0, - take: 50, - }); + @Post("usager/:usagerRef") + public async getUsagerSms( + @CurrentUsager() currentUsager: Usager, + @Body() pageOptionsDto: PageOptionsDto + ) { + const queryBuilder = messageSmsRepository.createQueryBuilder("message_sms"); + + const whereConditions: ObjectLiteral = { + structureId: currentUsager.structureId, + usagerRef: currentUsager.ref, + }; + + queryBuilder + .where(whereConditions) + .orderBy("id", pageOptionsDto.order) + .skip((pageOptionsDto.page - 1) * pageOptionsDto.take) + .take(pageOptionsDto.take); + + const itemCount = await queryBuilder.getCount(); + const { entities } = await queryBuilder.getRawAndEntities(); + const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); + return new PageResultsDto(entities, pageMetaDto); } } diff --git a/packages/common/src/pagination/PageOptions.class.ts b/packages/common/src/pagination/PageOptions.class.ts new file mode 100644 index 0000000000..0aadfc4e50 --- /dev/null +++ b/packages/common/src/pagination/PageOptions.class.ts @@ -0,0 +1,13 @@ +import { Order } from "./PageOrder.enum"; + +export class PageOptions { + public order: Order; + public page: number; + public take: number; + + constructor(options?: Partial) { + this.order = options?.order ?? Order.DESC; + this.page = options?.page ?? 1; + this.take = options?.take ?? 5; + } +} diff --git a/packages/common/src/pagination/PageOptions.interface.ts b/packages/common/src/pagination/PageOptions.interface.ts deleted file mode 100644 index efec8e52a7..0000000000 --- a/packages/common/src/pagination/PageOptions.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type Order } from "./PageOrder.enum"; - -export interface PageOptions { - order: Order; - page: number; - take: number; -} diff --git a/packages/common/src/pagination/PageResults.class.ts b/packages/common/src/pagination/PageResults.class.ts new file mode 100644 index 0000000000..21dc9326a8 --- /dev/null +++ b/packages/common/src/pagination/PageResults.class.ts @@ -0,0 +1,18 @@ +import { type PageMeta } from "./PageMeta.interface"; + +export class PageResults { + public data: T[]; + public meta: PageMeta; + + constructor(results?: PageResults) { + this.data = results?.data ?? []; + this.meta = results?.meta ?? { + page: 0, + take: 0, + itemCount: 0, + pageCount: 0, + hasPreviousPage: false, + hasNextPage: false, + }; + } +} diff --git a/packages/common/src/pagination/PageResults.interface.ts b/packages/common/src/pagination/PageResults.interface.ts deleted file mode 100644 index 6a67b573d5..0000000000 --- a/packages/common/src/pagination/PageResults.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type PageMeta } from "./PageMeta.interface"; - -export interface PageResults { - data: T[]; - meta: PageMeta; -} diff --git a/packages/common/src/pagination/index.ts b/packages/common/src/pagination/index.ts index 75c8b22687..f36ecafb5d 100644 --- a/packages/common/src/pagination/index.ts +++ b/packages/common/src/pagination/index.ts @@ -1,5 +1,5 @@ // @index('./*', f => `export * from '${f.path}'`) export * from "./PageMeta.interface"; -export * from "./PageOptions.interface"; +export * from "./PageOptions.class"; export * from "./PageOrder.enum"; -export * from "./PageResults.interface"; +export * from "./PageResults.class"; diff --git a/packages/frontend/src/app/modules/admin-portail-usagers/components/manage-user-usager/manage-user-usager.component.ts b/packages/frontend/src/app/modules/admin-portail-usagers/components/manage-user-usager/manage-user-usager.component.ts index bbab976f79..788ac7f780 100644 --- a/packages/frontend/src/app/modules/admin-portail-usagers/components/manage-user-usager/manage-user-usager.component.ts +++ b/packages/frontend/src/app/modules/admin-portail-usagers/components/manage-user-usager/manage-user-usager.component.ts @@ -1,7 +1,6 @@ import { Component, OnInit, TemplateRef, ViewChild } from "@angular/core"; import { NgbModal, NgbModalRef } from "@ng-bootstrap/ng-bootstrap"; import { - Order, PageOptions, PageResults, UsagersCountByStatus, @@ -23,24 +22,9 @@ import saveAs from "file-saver"; export class ManageUserUsagerComponent implements OnInit { @ViewChild("activateAllAccountsModal", { static: true }) public activateAllAccountsModal!: TemplateRef; + public params = new PageOptions({ take: 50 }); - public params: PageOptions = { - order: Order.DESC, - page: 1, - take: 50, - }; - - public searchResults: PageResults = { - data: [], - meta: { - page: 0, - take: 0, - itemCount: 0, - pageCount: 0, - hasPreviousPage: false, - hasNextPage: false, - }, - }; + public searchResults = new PageResults(); public loading = false; public activatingAccounts = false; diff --git a/packages/frontend/src/app/modules/usager-notes/components/base-usager-notes/base-usager-notes.component.ts b/packages/frontend/src/app/modules/usager-notes/components/base-usager-notes/base-usager-notes.component.ts index dd0b5851bb..c91d732a4e 100644 --- a/packages/frontend/src/app/modules/usager-notes/components/base-usager-notes/base-usager-notes.component.ts +++ b/packages/frontend/src/app/modules/usager-notes/components/base-usager-notes/base-usager-notes.component.ts @@ -4,7 +4,6 @@ import { UsagerLight } from "../../../../../_common/model"; import { PageOptions, PageResults, - Order, UsagerNote, UserStructure, } from "@domifa/common"; @@ -23,7 +22,7 @@ export class BaseUsagerNotesComponent implements OnInit, OnDestroy { @Input() public me!: UserStructure; @Input() public usager!: UsagerFormModel; - public params!: PageOptions; + public params = new PageOptions(); public loading: boolean; public notes: UsagerNote[]; @@ -41,11 +40,7 @@ export class BaseUsagerNotesComponent implements OnInit, OnDestroy { ) { this.loading = false; this.notes = []; - this.params = { - order: Order.DESC, - page: 1, - take: 5, - }; + this.currentUserSubject$ = this.authService.currentUserSubject; } diff --git a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-courriers/profil-historique-courriers.component.ts b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-courriers/profil-historique-courriers.component.ts index 3b62fcad3a..f759e2e57c 100644 --- a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-courriers/profil-historique-courriers.component.ts +++ b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-courriers/profil-historique-courriers.component.ts @@ -18,7 +18,6 @@ import { UserStructure, Interaction, PageOptions, - Order, PageResults, } from "@domifa/common"; @@ -40,25 +39,9 @@ export class ProfilHistoriqueCourriersComponent implements OnInit, OnDestroy { public deleteInteractionModal!: TemplateRef; public loading: boolean; + public params = new PageOptions({ take: 50 }); - public params: PageOptions = { - order: Order.DESC, - page: 1, - take: 50, - }; - - public searchResults: PageResults = { - data: [], - meta: { - page: 0, - take: 0, - itemCount: 0, - pageCount: 0, - hasPreviousPage: false, - hasNextPage: false, - }, - }; - + public searchResults = new PageResults(); constructor( private readonly toastService: CustomToastService, private readonly interactionService: InteractionService, diff --git a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-login-portail/profil-historique-login-portail.component.ts b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-login-portail/profil-historique-login-portail.component.ts index e441fc0ce9..71bac48368 100644 --- a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-login-portail/profil-historique-login-portail.component.ts +++ b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-login-portail/profil-historique-login-portail.component.ts @@ -1,6 +1,5 @@ import { Component, Input, OnDestroy, OnInit } from "@angular/core"; import { - Order, PageOptions, PageResults, UserStructure, @@ -29,23 +28,11 @@ export class ProfilHistoriqueLoginPortailComponent public loading: boolean; - public params: PageOptions = { - order: Order.DESC, - page: 1, + public params = new PageOptions({ take: 10, - }; + }); - public searchResults: PageResults = { - data: [], - meta: { - page: 0, - take: 0, - itemCount: 0, - pageCount: 0, - hasPreviousPage: false, - hasNextPage: false, - }, - }; + public searchResults = new PageResults(); constructor(private readonly interactionService: InteractionService) { this.loading = true; diff --git a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.html b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.html index 05a772691b..c4f4d2241f 100644 --- a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.html +++ b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.html @@ -12,7 +12,7 @@

Historique des envois de SMS

-
+
- +
Historique des SMS envoyés @@ -27,7 +27,7 @@

Historique des envois de SMS

{{ (sms.sendDate ? sms.sendDate : sms.scheduledDate) @@ -42,7 +42,41 @@

Historique des envois de SMS

+
+
+ + +
-
+ +
+ +

Aucun sms enregistré actuellement

diff --git a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.ts b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.ts index b27c9f9442..4022181485 100644 --- a/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.ts +++ b/packages/frontend/src/app/modules/usager-profil/components/_historiques/profil-historique-sms/profil-historique-sms.component.ts @@ -3,7 +3,13 @@ import { Subscription } from "rxjs"; import { UsagerFormModel } from "../../../../usager-shared/interfaces"; import { UsagerProfilService } from "../../../services/usager-profil.service"; -import { MessageSms, SMS_STATUS_LABELS, UserStructure } from "@domifa/common"; +import { + MessageSms, + PageOptions, + PageResults, + SMS_STATUS_LABELS, + UserStructure, +} from "@domifa/common"; import { SMS_LABELS } from "../../../constants"; @Component({ @@ -18,21 +24,41 @@ export class ProfilHistoriqueSmsComponent implements OnInit, OnDestroy { public readonly SMS_LABELS = SMS_LABELS; public readonly SMS_STATUS_LABELS = SMS_STATUS_LABELS; - public messagesList: MessageSms[]; + + public loading: boolean; + + public params = new PageOptions({ + take: 10, + }); + + public searchResults = new PageResults(); constructor(private readonly usagerProfilService: UsagerProfilService) { - this.messagesList = []; + this.loading = true; } public ngOnInit(): void { - this.subscription.add( - this.usagerProfilService.findMySms(this.usager.ref).subscribe({ - next: (messages: MessageSms[]) => (this.messagesList = messages), - }) - ); + this.getSms(); } public ngOnDestroy(): void { this.subscription.unsubscribe(); } + + public getSms() { + this.loading = true; + this.subscription.add( + this.usagerProfilService + .findMySms(this.usager.ref, this.params) + .subscribe((searchResults: PageResults) => { + this.loading = false; + this.searchResults = searchResults; + window.scroll({ + behavior: "smooth", + left: 0, + top: 0, + }); + }) + ); + } } diff --git a/packages/frontend/src/app/modules/usager-profil/services/usager-profil.service.ts b/packages/frontend/src/app/modules/usager-profil/services/usager-profil.service.ts index 177dc8dafa..bf675ecf77 100644 --- a/packages/frontend/src/app/modules/usager-profil/services/usager-profil.service.ts +++ b/packages/frontend/src/app/modules/usager-profil/services/usager-profil.service.ts @@ -5,7 +5,12 @@ import { environment } from "../../../../environments/environment"; import { UsagerLight } from "../../../../_common/model"; import { Store } from "@ngrx/store"; import { usagerActions, UsagerState } from "../../../shared"; -import { ApiMessage, MessageSms } from "@domifa/common"; +import { + ApiMessage, + MessageSms, + PageOptions, + PageResults, +} from "@domifa/common"; @Injectable({ providedIn: "root", @@ -56,9 +61,13 @@ export class UsagerProfilService { ); } - public findMySms(ref: number): Observable { - return this.http.get( - environment.apiUrl + "sms/usager/" + ref.toString() + public findMySms( + ref: number, + pageOptions: PageOptions + ): Observable> { + return this.http.post>( + environment.apiUrl + "sms/usager/" + ref.toString(), + pageOptions ); } } From 84134504459e4cfb5271a291aed7619e497817f0 Mon Sep 17 00:00:00 2001 From: "Yassine R." Date: Tue, 19 Aug 2025 15:23:34 +0200 Subject: [PATCH 2/3] Update packages/common/src/pagination/PageResults.class.ts Co-authored-by: Revu --- packages/common/src/pagination/PageResults.class.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/common/src/pagination/PageResults.class.ts b/packages/common/src/pagination/PageResults.class.ts index 21dc9326a8..e4d0b1751b 100644 --- a/packages/common/src/pagination/PageResults.class.ts +++ b/packages/common/src/pagination/PageResults.class.ts @@ -4,11 +4,11 @@ export class PageResults { public data: T[]; public meta: PageMeta; - constructor(results?: PageResults) { + constructor(results?: Partial>) { this.data = results?.data ?? []; this.meta = results?.meta ?? { - page: 0, - take: 0, + page: 1, + take: 5, itemCount: 0, pageCount: 0, hasPreviousPage: false, From 7556185299d489f6a2a011da571b5ed9d03307ea Mon Sep 17 00:00:00 2001 From: "Yassine R." Date: Tue, 19 Aug 2025 19:32:04 +0200 Subject: [PATCH 3/3] fix(backend): refactor of search classes --- .../1755523274895-auto-migration.ts | 123 +++++++++--------- .../1755523274896-auto-migration.ts | 19 +-- .../interactions/interactions.controller.ts | 18 ++- .../services/interactions.service.ts | 16 +-- .../portail-usagers-manager.controller.ts | 18 +-- .../backend/src/modules/sms/sms.controller.ts | 40 +++--- .../controllers/usager-notes.controller.ts | 12 +- .../src/usagers/dto/pagination/index.ts | 6 +- .../pagination/page-meta-parameters.dto.ts | 6 - .../usagers/dto/pagination/page-meta.dto.ts | 31 ----- ...page-search.dto.ts => page-options.dto.ts} | 8 +- .../dto/pagination/page-results.dto.ts | 17 --- .../dto/pagination/results-order.enum.ts | 4 - .../src/pagination/PageItem.interface.ts | 6 + .../common/src/pagination/PageMeta.class.ts | 30 +++++ .../src/pagination/PageMeta.interface.ts | 8 -- .../src/pagination/PageResults.class.ts | 11 +- packages/common/src/pagination/index.ts | 3 +- 18 files changed, 172 insertions(+), 204 deletions(-) delete mode 100644 packages/backend/src/usagers/dto/pagination/page-meta-parameters.dto.ts delete mode 100644 packages/backend/src/usagers/dto/pagination/page-meta.dto.ts rename packages/backend/src/usagers/dto/pagination/{page-search.dto.ts => page-options.dto.ts} (83%) delete mode 100644 packages/backend/src/usagers/dto/pagination/page-results.dto.ts delete mode 100644 packages/backend/src/usagers/dto/pagination/results-order.enum.ts create mode 100644 packages/common/src/pagination/PageItem.interface.ts create mode 100644 packages/common/src/pagination/PageMeta.class.ts delete mode 100644 packages/common/src/pagination/PageMeta.interface.ts diff --git a/packages/backend/src/_migrations/1755523274895-auto-migration.ts b/packages/backend/src/_migrations/1755523274895-auto-migration.ts index b29c79f6b0..6ad51205a0 100644 --- a/packages/backend/src/_migrations/1755523274895-auto-migration.ts +++ b/packages/backend/src/_migrations/1755523274895-auto-migration.ts @@ -6,86 +6,81 @@ export class AutoMigration1755523274895 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise { console.log("🚀 Starting password type migration"); - const cutoffDate = "2024-08-15"; + // Ajout de la colonne passwordType + await queryRunner.query(` + ALTER TABLE "user_usager" + ADD COLUMN "passwordType" text + `); // Initial stats - const initialStats = await queryRunner.query( - ` - SELECT - COUNT(*) as totalAccounts, - COUNT(*) FILTER (WHERE "createdAt" >= $1) as createdAfterCutoff, - COUNT(*) FILTER (WHERE "createdAt" < $1) as createdBeforeCutoff, - COUNT(*) FILTER (WHERE "isTemporaryPassword" = true) as temporaryPasswords, - COUNT(*) FILTER (WHERE "isTemporaryPassword" = false) as personalPasswords - FROM user_usager - `, - [cutoffDate] - ); + const initialStats = await queryRunner.query(` + SELECT + COUNT(*) as "totalAccounts", + COUNT(*) FILTER (WHERE "isTemporaryPassword" = true) as "temporaryPasswords", + COUNT(*) FILTER (WHERE "isTemporaryPassword" = false) as "personalPasswords" + FROM user_usager + `); console.log(`📊 Initial stats:`, initialStats[0]); - // Update RANDOM (isTemporaryPassword = true AND created after cutoff) - const randomUpdates = await queryRunner.query( - ` - UPDATE user_usager - SET "passwordType" = 'RANDOM' - WHERE "isTemporaryPassword" = true - AND "createdAt" < $1 - `, - [cutoffDate] - ); - - console.log( - `✅ Set ${randomUpdates.affectedRows || 0} accounts to RANDOM type` - ); - - // Update BIRTH_DATE (isTemporaryPassword = true AND created before cutoff) - const birthDateUpdates = await queryRunner.query( - ` - UPDATE user_usager - SET "passwordType" = 'BIRTH_DATE' - WHERE "isTemporaryPassword" = true - AND "createdAt" >= $1 - `, - [cutoffDate] - ); - - console.log( - `✅ Set ${birthDateUpdates.affectedRows || 0} accounts to BIRTH_DATE type` - ); - - // Update PERSONAL (isTemporaryPassword = false) - const personalUpdates = await queryRunner.query(` - UPDATE user_usager - SET "passwordType" = 'PERSONAL' - WHERE "isTemporaryPassword" = false - `); - - console.log( - `✅ Set ${personalUpdates.affectedRows || 0} accounts to PERSONAL type` - ); + // Update RANDOM pour tous les mots de passe temporaires + const randomResult = await queryRunner.query(` + UPDATE user_usager + SET "passwordType" = 'RANDOM' + WHERE "isTemporaryPassword" = true + `); + + console.log(`✅ Set ${randomResult[1]} accounts to RANDOM type`); + + // Update PERSONAL pour tous les mots de passe personnels + const personalResult = await queryRunner.query(` + UPDATE user_usager + SET "passwordType" = 'PERSONAL' + WHERE "isTemporaryPassword" = false + `); + + console.log(`✅ Set ${personalResult[1]} accounts to PERSONAL type`); + + // Verification qu'aucun compte n'a passwordType NULL + const nullPasswordTypes = await queryRunner.query(` + SELECT COUNT(*) as "nullCount" + FROM user_usager + WHERE "passwordType" IS NULL + `); + + if (nullPasswordTypes[0].nullCount > 0) { + throw new Error( + `❌ Migration failed: ${nullPasswordTypes[0].nullCount} accounts still have NULL passwordType` + ); + } // Final verification const finalStats = await queryRunner.query(` - SELECT - "passwordType", - COUNT(*) as count - FROM user_usager - GROUP BY "passwordType" - ORDER BY "passwordType" - `); + SELECT + "passwordType", + COUNT(*) as count + FROM user_usager + GROUP BY "passwordType" + ORDER BY "passwordType" + `); console.log("📊 Final distribution:"); finalStats.forEach((stat) => { - console.log(` ${stat.passwordType || "NULL"}: ${stat.count} accounts`); + console.log(` ${stat.passwordType}: ${stat.count} accounts`); }); + // Rendre la colonne NOT NULL maintenant qu'elle est peuplée + await queryRunner.query(` + ALTER TABLE "user_usager" + ALTER COLUMN "passwordType" SET NOT NULL + `); + console.log("🎉 Password type migration completed!"); } public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `ALTER TABLE "user_usager" DROP COLUMN "passwordType"` - ); + await queryRunner.query(` + ALTER TABLE "user_usager" DROP COLUMN "passwordType" + `); } } diff --git a/packages/backend/src/_migrations/1755523274896-auto-migration.ts b/packages/backend/src/_migrations/1755523274896-auto-migration.ts index efc075369e..7f9f591bb2 100644 --- a/packages/backend/src/_migrations/1755523274896-auto-migration.ts +++ b/packages/backend/src/_migrations/1755523274896-auto-migration.ts @@ -42,21 +42,14 @@ export class AutoMigration1755523274896 implements MigrationInterface { for (const user of accountExistingButDisabledUsers) { try { await queryRunner.query( - ` - UPDATE usager - SET options = jsonb_set( - COALESCE(options, '{}'), - '{portailUsagerEnabled}', - 'true'::jsonb - ) - WHERE uuid = $1 - `, + ` UPDATE usager SET options = jsonb_set( COALESCE(options, '{}'), '{portailUsagerEnabled}', 'true'::jsonb ) WHERE uuid = $1`, [user.uuid] ); - - console.log( - `✅ accountExistingButDisabled: Portal enabled for UUID: ${user.uuid}` - ); + if (accountExistingButDisabledUpdatedCount % 200 === 0) { + console.log( + `#️⃣ ${accountExistingButDisabledUpdatedCount} / ${accountExistingButDisabledUsers.length}` + ); + } accountExistingButDisabledUpdatedCount++; } catch (error) { console.error( diff --git a/packages/backend/src/modules/interactions/interactions.controller.ts b/packages/backend/src/modules/interactions/interactions.controller.ts index 99269af4f3..56875802a0 100644 --- a/packages/backend/src/modules/interactions/interactions.controller.ts +++ b/packages/backend/src/modules/interactions/interactions.controller.ts @@ -34,12 +34,13 @@ import { InteractionsService, interactionsCreator, } from "./services"; +import { PageOptionsDto } from "../../usagers/dto/pagination"; import { - PageMetaDto, - PageOptionsDto, - PageResultsDto, -} from "../../usagers/dto/pagination"; -import { CommonInteraction, Usager } from "@domifa/common"; + CommonInteraction, + Usager, + PageMeta, + PageResults, +} from "@domifa/common"; import { MessageSmsService } from "../sms/services/message-sms.service"; @UseGuards(AuthGuard("jwt"), AppUserGuard) @@ -117,8 +118,11 @@ export class InteractionsController { const itemCount = await queryBuilder.getCount(); const entities = await queryBuilder.getRawMany(); - const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); - return new PageResultsDto(entities, pageMetaDto); + const pageMetaDto = new PageMeta({ + itemCount, + pageOptions: pageOptionsDto, + }); + return new PageResults({ data: entities, meta: pageMetaDto }); } @UseGuards(UsagerAccessGuard, InteractionsGuard) diff --git a/packages/backend/src/modules/interactions/services/interactions.service.ts b/packages/backend/src/modules/interactions/services/interactions.service.ts index 45e9577663..202d8a80b5 100644 --- a/packages/backend/src/modules/interactions/services/interactions.service.ts +++ b/packages/backend/src/modules/interactions/services/interactions.service.ts @@ -1,12 +1,8 @@ import { Injectable } from "@nestjs/common"; import { interactionRepository } from "../../../database"; -import { - PageMetaDto, - PageOptionsDto, - PageResultsDto, -} from "../../../usagers/dto"; +import { PageOptionsDto } from "../../../usagers/dto"; import { In, IsNull, Not } from "typeorm"; -import { INTERACTIONS_IN } from "@domifa/common"; +import { INTERACTIONS_IN, PageMeta, PageResults } from "@domifa/common"; @Injectable() export class InteractionsService { @@ -36,8 +32,12 @@ export class InteractionsService { const itemCount = await queryBuilder.getCount(); const entities = await queryBuilder.getRawMany(); - const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); - return new PageResultsDto(entities, pageMetaDto); + + const pageMetaDto = new PageMeta({ + itemCount, + pageOptions: pageOptionsDto, + }); + return new PageResults({ data: entities, meta: pageMetaDto }); } public async searchPendingInteractionsWithContent( diff --git a/packages/backend/src/modules/portail-usagers/controllers/portail-usagers-manager/portail-usagers-manager.controller.ts b/packages/backend/src/modules/portail-usagers/controllers/portail-usagers-manager/portail-usagers-manager.controller.ts index 63f8175876..c0f26aa7f0 100644 --- a/packages/backend/src/modules/portail-usagers/controllers/portail-usagers-manager/portail-usagers-manager.controller.ts +++ b/packages/backend/src/modules/portail-usagers/controllers/portail-usagers-manager/portail-usagers-manager.controller.ts @@ -3,6 +3,8 @@ import { UsagersCountByStatus, UserUsager, UserUsagerWithUsagerInfo, + PageMeta, + PageResults, } from "@domifa/common"; import { Body, @@ -43,11 +45,7 @@ import { } from "../../dto"; import { AppLogsService } from "../../../app-logs/app-logs.service"; import { userUsagerCreator } from "../../services"; -import { - PageMetaDto, - PageOptionsDto, - PageResultsDto, -} from "../../../../usagers/dto"; +import { PageOptionsDto } from "../../../../usagers/dto"; import { format } from "date-fns"; import * as XLSX from "xlsx"; @@ -340,15 +338,19 @@ export class PortailUsagersManagerController { public async getAllAccounts( @Body() pageOptionsDto: PageOptionsDto, @CurrentUser() currentUser: UserStructureAuthenticated - ): Promise> { + ): Promise> { const { itemCount, entities } = await userUsagerRepository.getAccountsWithUsagerInfo( currentUser, pageOptionsDto, false ); - const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); - return new PageResultsDto(entities, pageMetaDto); + + const pageMetaDto = new PageMeta({ + itemCount, + pageOptions: pageOptionsDto, + }); + return new PageResults({ data: entities, meta: pageMetaDto }); } @UseGuards(UsagerAccessGuard) diff --git a/packages/backend/src/modules/sms/sms.controller.ts b/packages/backend/src/modules/sms/sms.controller.ts index bbd1b1df20..37d3c549b3 100644 --- a/packages/backend/src/modules/sms/sms.controller.ts +++ b/packages/backend/src/modules/sms/sms.controller.ts @@ -1,4 +1,4 @@ -import { Usager } from "@domifa/common"; +import { PageMeta, PageResults, Usager } from "@domifa/common"; import { Controller, UseGuards, Post, Body } from "@nestjs/common"; import { AuthGuard } from "@nestjs/passport"; import { ApiTags, ApiBearerAuth } from "@nestjs/swagger"; @@ -10,8 +10,7 @@ import { } from "../../auth/decorators"; import { AppUserGuard, UsagerAccessGuard } from "../../auth/guards"; import { messageSmsRepository } from "../../database"; -import { PageMetaDto, PageOptionsDto, PageResultsDto } from "../../usagers/dto"; -import { ObjectLiteral } from "typeorm"; +import { PageOptionsDto } from "../../usagers/dto"; @Controller("sms") @UseGuards(AuthGuard("jwt"), AppUserGuard) @@ -26,22 +25,27 @@ export class SmsController { @CurrentUsager() currentUsager: Usager, @Body() pageOptionsDto: PageOptionsDto ) { - const queryBuilder = messageSmsRepository.createQueryBuilder("message_sms"); - - const whereConditions: ObjectLiteral = { - structureId: currentUsager.structureId, - usagerRef: currentUsager.ref, - }; - - queryBuilder - .where(whereConditions) - .orderBy("id", pageOptionsDto.order) - .skip((pageOptionsDto.page - 1) * pageOptionsDto.take) + const queryBuilder = messageSmsRepository + .createQueryBuilder("message_sms") + .where("message_sms.structureId = :structureId", { + structureId: currentUsager.structureId, + }) + .andWhere("message_sms.usagerRef = :usagerRef", { + usagerRef: currentUsager.ref, + }) + .orderBy("createdAt", pageOptionsDto.order) + .skip(pageOptionsDto.skip) .take(pageOptionsDto.take); - const itemCount = await queryBuilder.getCount(); - const { entities } = await queryBuilder.getRawAndEntities(); - const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); - return new PageResultsDto(entities, pageMetaDto); + const [entities, itemCount] = await Promise.all([ + queryBuilder.getMany(), + queryBuilder.getCount(), + ]); + + const pageMetaDto = new PageMeta({ + itemCount, + pageOptions: pageOptionsDto, + }); + return new PageResults({ data: entities, meta: pageMetaDto }); } } diff --git a/packages/backend/src/usagers/controllers/usager-notes.controller.ts b/packages/backend/src/usagers/controllers/usager-notes.controller.ts index e51f3824d6..62a382096a 100644 --- a/packages/backend/src/usagers/controllers/usager-notes.controller.ts +++ b/packages/backend/src/usagers/controllers/usager-notes.controller.ts @@ -27,6 +27,8 @@ import { UsagerNote, Usager, UsagerPinnedNote, + PageMeta, + PageResults, } from "@domifa/common"; import { CreateNoteDto } from "../dto/create-note.dto"; import { @@ -37,12 +39,13 @@ import { } from "../../database"; import { CurrentUsagerNote } from "../../auth/decorators/current-usager-note.decorator"; import { AppUserGuard, UsagerNoteAccessGuard } from "../../auth/guards"; -import { PageResultsDto, PageMetaDto, PageOptionsDto } from "../dto/pagination"; + import { ObjectLiteral } from "typeorm"; import { AllowUserProfiles, AllowUserStructureRoles, } from "../../auth/decorators"; +import { PageOptionsDto } from "../dto"; @ApiTags("usagers-notes") @ApiBearerAuth() @@ -80,8 +83,11 @@ export class UsagerNotesController { const itemCount = await queryBuilder.getCount(); const { entities } = await queryBuilder.getRawAndEntities(); - const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto }); - return new PageResultsDto(entities, pageMetaDto); + const pageMetaDto = new PageMeta({ + itemCount, + pageOptions: pageOptionsDto, + }); + return new PageResults({ data: entities, meta: pageMetaDto }); } @UseGuards(UsagerAccessGuard) diff --git a/packages/backend/src/usagers/dto/pagination/index.ts b/packages/backend/src/usagers/dto/pagination/index.ts index 01894bbda7..9ad848091d 100644 --- a/packages/backend/src/usagers/dto/pagination/index.ts +++ b/packages/backend/src/usagers/dto/pagination/index.ts @@ -1,6 +1,2 @@ // @index('./*', f => `export * from '${f.path}'`) -export * from "./page-meta-parameters.dto"; -export * from "./page-meta.dto"; -export * from "./page-search.dto"; -export * from "./page-results.dto"; -export * from "./results-order.enum"; +export * from "./page-options.dto"; diff --git a/packages/backend/src/usagers/dto/pagination/page-meta-parameters.dto.ts b/packages/backend/src/usagers/dto/pagination/page-meta-parameters.dto.ts deleted file mode 100644 index c207dfaa30..0000000000 --- a/packages/backend/src/usagers/dto/pagination/page-meta-parameters.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PageOptionsDto } from "./page-search.dto"; - -export interface PageMetaDtoParameters { - pageOptionsDto: PageOptionsDto; - itemCount: number; -} diff --git a/packages/backend/src/usagers/dto/pagination/page-meta.dto.ts b/packages/backend/src/usagers/dto/pagination/page-meta.dto.ts deleted file mode 100644 index a57ea50338..0000000000 --- a/packages/backend/src/usagers/dto/pagination/page-meta.dto.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { PageMetaDtoParameters } from "./page-meta-parameters.dto"; - -export class PageMetaDto { - @ApiProperty() - readonly page: number; - - @ApiProperty() - readonly take: number; - - @ApiProperty() - readonly itemCount: number; - - @ApiProperty() - readonly pageCount: number; - - @ApiProperty() - readonly hasPreviousPage: boolean; - - @ApiProperty() - readonly hasNextPage: boolean; - - constructor({ pageOptionsDto, itemCount }: PageMetaDtoParameters) { - this.page = pageOptionsDto.page; - this.take = pageOptionsDto.take; - this.itemCount = itemCount; - this.pageCount = Math.ceil(this.itemCount / this.take); - this.hasPreviousPage = this.page > 1; - this.hasNextPage = this.page < this.pageCount; - } -} diff --git a/packages/backend/src/usagers/dto/pagination/page-search.dto.ts b/packages/backend/src/usagers/dto/pagination/page-options.dto.ts similarity index 83% rename from packages/backend/src/usagers/dto/pagination/page-search.dto.ts rename to packages/backend/src/usagers/dto/pagination/page-options.dto.ts index 11bf9126cf..e076592592 100644 --- a/packages/backend/src/usagers/dto/pagination/page-search.dto.ts +++ b/packages/backend/src/usagers/dto/pagination/page-options.dto.ts @@ -1,7 +1,7 @@ +import { Order } from "@domifa/common"; import { ApiPropertyOptional } from "@nestjs/swagger"; import { Type } from "class-transformer"; import { IsEnum, IsInt, IsNotEmpty, Max, Min } from "class-validator"; -import { Order } from "./results-order.enum"; export class PageOptionsDto { @ApiPropertyOptional({ enum: Order, default: Order.ASC }) @@ -21,7 +21,7 @@ export class PageOptionsDto { @ApiPropertyOptional({ minimum: 1, - maximum: 50, + maximum: 500, default: 10, }) @Type(() => Number) @@ -30,4 +30,8 @@ export class PageOptionsDto { @Max(500) @IsNotEmpty() readonly take: number = 10; + + get skip(): number { + return (this.page - 1) * this.take; + } } diff --git a/packages/backend/src/usagers/dto/pagination/page-results.dto.ts b/packages/backend/src/usagers/dto/pagination/page-results.dto.ts deleted file mode 100644 index 50d7f64a36..0000000000 --- a/packages/backend/src/usagers/dto/pagination/page-results.dto.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ApiProperty } from "@nestjs/swagger"; -import { IsArray } from "class-validator"; -import { PageMetaDto } from "./page-meta.dto"; - -export class PageResultsDto { - @IsArray() - @ApiProperty({ isArray: true }) - readonly data: T[]; - - @ApiProperty({ type: () => PageMetaDto }) - readonly meta: PageMetaDto; - - constructor(data: T[], meta: PageMetaDto) { - this.data = data; - this.meta = meta; - } -} diff --git a/packages/backend/src/usagers/dto/pagination/results-order.enum.ts b/packages/backend/src/usagers/dto/pagination/results-order.enum.ts deleted file mode 100644 index 78a3fa76da..0000000000 --- a/packages/backend/src/usagers/dto/pagination/results-order.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Order { - ASC = "ASC", - DESC = "DESC", -} diff --git a/packages/common/src/pagination/PageItem.interface.ts b/packages/common/src/pagination/PageItem.interface.ts new file mode 100644 index 0000000000..f923e3bf6e --- /dev/null +++ b/packages/common/src/pagination/PageItem.interface.ts @@ -0,0 +1,6 @@ +import { PageOptions } from "./PageOptions.class"; + +export interface PageMetaParams { + pageOptions: PageOptions; + itemCount: number; +} diff --git a/packages/common/src/pagination/PageMeta.class.ts b/packages/common/src/pagination/PageMeta.class.ts new file mode 100644 index 0000000000..4c98eadec3 --- /dev/null +++ b/packages/common/src/pagination/PageMeta.class.ts @@ -0,0 +1,30 @@ +import { PageMetaParams } from "./PageItem.interface"; + +export class PageMeta { + public page: number; + public take: number; + public itemCount: number; + public pageCount: number; + public hasPreviousPage: boolean; + public hasNextPage: boolean; + + constructor(params?: PageMetaParams) { + if (params) { + const { itemCount, pageOptions } = params; + + this.page = pageOptions?.page; + this.take = pageOptions?.take; + this.itemCount = itemCount; + this.pageCount = Math.ceil(itemCount / pageOptions.take); + this.hasPreviousPage = pageOptions.page > 1; + this.hasNextPage = pageOptions.page < this.pageCount; + } else { + this.page = 1; + this.take = 10; + this.itemCount = 0; + this.pageCount = 0; + this.hasPreviousPage = false; + this.hasNextPage = false; + } + } +} diff --git a/packages/common/src/pagination/PageMeta.interface.ts b/packages/common/src/pagination/PageMeta.interface.ts deleted file mode 100644 index 0c4c1e25a8..0000000000 --- a/packages/common/src/pagination/PageMeta.interface.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface PageMeta { - page: number; - take: number; - itemCount: number; - pageCount: number; - hasPreviousPage: boolean; - hasNextPage: boolean; -} diff --git a/packages/common/src/pagination/PageResults.class.ts b/packages/common/src/pagination/PageResults.class.ts index e4d0b1751b..a8c029d340 100644 --- a/packages/common/src/pagination/PageResults.class.ts +++ b/packages/common/src/pagination/PageResults.class.ts @@ -1,4 +1,4 @@ -import { type PageMeta } from "./PageMeta.interface"; +import { PageMeta } from "./PageMeta.class"; export class PageResults { public data: T[]; @@ -6,13 +6,6 @@ export class PageResults { constructor(results?: Partial>) { this.data = results?.data ?? []; - this.meta = results?.meta ?? { - page: 1, - take: 5, - itemCount: 0, - pageCount: 0, - hasPreviousPage: false, - hasNextPage: false, - }; + this.meta = results?.meta ?? new PageMeta(); } } diff --git a/packages/common/src/pagination/index.ts b/packages/common/src/pagination/index.ts index f36ecafb5d..8ddc98b597 100644 --- a/packages/common/src/pagination/index.ts +++ b/packages/common/src/pagination/index.ts @@ -1,5 +1,6 @@ // @index('./*', f => `export * from '${f.path}'`) -export * from "./PageMeta.interface"; +export * from "./PageItem.interface"; +export * from "./PageMeta.class"; export * from "./PageOptions.class"; export * from "./PageOrder.enum"; export * from "./PageResults.class";