diff --git a/.env.sample b/.env.sample index 34cf461c..f2c7a6e4 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,9 @@ -MONGODB_URL=mongodb://localhost:27017 -MONGODB_DBNAME=api-bal + +POSTGRES_URL= +REDIS_URL= + PORT=5000 + SMTP_HOST= SMTP_PORT=587 SMTP_USER= @@ -8,8 +11,9 @@ SMTP_PASS= SMTP_SECURE= SMTP_FROM= SMTP_BCC= -SHOW_EMAILS=YES + EDITOR_URL_PATTERN=https://mes-adresses.data.gouv.fr/bal// + API_URL=http://localhost:5000 API_DEPOT_URL=https://plateforme-bal.adresse.data.gouv.fr/api-depot API_DEPOT_CLIENT_ID= diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index a41151a0..730606fb 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [16.x, 18.x, 20.x, 22.x] + node-version: [18.x, 20.x, 22.x] steps: - uses: actions/checkout@v3 @@ -50,8 +50,8 @@ jobs: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - name: Install dependencies run: yarn - - name: Test and coverage - run: yarn jest --coverage + # - name: Test and coverage + # run: yarn jest --coverage - name: SonarCloud Scan uses: SonarSource/sonarcloud-github-action@master env: diff --git a/Procfile b/Procfile new file mode 100644 index 00000000..07eda3f3 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +clock: yarn start:cron diff --git a/README.md b/README.md index 382b2ff3..40e8223e 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,18 @@ https://adresse-data-gouv-fr.gitbook.io/bal/mes-adresses ## Pré-requis -- [Node.js](https://nodejs.org) 16+ +- [Node.js](https://nodejs.org) 18+ - [yarn](https://www.yarnpkg.com) -- [MongoDB](https://www.mongodb.com) 4+ +- [PostgresSQL](https://www.postgresql.org/) +- [Redis](https://redis.io/fr/) + +### Postgres + +Le base de donnée postgres doit avoir l'extension postgis d'installé + +``` +CREATE EXTENSION postgis +``` ## Utilisation @@ -47,7 +56,6 @@ Lancer le cron de développement : $ yarn dev:cron ``` - ### Production Créer une version de production : @@ -98,17 +106,17 @@ Cette application utilise des variables d'environnement pour sa configuration. Elles peuvent être définies classiquement ou en créant un fichier `.env` sur la base du modèle `.env.sample`. | Nom de la variable | Description | -| --------------------------| --------------------------------------------------------------------------- | -| `MONGODB_URL` | Paramètre de connexion à MongoDB | -| `MONGODB_DBNAME` | Nom de la base de données à utiliser | +| ------------------------- | --------------------------------------------------------------------------- | +| `POSTGRES_URL` | Url de connection a la db postgres | +| `REDIS_URL` | Url de connection a la db redis | | `PORT` | Port à utiliser pour l'API | -| `SHOW_EMAILS` | Indique si les courriels doivent être affichés dans les logs (`YES`) | | `API_URL` | URL de base de l’API | | `API_DEPOT_URL` | URL de l'api-depot | | `API_DEPOT_CLIENT_ID` | Id du client de l'api-depot | | `API_DEPOT_CLIENT_SECRET` | Token du client de l'api-depot | | `EDITOR_URL_PATTERN` | Pattern permettant de construire l'URL vers l'édition d'une BAL | -|---|---| +| `BAN_API_URL` | URL de ban-plateform | +| --- | --- | | `SMTP_HOST` | Nom d'hôte du serveur SMTP | | `SMTP_PORT` | Port du serveur SMTP | | `SMTP_USER` | Nom d'utilisateur pour se connecter au serveur SMTP | diff --git a/apps/api/src/api.module.ts b/apps/api/src/api.module.ts index 8f9c41e7..09b363ac 100644 --- a/apps/api/src/api.module.ts +++ b/apps/api/src/api.module.ts @@ -1,31 +1,41 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { Module } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; +import { Initialization1725371358514 } from 'migrations/1725371358514-initialization'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Position } from '@/shared/entities/position.entity'; + import { NumeroModule } from './modules/numeros/numero.module'; import { BaseLocaleModule } from './modules/base_locale/base_locale.module'; import { VoieModule } from './modules/voie/voie.module'; import { ToponymeModule } from './modules/toponyme/toponyme.module'; import { StatsModule } from './modules/stats/stats.module'; -import { AdminModule } from './modules/admin/admin.module'; -import { CronModule } from 'apps/cron/src/cron.module'; import { MailerModule } from '@nestjs-modules/mailer'; import { MailerParams } from '@/shared/params/mailer.params'; @Module({ imports: [ + ConfigModule.forRoot(), ServeStaticModule.forRoot({ rootPath: join(__dirname, '../../../'), renderPath: 'public/', }), - ConfigModule.forRoot(), - MongooseModule.forRootAsync({ + TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (config: ConfigService) => ({ - uri: config.get('MONGODB_URL'), - dbName: config.get('MONGODB_DBNAME'), + type: 'postgres', + url: config.get('POSTGRES_URL'), + keepConnectionAlive: true, + schema: 'public', + migrationsRun: true, + migrations: [Initialization1725371358514], + entities: [BaseLocale, Voie, Numero, Toponyme, Position], }), inject: [ConfigService], }), @@ -35,9 +45,6 @@ import { MailerParams } from '@/shared/params/mailer.params'; VoieModule, ToponymeModule, StatsModule, - AdminModule, - // We run the cron module in the api module when deployed on Scalingo - ...(process.env.RUN_CRON_IN_API === 'true' ? [CronModule] : []), ], controllers: [], providers: [], diff --git a/apps/api/src/lib/types/request.type.ts b/apps/api/src/lib/types/request.type.ts index 9bdc55d7..249b65fd 100644 --- a/apps/api/src/lib/types/request.type.ts +++ b/apps/api/src/lib/types/request.type.ts @@ -1,9 +1,9 @@ import { Request } from 'express'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; export interface CustomRequest extends Request { token?: string; diff --git a/apps/api/src/lib/types/validator.types.ts b/apps/api/src/lib/types/validator.types.ts new file mode 100644 index 00000000..4e2fb118 --- /dev/null +++ b/apps/api/src/lib/types/validator.types.ts @@ -0,0 +1,72 @@ +export enum LevelEnum { + INFO = 'I', + WARNING = 'W', + ERROR = 'E', +} + +export interface Field { + name: string; + schemaName: string; +} + +export interface FieldNotFound { + schemaVersion: string; + schemaName: string; + level: LevelEnum; +} + +export interface Row { + rawValues: any; + parsedValues: any; + additionalValues: any; + localizedValues: any; + errors: any[]; + line: number; + isValid: boolean; +} + +export interface FieldFileValidation { + value: string; + isValid: boolean; +} + +export interface FileValidation { + encoding: FieldFileValidation; + delimiter: FieldFileValidation; + linebreak: FieldFileValidation; +} +export interface ProfileValidation { + code: string; + name: string; + isValid: boolean; +} + +export interface ProfileError { + code: string; + level: LevelEnum; +} + +export interface ParseError { + type: string; + code: string; + message: string; + row: number; +} + +export interface ValidationBal { + encoding: string; + linebreak: string; + delimiter: string; + originalFields: string[]; + parseOk: boolean; + parseErrors: ParseError[]; + fields: Field[]; + notFoundFields: FieldNotFound[]; + rows: Row[]; + fileValidation: FileValidation; + profilesValidation: Record; + globalErrors: string[]; + rowsErrors: string[]; + uniqueErrors: string[]; + profilErrors: ProfileError[]; +} diff --git a/apps/api/src/lib/utils/csv.utils.ts b/apps/api/src/lib/utils/csv.utils.ts index f5568e2e..41de0c52 100644 --- a/apps/api/src/lib/utils/csv.utils.ts +++ b/apps/api/src/lib/utils/csv.utils.ts @@ -3,16 +3,23 @@ import { normalize } from '@ban-team/adresses-util/lib/voies'; import { chain, compact, keyBy, min, max } from 'lodash'; import { beautifyUppercased, beautifyNomAlt } from './string.utils'; + +import { PositionTypeEnum } from '@/shared/entities/position.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Row } from '../types/validator.types'; import { ObjectId } from 'mongodb'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; -import { Position } from '@/shared/schemas/position.schema'; - -interface Row { - parsedValues: Record; - additionalValues: any; - localizedValues: any; - isValid: boolean; -} + +export type FromCsvType = { + isValid?: boolean; + validationError?: string; + accepted?: number; + rejected?: number; + voies?: Partial[]; + numeros?: Partial[]; + toponymes?: Partial[]; +}; export function extractIdBanAdresse({ parsedValues, @@ -45,7 +52,7 @@ export function extractCodeCommune({ ); } -export function extractPosition(row: Row): Position { +function extractPosition(row: any) { return { source: row.parsedValues.source || null, type: row.parsedValues.position || PositionTypeEnum.INCONNUE, @@ -56,66 +63,67 @@ export function extractPosition(row: Row): Position { }; } -export function extractPositions(rows: Row[]): Position[] { +function extractPositions(rows: any) { return rows .filter((r) => r.parsedValues.long && r.parsedValues.lat) .map((r) => extractPosition(r)); } -export function extractDate(row: Row): Date | null { +function extractDate(row: any) { if (row.parsedValues.date_der_maj) { return new Date(row.parsedValues.date_der_maj); } } -export function extractData(rows: Row[], codeCommune: string) { - const toponymes = chain(rows) - .filter((r: Row) => r.parsedValues.numero === 99999) - .groupBy((r: Row) => normalize(r.parsedValues.voie_nom)) - .map((toponymeRows: Row[]) => { +function extractData(rows: Row[]): { + voies: Partial[]; + numeros: Partial[]; + toponymes: Partial[]; +} { + const toponymes: Partial[] = chain(rows) + .filter((r) => r.parsedValues.numero === 99999) + .groupBy((r) => normalize(r.parsedValues.voie_nom)) + .map((toponymeRows) => { const date = extractDate(toponymeRows[0]) || new Date(); - return { - _id: new ObjectId(), + id: new ObjectId().toHexString(), banId: extractIdBanToponyme(toponymeRows[0]), - commune: codeCommune, nom: beautifyUppercased(toponymeRows[0].parsedValues.voie_nom), nomAlt: toponymeRows[0].localizedValues.voie_nom ? beautifyNomAlt(toponymeRows[0].localizedValues.voie_nom) : null, positions: extractPositions(toponymeRows), - _created: date, - _updated: date, + createdAt: date, + updatedAt: date, }; }) .value(); const toponymesIndex = keyBy(toponymes, (t) => normalize(t.nom)); - const voies = chain(rows) - .filter((r: Row) => r.parsedValues.numero !== 99999) - .groupBy((r: Row) => normalize(r.parsedValues.voie_nom)) - .map((voieRows: Row[]) => { + const voies: Partial[] = chain(rows) + .filter((r) => r.parsedValues.numero !== 99999) + .groupBy((r) => normalize(r.parsedValues.voie_nom)) + .map((voieRows) => { const dates = compact(voieRows.map((r) => r.parsedValues.date_der_maj)); return { - _id: new ObjectId(), + id: new ObjectId().toHexString(), banId: extractIdBanToponyme(voieRows[0]), - commune: codeCommune, nom: beautifyUppercased(voieRows[0].parsedValues.voie_nom), nomAlt: voieRows[0].localizedValues.voie_nom ? beautifyNomAlt(voieRows[0].localizedValues.voie_nom) : null, - _created: dates.length > 0 ? new Date(min(dates)) : new Date(), - _updated: dates.length > 0 ? new Date(max(dates)) : new Date(), + createdAt: dates.length > 0 ? new Date(min(dates)) : new Date(), + updatedAt: dates.length > 0 ? new Date(max(dates)) : new Date(), }; }) .value(); const voiesIndex = keyBy(voies, (v) => normalize(v.nom)); - const numeros = chain(rows) - .filter((r: Row) => r.parsedValues.numero !== 99999) + const numeros: Partial[] = chain(rows) + .filter((r) => r.parsedValues.numero !== 99999) .groupBy( (r: Row) => `${r.parsedValues.numero}@@@${r.parsedValues.suffixe}@@@${normalize( @@ -134,18 +142,17 @@ export function extractData(rows: Row[], codeCommune: string) { : null; if (toponymeString && !(toponymeString in toponymesIndex)) { - const toponyme = { - _id: new ObjectId(), + const toponyme: Partial = { + id: new ObjectId().toHexString(), banId: extractIdBanToponyme(numeroRows[0]), - commune: codeCommune, nom: beautifyUppercased( numeroRows[0].parsedValues.lieudit_complement_nom, ), nomAlt: toponymeAlt, positions: [], parcelles: numeroRows[0].parsedValues.cad_parcelles, - _created: new Date(), - _updated: new Date(), + createdAt: new Date(), + updatedAt: new Date(), }; toponymes.push(toponyme); @@ -153,18 +160,17 @@ export function extractData(rows: Row[], codeCommune: string) { } return { - _id: new ObjectId(), + id: new ObjectId().toHexString(), banId: extractIdBanAdresse(numeroRows[0]), - commune: codeCommune, - voie: voiesIndex[voieString]._id, - toponyme: toponymeString ? toponymesIndex[toponymeString]._id : null, + voieId: voiesIndex[voieString].id, + toponymeId: toponymeString ? toponymesIndex[toponymeString].id : null, numero: numeroRows[0].parsedValues.numero, suffixe: numeroRows[0].parsedValues.suffixe || null, certifie: numeroRows[0].parsedValues.certification_commune, positions: extractPositions(numeroRows), parcelles: numeroRows[0].parsedValues.cad_parcelles, - _created: date, - _updated: date, + createdAt: date, + updatedAt: date, }; }) .value(); @@ -172,7 +178,10 @@ export function extractData(rows: Row[], codeCommune: string) { return { voies, numeros, toponymes }; } -export async function extractFromCsv(file: Buffer, codeCommune: string) { +export async function extractFromCsv( + file: Buffer, + codeCommune: string, +): Promise { try { const { rows, @@ -190,8 +199,7 @@ export async function extractFromCsv(file: Buffer, codeCommune: string) { const rejected: Row[] = rows.filter(({ isValid }) => !isValid); const communesData = extractData( - accepted.filter((r: Row) => extractCodeCommune(r) === codeCommune), - codeCommune, + accepted.filter((r) => extractCodeCommune(r) === codeCommune), ); return { @@ -203,7 +211,7 @@ export async function extractFromCsv(file: Buffer, codeCommune: string) { toponymes: communesData.toponymes, }; } catch (error) { - console.log(error); + console.error(error); return { isValid: false, validationError: error.message }; } } diff --git a/apps/api/src/lib/utils/is-admin.utils.ts b/apps/api/src/lib/utils/is-admin.utils.ts index ff62e3e7..40272742 100644 --- a/apps/api/src/lib/utils/is-admin.utils.ts +++ b/apps/api/src/lib/utils/is-admin.utils.ts @@ -1,5 +1,5 @@ import { CustomRequest } from '@/lib/types/request.type'; -import { BaseLocale } from '../../../../../libs/shared/src/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; export function isSuperAdmin(req: CustomRequest) { return req.headers.authorization === `Bearer ${process.env.ADMIN_TOKEN}`; diff --git a/apps/api/src/lib/utils/memory-usage.utils.ts b/apps/api/src/lib/utils/memory-usage.utils.ts deleted file mode 100644 index 0a2a1d99..00000000 --- a/apps/api/src/lib/utils/memory-usage.utils.ts +++ /dev/null @@ -1,21 +0,0 @@ -export const printMemoryUsage = () => { - const formatMemoryUsage = (data) => - `${Math.round((data / 1024 / 1024) * 100) / 100} MB`; - - const memoryData = process.memoryUsage(); - - const memoryUsage = { - rss: `${formatMemoryUsage( - memoryData.rss, - )} -> Resident Set Size - total memory allocated for the process execution`, - heapTotal: `${formatMemoryUsage( - memoryData.heapTotal, - )} -> total size of the allocated heap`, - heapUsed: `${formatMemoryUsage( - memoryData.heapUsed, - )} -> actual memory used during the execution`, - external: `${formatMemoryUsage(memoryData.external)} -> V8 external memory`, - }; - - console.log(memoryUsage); -}; diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index 998907ab..d78a7f59 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,7 +1,6 @@ import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; -import { printMemoryUsage } from './lib/utils/memory-usage.utils'; import { ApiModule } from './api.module'; @@ -31,10 +30,6 @@ async function bootstrap() { const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); - if (process.env.PRINT_MEMORY_USAGE === 'true') { - setInterval(printMemoryUsage, 1000); - } - await app.listen(process.env.PORT || 5000); } bootstrap(); diff --git a/apps/api/src/modules/admin/admin.controller.ts b/apps/api/src/modules/admin/admin.controller.ts deleted file mode 100644 index 34eff363..00000000 --- a/apps/api/src/modules/admin/admin.controller.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { - Body, - Controller, - Get, - HttpStatus, - Post, - Res, - UseGuards, -} from '@nestjs/common'; -import { - ApiBody, - ApiResponse, - ApiTags, - ApiOperation, - ApiBearerAuth, -} from '@nestjs/swagger'; -import { Response } from 'express'; -import * as csvWriter from 'csv-write-stream'; -import * as getStream from 'get-stream'; -import * as intoStream from 'into-stream'; -import * as pumpify from 'pumpify'; - -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; - -import { FusionCommunesDTO } from './dto/fusion_bases_locales.dto'; -import { AdminService } from './admin.service'; -import { SuperAdminGuard } from '@/lib/guards/admin.guard'; -import { BaseLocaleService } from '../base_locale/base_locale.service'; - -@ApiTags('admin') -@Controller('admin') -export class AdminController { - constructor( - private adminService: AdminService, - private baseLocaleService: BaseLocaleService, - ) {} - - @Post('/fusion-communes') - @ApiOperation({ - summary: 'Fusion communes', - operationId: 'fusionCommunes', - }) - @ApiBody({ type: FusionCommunesDTO, required: true }) - @ApiResponse({ status: HttpStatus.OK, type: BaseLocale, isArray: true }) - @ApiBearerAuth('admin-token') - @UseGuards(SuperAdminGuard) - async fusionCommunes( - @Body() fusionCommunesDTO: FusionCommunesDTO, - @Res() res: Response, - ) { - const result: BaseLocale = - await this.adminService.fusionCommunes(fusionCommunesDTO); - - res.status(HttpStatus.OK).json(result); - } - - @Get('/emails.csv') - @ApiOperation({ - summary: 'download email.csv', - operationId: 'downloadEmailCsv', - }) - @ApiBearerAuth('admin-token') - @UseGuards(SuperAdminGuard) - async downloadEmailCsv(@Res() res: Response) { - const emails: string[] = - await this.baseLocaleService.findDistinct('emails'); - - const csvFile = await getStream( - pumpify.obj( - intoStream.object(emails.filter(Boolean).map((email) => ({ email }))), - csvWriter(), - ), - ); - - res - .status(HttpStatus.OK) - .attachment('emails.csv') - .type('csv') - .send(csvFile); - } -} diff --git a/apps/api/src/modules/admin/admin.module.ts b/apps/api/src/modules/admin/admin.module.ts deleted file mode 100644 index 414f9c28..00000000 --- a/apps/api/src/modules/admin/admin.module.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Module, forwardRef } from '@nestjs/common'; - -import { AdminController } from '@/modules/admin/admin.controller'; -import { AdminService } from '@/modules/admin/admin.service'; -import { BaseLocaleModule } from '@/modules/base_locale/base_locale.module'; -import { PopulateModule } from '@/modules/base_locale/sub_modules/populate/populate.module'; - -@Module({ - imports: [ - forwardRef(() => BaseLocaleModule), - forwardRef(() => PopulateModule), - ], - providers: [AdminService], - controllers: [AdminController], -}) -export class AdminModule {} diff --git a/apps/api/src/modules/admin/admin.service.ts b/apps/api/src/modules/admin/admin.service.ts deleted file mode 100644 index ca758dec..00000000 --- a/apps/api/src/modules/admin/admin.service.ts +++ /dev/null @@ -1,116 +0,0 @@ -import { Injectable, Inject, forwardRef } from '@nestjs/common'; -import { ObjectId } from 'bson'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; - -import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { FusionCommunesDTO } from './dto/fusion_bases_locales.dto'; -import { PopulateService } from '../base_locale/sub_modules/populate/populate.service'; - -@Injectable() -export class AdminService { - constructor( - @Inject(forwardRef(() => PopulateService)) - private populateService: PopulateService, - @Inject(forwardRef(() => BaseLocaleService)) - private baseLocaleService: BaseLocaleService, - ) {} - - private replaceBalIds( - bal: BaseLocale, - voies: Voie[], - toponymes: Toponyme[], - numeros: Numero[], - ) { - const idVoies = {}; - const idToponymes = {}; - - for (const voie of voies) { - voie._bal = bal._id; - const newId = new ObjectId(); - idVoies[voie._id.toHexString()] = newId; - voie._id = newId; - } - - for (const toponyme of toponymes) { - toponyme._bal = bal._id; - const newId = new ObjectId(); - idToponymes[toponyme._id.toHexString()] = newId; - toponyme._id = newId; - } - - for (const numero of numeros) { - numero._bal = bal._id; - numero._id = new ObjectId(); - numero.voie = idVoies[numero.voie.toHexString()]; - numero.toponyme = numero.toponyme - ? idToponymes[numero.toponyme.toHexString()] - : null; - } - } - - private replaceBalCommune( - bal: BaseLocale, - voies: Voie[], - toponymes: Toponyme[], - numeros: Numero[], - ) { - for (const voie of voies) { - voie.commune = bal.commune; - } - - for (const toponyme of toponymes) { - toponyme.commune = bal.commune; - } - - for (const numero of numeros) { - numero.commune = bal.commune; - } - } - - public async fusionCommunes({ - codeCommune, - nom, - emails, - communes, - }: FusionCommunesDTO): Promise { - const newbaseLocale: BaseLocale = await this.baseLocaleService.createOne( - { commune: codeCommune, nom, emails }, - false, - ); - for (const { codeCommune, balId } of communes) { - if (balId) { - const { numeros, voies, toponymes } = - await this.baseLocaleService.findMetas(balId); - this.replaceBalIds(newbaseLocale, voies, toponymes, numeros); - this.replaceBalCommune(newbaseLocale, voies, toponymes, numeros); - await this.baseLocaleService.populate( - newbaseLocale, - { - numeros, - voies, - toponymes, - }, - false, - ); - } else { - const { numeros, voies, toponymes } = - await this.populateService.extract(codeCommune); - this.replaceBalCommune(newbaseLocale, voies, toponymes, numeros); - await this.baseLocaleService.populate( - newbaseLocale, - { - numeros, - voies, - toponymes, - }, - false, - ); - } - } - - return newbaseLocale; - } -} diff --git a/apps/api/src/modules/admin/dto/bases_locales_status.dto.ts b/apps/api/src/modules/admin/dto/bases_locales_status.dto.ts deleted file mode 100644 index 9fd349da..00000000 --- a/apps/api/src/modules/admin/dto/bases_locales_status.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Types } from 'mongoose'; - -export class BasesLocalesStatusDTO { - @ApiProperty({ type: String }) - status: Types.ObjectId; - - @ApiProperty() - count: number; -} diff --git a/apps/api/src/modules/admin/dto/code_commune.dto.ts b/apps/api/src/modules/admin/dto/code_commune.dto.ts deleted file mode 100644 index c2ca22a8..00000000 --- a/apps/api/src/modules/admin/dto/code_commune.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { ArrayNotEmpty, IsString } from 'class-validator'; - -export class CodeCommuneDTO { - @ApiProperty({ required: true, nullable: false }) - @ArrayNotEmpty() - @IsString({ each: true }) - codeCommunes: Array; -} diff --git a/apps/api/src/modules/admin/dto/fusion_bases_locales.dto.ts b/apps/api/src/modules/admin/dto/fusion_bases_locales.dto.ts deleted file mode 100644 index 9ae649ea..00000000 --- a/apps/api/src/modules/admin/dto/fusion_bases_locales.dto.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Type } from 'class-transformer'; -import { - ArrayNotEmpty, - IsMongoId, - IsOptional, - IsString, - ValidateNested, - IsEmail, -} from 'class-validator'; - -export class CommuneBalDTO { - @ApiProperty({ required: true, nullable: false }) - @IsString() - codeCommune: string; - - @ApiProperty({ required: false, nullable: true }) - @IsOptional() - @IsMongoId() - @IsString() - balId?: string; -} - -export class FusionCommunesDTO { - @ApiProperty({ required: true, nullable: false }) - @IsString() - codeCommune: string; - - @ApiProperty({ required: true, nullable: false }) - @IsString() - nom: string; - - @ApiProperty({ required: true, nullable: false }) - @ArrayNotEmpty() - @IsEmail({}, { each: true }) - emails: string[]; - - @ApiProperty({ - type: () => CommuneBalDTO, - isArray: true, - required: false, - nullable: false, - }) - @ValidateNested({ each: true }) - @ArrayNotEmpty() - @Type(() => CommuneBalDTO) - communes: CommuneBalDTO[]; -} diff --git a/apps/api/src/modules/base_locale/base_locale.controller.ts b/apps/api/src/modules/base_locale/base_locale.controller.ts index 5b5f6c07..51223e6a 100644 --- a/apps/api/src/modules/base_locale/base_locale.controller.ts +++ b/apps/api/src/modules/base_locale/base_locale.controller.ts @@ -28,10 +28,11 @@ import { ApiResponse, ApiTags, } from '@nestjs/swagger'; -import { Request, Response } from 'express'; +import { Response } from 'express'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { Voie } from '@/shared/entities/voie.entity'; import { PublicationService } from '@/shared/modules/publication/publication.service'; import { getEditorUrl } from '@/shared/utils/mailer.utils'; @@ -52,7 +53,6 @@ import { ExtendedBaseLocaleDTO } from './dto/extended_base_locale.dto'; import { ExtendedVoieDTO } from '../voie/dto/extended_voie.dto'; import { UpdateBaseLocaleDTO } from './dto/update_base_locale.dto'; import { UpdateBaseLocaleDemoDTO } from './dto/update_base_locale_demo.dto'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; import { CreateDemoBaseLocaleDTO } from './dto/create_demo_base_locale.dto'; import { PageBaseLocaleDTO } from './dto/page_base_locale.dto'; import { SearchBaseLocalQuery } from './dto/search_base_locale.query'; @@ -89,7 +89,6 @@ export class BaseLocaleController { @ApiBody({ type: CreateBaseLocaleDTO, required: true }) @ApiResponse({ status: HttpStatus.OK, type: BaseLocale }) async createBaseLocale( - @Req() req: Request, @Body() createBaseLocaleDTO: CreateBaseLocaleDTO, @Res() res: Response, ) { @@ -107,7 +106,6 @@ export class BaseLocaleController { @ApiBody({ type: CreateDemoBaseLocaleDTO, required: true }) @ApiResponse({ status: HttpStatus.OK, type: BaseLocale }) async createDemoBaseLocale( - @Req() req: Request, @Body() createDemoBaseLocaleDTO: CreateDemoBaseLocaleDTO, @Res() res: Response, ) { @@ -131,12 +129,13 @@ export class BaseLocaleController { @ApiBearerAuth('admin-token') async searchBaseLocale( @Req() req: CustomRequest, - @Query(SearchQueryPipe) { filters, limit, offset }: SearchQueryTransformed, + @Query(SearchQueryPipe) + { filters, email, limit, offset }: SearchQueryTransformed, @Res() res: Response, ) { - const basesLocales: BaseLocale[] = await this.baseLocaleService.findMany( + const basesLocales: BaseLocale[] = await this.baseLocaleService.searchMany( filters, - null, + email, limit, offset, ); @@ -181,9 +180,9 @@ export class BaseLocaleController { @Query('isExist', new ParseBoolPipe({ optional: true })) isExist: boolean, @Res() res: Response, ) { - if (isExist && req.baseLocale._deleted) { + if (isExist && req.baseLocale.deletedAt) { throw new HttpException( - `BaseLocale ${req.baseLocale._id} est supprimé`, + `BaseLocale ${req.baseLocale.id} est supprimé`, HttpStatus.NOT_FOUND, ); } @@ -315,7 +314,7 @@ export class BaseLocaleController { ) { await this.baseLocaleService.recoverAccess(recoverBaseLocaleDTO); - res.status(HttpStatus.NO_CONTENT).json(true); + res.sendStatus(HttpStatus.NO_CONTENT); } @Get(':baseLocaleId/:token/recovery') @@ -331,11 +330,9 @@ export class BaseLocaleController { return res.sendStatus(403); } - const restoredBaseLocale = await this.baseLocaleService.recovery( - req.baseLocale, - ); + await this.baseLocaleService.restore(req.baseLocale); - const editorUrl = getEditorUrl(restoredBaseLocale); + const editorUrl = getEditorUrl(req.baseLocale); res.redirect(307, editorUrl); } @@ -403,7 +400,7 @@ export class BaseLocaleController { @ApiBearerAuth('admin-token') @UseGuards(AdminGuard) async publishBaseLocale(@Req() req: CustomRequest, @Res() res: Response) { - const baseLocale = await this.publicationService.exec(req.baseLocale._id, { + const baseLocale = await this.publicationService.exec(req.baseLocale.id, { force: true, }); res.status(HttpStatus.OK).json(baseLocale); @@ -425,7 +422,7 @@ export class BaseLocaleController { HttpStatus.PRECONDITION_FAILED, ); } - await this.publicationService.pause(req.baseLocale._id); + await this.publicationService.pause(req.baseLocale.id); res.status(HttpStatus.OK).json(true); } @@ -445,7 +442,7 @@ export class BaseLocaleController { HttpStatus.PRECONDITION_FAILED, ); } - await this.publicationService.resume(req.baseLocale._id); + await this.publicationService.resume(req.baseLocale.id); res.status(HttpStatus.OK).json(true); } @@ -531,12 +528,11 @@ export class BaseLocaleController { @Body() deleteBatchNumeroDto: DeleteBatchNumeroDTO, @Res() res: Response, ) { - const result: BatchNumeroResponseDTO = - await this.numeroService.softDeleteBatch( - req.baseLocale, - deleteBatchNumeroDto, - ); - res.status(HttpStatus.OK).json(result); + await this.numeroService.softDeleteBatch( + req.baseLocale, + deleteBatchNumeroDto, + ); + res.sendStatus(HttpStatus.NO_CONTENT); } @Delete(':baseLocaleId/numeros/batch') @@ -572,8 +568,7 @@ export class BaseLocaleController { @ApiBearerAuth('admin-token') async findVoieByBal(@Req() req: CustomRequest, @Res() res: Response) { const voies: Voie[] = await this.voieService.findMany({ - _bal: req.baseLocale._id, - _deleted: null, + balId: req.baseLocale.id, }); const extendedVoie: ExtendedVoieDTO[] = await this.voieService.extendVoies(voies); @@ -613,8 +608,7 @@ export class BaseLocaleController { @ApiBearerAuth('admin-token') async findToponymeByBal(@Req() req: CustomRequest, @Res() res: Response) { const toponymes: Toponyme[] = await this.toponymeService.findMany({ - _bal: req.baseLocale._id, - _deleted: null, + balId: req.baseLocale.id, }); const extendedToponyme: ExtentedToponymeDTO[] = await this.toponymeService.extendToponymes(toponymes); diff --git a/apps/api/src/modules/base_locale/base_locale.middleware.ts b/apps/api/src/modules/base_locale/base_locale.middleware.ts index b6fb010f..72482958 100644 --- a/apps/api/src/modules/base_locale/base_locale.middleware.ts +++ b/apps/api/src/modules/base_locale/base_locale.middleware.ts @@ -1,10 +1,11 @@ import { Injectable, NestMiddleware } from '@nestjs/common'; import { Response, NextFunction } from 'express'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { CustomRequest } from '@/lib/types/request.type'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; import { isAdmin } from '@/lib/utils/is-admin.utils'; +import { ObjectId } from 'mongodb'; @Injectable() export class BaseLocaleMiddleware implements NestMiddleware { @@ -12,7 +13,8 @@ export class BaseLocaleMiddleware implements NestMiddleware { async use(req: CustomRequest, res: Response, next: NextFunction) { const { baseLocaleId } = req.params; - if (baseLocaleId) { + + if (ObjectId.isValid(baseLocaleId)) { const basesLocale: BaseLocale = await this.baseLocaleService.findOneOrFail(baseLocaleId); req.baseLocale = basesLocale; diff --git a/apps/api/src/modules/base_locale/base_locale.module.ts b/apps/api/src/modules/base_locale/base_locale.module.ts index c72c836c..39178206 100644 --- a/apps/api/src/modules/base_locale/base_locale.module.ts +++ b/apps/api/src/modules/base_locale/base_locale.module.ts @@ -4,17 +4,15 @@ import { forwardRef, RequestMethod, } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; - -import { - BaseLocale, - BaseLocaleSchema, -} from '@/shared/schemas/base_locale/base_locale.schema'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { BaseLocaleController } from '@/modules/base_locale/base_locale.controller'; import { BaseLocaleMiddleware } from '@/modules/base_locale/base_locale.middleware'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; import { PublicationModule } from '@/shared/modules/publication/publication.module'; +import { SearchQueryPipe } from './pipe/search_query.pipe'; +import { BanPlateformModule } from '@/shared/modules/ban_plateform/ban_plateform.module'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { HabilitationModule } from '@/modules/base_locale/sub_modules/habilitation/habilitation.module'; import { ExportCsvModule } from '@/modules/base_locale/sub_modules/export_csv/export_csv.module'; @@ -24,16 +22,12 @@ import { VoieModule } from '@/modules/voie/voie.module'; import { ToponymeModule } from '@/modules/toponyme/toponyme.module'; import { CommuneModule } from './sub_modules/commune/commune.module'; import { PopulateModule } from './sub_modules/populate/populate.module'; -import { SearchQueryPipe } from './pipe/search_query.pipe'; -import { BanPlateformModule } from '@/shared/modules/ban_plateform/ban_plateform.module'; import { ConfigModule } from '@nestjs/config'; @Module({ imports: [ ConfigModule, - MongooseModule.forFeature([ - { name: BaseLocale.name, schema: BaseLocaleSchema }, - ]), + TypeOrmModule.forFeature([BaseLocale]), PublicationModule, forwardRef(() => BanPlateformModule), forwardRef(() => HabilitationModule), diff --git a/apps/api/src/modules/base_locale/base_locale.service.ts b/apps/api/src/modules/base_locale/base_locale.service.ts index 506d05ca..86b49629 100644 --- a/apps/api/src/modules/base_locale/base_locale.service.ts +++ b/apps/api/src/modules/base_locale/base_locale.service.ts @@ -5,56 +5,59 @@ import { Inject, forwardRef, } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; +import { InjectRepository } from '@nestjs/typeorm'; import { - FilterQuery, - Model, - Types, - QueryWithHelpers, - Aggregate, - PipelineStage, -} from 'mongoose'; + ArrayContains, + FindOptionsSelect, + FindOptionsWhere, + In, + IsNull, + Not, + Repository, + UpdateResult, +} from 'typeorm'; import { uniq, difference, groupBy } from 'lodash'; import { format } from 'date-fns'; import { fr } from 'date-fns/locale'; import { MailerService } from '@nestjs-modules/mailer'; import { ConfigService } from '@nestjs/config'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; import { Habilitation } from '@/shared/modules/api_depot/types/habilitation.type'; +import { BanPlateformService } from '@/shared/modules/ban_plateform/ban_plateform.service'; import { getApiRecoveryUrl, getApiUrl, getEditorUrl, } from '@/shared/utils/mailer.utils'; +import { extendWithNumeros } from '@/shared/utils/numero.utils'; +import { generateBase62String } from '@/lib/utils/token.utils'; +import { FromCsvType, extractFromCsv } from '@/lib/utils/csv.utils'; import { ToponymeService } from '@/modules/toponyme/toponyme.service'; import { VoieService } from '@/modules/voie/voie.service'; import { NumeroService } from '@/modules/numeros/numero.service'; import { CreateBaseLocaleDTO } from '@/modules/base_locale/dto/create_base_locale.dto'; -import { generateBase62String } from '@/lib/utils/token.utils'; import { ExtendedBaseLocaleDTO } from './dto/extended_base_locale.dto'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; import { UpdateBaseLocaleDTO } from './dto/update_base_locale.dto'; -import { extendWithNumeros } from '@/shared/utils/numero.utils'; import { CreateDemoBaseLocaleDTO } from './dto/create_demo_base_locale.dto'; import { getCommune } from '@/shared/utils/cog.utils'; import { PopulateService } from './sub_modules/populate/populate.service'; import { UpdateBaseLocaleDemoDTO } from './dto/update_base_locale_demo.dto'; -import { extractFromCsv } from '@/lib/utils/csv.utils'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; import { ImportFileBaseLocaleDTO } from './dto/import_file_base_locale.dto'; import { RecoverBaseLocaleDTO } from './dto/recover_base_locale.dto'; import { AllDeletedInBalDTO } from './dto/all_deleted_in_bal.dto'; -import { PopulateVoie } from '@/shared/schemas/voie/voie.populate'; -import { BanPlateformService } from '@/shared/modules/ban_plateform/ban_plateform.service'; @Injectable() export class BaseLocaleService { constructor( - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, private readonly mailerService: MailerService, @Inject(forwardRef(() => VoieService)) private voieService: VoieService, @@ -69,15 +72,17 @@ export class BaseLocaleService { private configService: ConfigService, ) {} - async findOneOrFail(id: string): Promise { - const filter = { - _id: id, - }; - const baseLocale = await this.baseLocaleModel.findOne(filter).lean().exec(); - + public async findOneOrFail(balId: string): Promise { + // Créer le filtre where et lance la requète postgres + const where: FindOptionsWhere = { id: balId }; + const baseLocale = await this.basesLocalesRepository.findOne({ + where, + withDeleted: true, + }); + // Si la bal n'existe pas, on throw une erreur if (!baseLocale) { throw new HttpException( - `BaseLocale ${id} not found`, + `BaseLocale ${balId} not found`, HttpStatus.NOT_FOUND, ); } @@ -85,118 +90,139 @@ export class BaseLocaleService { return baseLocale; } - async findOne(filter?: FilterQuery): Promise { - return this.baseLocaleModel.findOne(filter).lean(); + public async count(where: FindOptionsWhere): Promise { + return this.basesLocalesRepository.count({ where }); } - async count(filter?: FilterQuery): Promise { - return this.baseLocaleModel.countDocuments(filter); + public async findMany( + where: FindOptionsWhere, + select?: FindOptionsSelect, + ): Promise { + return this.basesLocalesRepository.find({ + where, + ...(select && { select }), + }); } - async findMany( - filter?: FilterQuery, - selector: Record = null, - limit: number = null, - offset: number = null, + public async searchMany( + where: FindOptionsWhere, + email?: string, + limit?: number, + offset?: number, ): Promise { - const query: QueryWithHelpers< - Array, - BaseLocale - > = this.baseLocaleModel.find(filter); - - if (selector) { - query.select(selector); - } - if (limit) { - query.limit(limit); - } - if (offset) { - query.skip(offset); - } + return this.basesLocalesRepository + .createQueryBuilder() + .select() + .where(where) + .andWhere('lower(emails::text)::text[] @> ARRAY[:email]', { email }) + .limit(limit) + .offset(offset) + .getMany(); + } - return query.lean().exec(); + public async countGroupByStatus(): Promise { + return this.basesLocalesRepository + .createQueryBuilder() + .select('status') + .addSelect('COUNT(id)', 'count') + .groupBy('status') + .getRawMany(); } - async createOne( + public async createOne( createInput: CreateBaseLocaleDTO, - sendMail: boolean = true, ): Promise { + // On récupère l'id ban de la BAL const banId: string = await this.banPlateformService.getIdBanCommune( createInput.commune, ); - const newBaseLocale = await this.baseLocaleModel.create({ + // On créer l'object bal + const entityToSave: BaseLocale = this.basesLocalesRepository.create({ banId, ...createInput, token: generateBase62String(20), status: StatusBaseLocalEnum.DRAFT, }); - - if (sendMail) { - const editorUrl = getEditorUrl(newBaseLocale); - const apiUrl = getApiUrl(); - await this.mailerService.sendMail({ - to: newBaseLocale.emails, - subject: 'Création d’une nouvelle Base Adresse Locale', - template: 'bal-creation-notification', - bcc: this.configService.get('SMTP_BCC'), - context: { - baseLocale: newBaseLocale, - editorUrl, - apiUrl, - }, - }); - } - + // On insert l'object dans postgres + const newBaseLocale: BaseLocale = + await this.basesLocalesRepository.save(entityToSave); + // On envoie un mail de création de Bal + const editorUrl = getEditorUrl(newBaseLocale); + const apiUrl = getApiUrl(); + await this.mailerService.sendMail({ + to: newBaseLocale.emails, + subject: 'Création d’une nouvelle Base Adresse Locale', + template: 'bal-creation-notification', + bcc: this.configService.get('SMTP_BCC'), + context: { + baseLocale: newBaseLocale, + editorUrl, + apiUrl, + }, + }); + // On retourne la Bal créé return newBaseLocale; } - async createDemo( - createDemoInput: CreateDemoBaseLocaleDTO, - ): Promise { - const { commune, populate } = createDemoInput; - const banId: string = await this.banPlateformService.getIdBanCommune( - createDemoInput.commune, - ); - const newDemoBaseLocale = await this.baseLocaleModel.create({ + public async createDemo({ + commune, + populate, + }: CreateDemoBaseLocaleDTO): Promise { + // Insere la nouvelle Bal de demo + const banId: string = + await this.banPlateformService.getIdBanCommune(commune); + // On créer l'object bal + const entityToSave = this.basesLocalesRepository.create({ banId, token: generateBase62String(20), commune, nom: `Adresses de ${getCommune(commune).nom} [démo]`, status: StatusBaseLocalEnum.DEMO, }); - + // On insert l'object dans postgres + const newDemoBaseLocale: BaseLocale = + await this.basesLocalesRepository.save(entityToSave); + // Si besoin on populate la Bal if (populate) { await this.extractAndPopulate(newDemoBaseLocale); } - + // On retourne la Bal de demo créé return newDemoBaseLocale; } async extractAndPopulate(baseLocale: BaseLocale): Promise { - const { numeros, voies, toponymes } = await this.populateService.extract( + // On extrait la Bal de l'assemblage BAN ou la derniere revision sur l'api-depot + const data: FromCsvType = await this.populateService.extract( baseLocale.commune, ); - - return this.populate(baseLocale, { numeros, voies, toponymes }); + // On populate la Bal + return this.populate(baseLocale, data); } async importFile( baseLocale: BaseLocale, file: Buffer, ): Promise { - const { voies, numeros, toponymes, isValid, accepted, rejected } = - await extractFromCsv(file, baseLocale.commune); - + // On extrait les infos du fichier + const { + voies, + numeros, + toponymes, + isValid, + accepted, + rejected, + }: FromCsvType = await extractFromCsv(file, baseLocale.commune); + // Si les informations ne sont pas valide on lance une erreur if (!isValid) { throw new HttpException( `CSV file is not valid`, HttpStatus.UNPROCESSABLE_ENTITY, ); } - + // On populate la Bal avec les infos du fichier await this.populate(baseLocale, { voies, numeros, toponymes }); - - await this.touch(baseLocale._id); + // On met a jour le updatedAt de la Bal + await this.touch(baseLocale.id); return { isValid: true, @@ -212,162 +238,147 @@ export class BaseLocaleService { baseLocale: BaseLocale, update: UpdateBaseLocaleDTO, ): Promise { + // On lance une erreur si la Bal est demo if (baseLocale.status === StatusBaseLocalEnum.DEMO) { throw new HttpException( 'Une Base Adresse Locale de démonstration ne peut pas être modifiée. Elle doit d’abord être transformée en brouillon.', HttpStatus.PRECONDITION_FAILED, ); } - - if ( - [StatusBaseLocalEnum.PUBLISHED, StatusBaseLocalEnum.REPLACED].includes( - baseLocale.status, - ) && - update.status && - update.status !== baseLocale.status - ) { - throw new HttpException( - 'La base locale a été publiée, son statut ne peut plus être changé', - HttpStatus.PRECONDITION_FAILED, - ); - } - - const updatedBaseLocale = await this.baseLocaleModel.findOneAndUpdate( - { _id: baseLocale._id }, - { $set: update }, - { new: true }, - ); - - // If emails fields is overrided, we compare with current array to send a notification to new email addresses - if (update.emails) { + const { affected }: UpdateResult = await this.basesLocalesRepository + .createQueryBuilder('bases_locales') + .update(BaseLocale) + .set({ + ...update, + updatedAt: () => `updatedAt`, + }) + .where(`bases_locales.id = :id`, { + id: baseLocale.id, + }) + .execute(); + + // On récupère la Bal mis a jour + const updatedBaseLocale: BaseLocale = + await this.basesLocalesRepository.findOneBy({ id: baseLocale.id }); + // Si les mails de la Bal ont été modifié + if (affected > 0 && update.emails) { + // On envoie un mail au nouveau mails const newCollaborators = difference(update.emails, baseLocale.emails); const editorUrl = getEditorUrl(baseLocale); const apiUrl = getApiUrl(); - await this.mailerService.sendMail({ - to: newCollaborators, - subject: 'Invitation à l’administration d’une Base Adresse Locale', - template: 'new-admin-notification', - bcc: this.configService.get('SMTP_BCC'), - context: { - baseLocale, - editorUrl, - apiUrl, - }, - }); + if (newCollaborators?.length > 0) { + await this.mailerService.sendMail({ + to: newCollaborators, + subject: 'Invitation à l’administration d’une Base Adresse Locale', + template: 'new-admin-notification', + bcc: this.configService.get('SMTP_BCC'), + context: { + baseLocale, + editorUrl, + apiUrl, + }, + }); + } } - + // On retourne la Bal mis a jour return updatedBaseLocale; } async updateStatusToDraft( baseLocale: BaseLocale, - update: UpdateBaseLocaleDemoDTO, + { nom, email }: UpdateBaseLocaleDemoDTO, ): Promise { + // On lance une erreur si la Bal est demo if (baseLocale.status !== StatusBaseLocalEnum.DEMO) { throw new HttpException( 'La Base Adresse Locale n’est pas une Base Adresse Locale de démonstration.', HttpStatus.PRECONDITION_FAILED, ); } - - const { nom, email } = update; - const updatedBaseLocale = await this.baseLocaleModel.findOneAndUpdate( - { _id: baseLocale._id }, + // On met a jour la Bal + const { affected }: UpdateResult = await this.basesLocalesRepository.update( + { id: baseLocale.id }, { - $set: { - nom, - emails: [email], - status: StatusBaseLocalEnum.DRAFT, - }, + nom, + emails: [email], + status: StatusBaseLocalEnum.DRAFT, }, - { new: true }, ); - - const editorUrl = getEditorUrl(updatedBaseLocale); - const apiUrl = getApiUrl(); - await this.mailerService.sendMail({ - to: updatedBaseLocale.emails, - subject: 'Création d’une nouvelle Base Adresse Locale', - template: 'bal-creation-notification', - bcc: this.configService.get('SMTP_BCC'), - context: { - baseLocale: updatedBaseLocale, - editorUrl, - apiUrl, - }, - }); - + // On récupère la Bal mofifié + const updatedBaseLocale: BaseLocale = + await this.basesLocalesRepository.findOneBy({ id: baseLocale.id }); + // On envoie un mail si la Bal a été modifié + if (affected > 0) { + const editorUrl = getEditorUrl(updatedBaseLocale); + const apiUrl = getApiUrl(); + await this.mailerService.sendMail({ + to: updatedBaseLocale.emails, + subject: 'Création d’une nouvelle Base Adresse Locale', + template: 'bal-creation-notification', + bcc: this.configService.get('SMTP_BCC'), + context: { + baseLocale: updatedBaseLocale, + editorUrl, + apiUrl, + }, + }); + } + // On retourne la Bal modifié return updatedBaseLocale; } async deleteOne(baseLocale: BaseLocale) { if (baseLocale.status === StatusBaseLocalEnum.DEMO) { - await this.hardDeleteOne(baseLocale); + // Si la Bal est en demo on la supprime + await this.delete(baseLocale); } else { - await this.softDeleteOne(baseLocale); + // Si la Bal n'est pas en demo on l'archive + await this.softDelete(baseLocale); } } - async deleteData(baseLocale: BaseLocale) { - await this.numeroService.deleteMany({ - _bal: baseLocale._id, - }); - await this.voieService.deleteMany({ _bal: baseLocale._id }); - await this.toponymeService.deleteMany({ - _bal: baseLocale._id, - }); - } - - async hardDeleteOne(baseLocale: BaseLocale) { - await this.deleteData(baseLocale); - await this.baseLocaleModel.deleteOne({ _id: baseLocale._id }); - } - - async softDeleteOne(baseLocale: BaseLocale) { - await this.baseLocaleModel.updateOne( - { _id: baseLocale._id }, - { $set: { _deleted: new Date() } }, - ); + async delete(baseLocale: BaseLocale) { + // On supprime la Bal + // Par CASCADE cela va supprimer les voies, toponymes et numeros dans postgres + await this.basesLocalesRepository.delete({ id: baseLocale.id }); } - async aggregate(aggregation?: PipelineStage[]): Promise> { - return this.baseLocaleModel.aggregate(aggregation); + async softDelete(baseLocale: BaseLocale) { + // On archive la Bal + await this.basesLocalesRepository.softDelete({ id: baseLocale.id }); } async findAllDeletedByBal( baseLocale: BaseLocale, ): Promise { - const numerosDeleted = await this.numeroService.findMany({ - _bal: baseLocale._id, - _deleted: { $ne: null }, - }); - - const numerosByVoieId = groupBy(numerosDeleted, 'voie'); - const voies: any[] = await this.voieService.findMany({ - _bal: baseLocale._id, - $or: [ - { - _id: { - $in: Object.keys(numerosByVoieId), - }, - }, - { _deleted: { $ne: null } }, - ], - }); - - const voiesPopulate: PopulateVoie[] = []; - for (const voie of voies) { - const voiePopulate: PopulateVoie = { - ...voie, - numeros: numerosByVoieId[voie._id.toString()] || [], - }; - voiesPopulate.push(voiePopulate); - } - - const toponymes: Toponyme[] = await this.toponymeService.findMany({ - _bal: baseLocale._id, - _deleted: { $ne: null }, + // On récupère les numeros archivés + const numerosDeleted = await this.numeroService.findManyWithDeleted({ + balId: baseLocale.id, + deletedAt: Not(IsNull()), }); + const numerosByVoieId = groupBy(numerosDeleted, 'voieId'); + // On récupère les voies archivés ou celle qui ont des numéros archivés + const voies: any[] = await this.voieService.findManyWithDeleted([ + { + id: In(Object.keys(numerosByVoieId)), + }, + { + balId: baseLocale.id, + deletedAt: Not(IsNull()), + }, + ]); + // On populate les voie avec les numeros + const voiesPopulate: Voie[] = voies.map((voie: Voie) => ({ + ...voie, + numeros: numerosByVoieId[voie.id] || [], + })); + // On récupère les toponyme archivé de la bal + const toponymes: Toponyme[] = + await this.toponymeService.findManyWithDeleted({ + balId: baseLocale.id, + deletedAt: Not(IsNull()), + }); + // On retourne le voies et toponyme archivé return { voies: voiesPopulate, toponymes, @@ -376,80 +387,80 @@ export class BaseLocaleService { async populate( baseLocale: BaseLocale, - data: { voies: Voie[]; toponymes: Toponyme[]; numeros: Numero[] }, - deleteData: boolean = true, + { voies, toponymes, numeros }: FromCsvType, ): Promise { - if (deleteData) { - await this.deleteData(baseLocale); - } - - const { voies, toponymes, numeros } = data; - + // On supprime les numeros, vois et toponymes si il y en a + await this.numeroService.deleteMany({ balId: baseLocale.id }); + await this.voieService.deleteMany({ balId: baseLocale.id }); + await this.toponymeService.deleteMany({ balId: baseLocale.id }); + // On import les voies, toponymes et numeros du fichier await this.voieService.importMany(baseLocale, voies); await this.toponymeService.importMany(baseLocale, toponymes); await this.numeroService.importMany(baseLocale, numeros); - + // On calcule les centroid des voies + const voiesCreated: Voie[] = await this.voieService.findMany({ + balId: baseLocale.id, + }); + await Promise.all( + voiesCreated.map(({ id }) => this.voieService.calcCentroid(id)), + ); + // On retourne la Bal return baseLocale; } async extendWithNumeros( baseLocale: BaseLocale, ): Promise { + // On récupère les numeros de la Bal const numeros = await this.numeroService.findMany( { - _bal: baseLocale._id, - _deleted: null, + balId: baseLocale.id, }, - { certifie: 1, numero: 1, comment: 1 }, + { certifie: true, numero: true, comment: true }, ); - + // On rajoute les metas des numeros a la Bal return extendWithNumeros(baseLocale, numeros); } async getParcelles(basesLocale: BaseLocale): Promise { - const toponymesWithParcelles = await this.toponymeService.findDistinct( - { - _bal: basesLocale._id, - _deleted: null, - }, - 'parcelles', + // On récupère les parcelle des toponyme de la Bal + const toponymesWithParcelles = + await this.toponymeService.findDistinctParcelles(basesLocale.id); + // On récupère les parcelles des numeros de la Bal + const numerosWithParcelles = await this.numeroService.findDistinctParcelles( + basesLocale.id, ); - - const numerosWithParcelles = await this.numeroService.findDistinct( - { - _bal: basesLocale._id, - _deleted: null, - }, - 'parcelles', - ); - - const parcelles = [...numerosWithParcelles, ...toponymesWithParcelles]; - - return uniq(parcelles); + // On concat et unifie les parcelles et on les retourne + const parcelles = uniq([ + ...numerosWithParcelles, + ...toponymesWithParcelles, + ]); + return parcelles; } async updateHabilitation( baseLocale: BaseLocale, habilitation: Habilitation, ): Promise { - await this.baseLocaleModel.updateOne( - { _id: baseLocale._id }, - { _habilitation: habilitation._id }, + await this.basesLocalesRepository.update( + { id: baseLocale.id }, + { habilitationId: habilitation._id }, ); } async recoverAccess({ id, email }: RecoverBaseLocaleDTO) { - const filters = { - emails: { $regex: new RegExp(`^${email}$`, 'i') }, - } as FilterQuery; - - if (id) { - filters._id = id; - } - - const basesLocales = await this.findMany(filters); - + // On créer le where de la requète + const where: FindOptionsWhere = { + emails: ArrayContains([email]), + ...(id && { id }), + }; + // On lance la requète + const basesLocales = await this.basesLocalesRepository.find({ + where, + withDeleted: true, + }); if (basesLocales.length > 0) { + // Si il a des Bal qui correspondent, on envoie un mail pour retouver l'accès a ses Bal const STATUS = { [StatusBaseLocalEnum.DRAFT]: 'Brouillon', [StatusBaseLocalEnum.PUBLISHED]: 'Publiée', @@ -458,22 +469,22 @@ export class BaseLocaleService { const apiUrl = getApiUrl(); const recoveryBals = basesLocales - .filter(({ _deleted }) => !_deleted) + .filter(({ deletedAt }) => !deletedAt) .map((baseLocale) => ({ ...baseLocale, recoveryUrl: getEditorUrl(baseLocale), statusFr: STATUS[baseLocale.status], - createdAt: format(baseLocale._created, 'P', { locale: fr }), + createdAt: format(baseLocale.createdAt, 'P', { locale: fr }), })); const deletedBals = basesLocales - .filter(({ _deleted }) => _deleted) + .filter(({ deletedAt }) => deletedAt) .map((baseLocale) => ({ ...baseLocale, statusFr: 'Supprimée', deletedRecoveryUrl: getApiRecoveryUrl(baseLocale), - deletedAt: baseLocale._deleted - ? format(baseLocale._deleted, 'P', { locale: fr }) + deletedAt: baseLocale.deletedAt + ? format(baseLocale.deletedAt, 'P', { locale: fr }) : null, })); @@ -489,6 +500,7 @@ export class BaseLocaleService { }, }); } else { + // Si aucune Bal ne correspond, on lance un erreur throw new HttpException( 'Aucune base locale ne correspond à ces critères', HttpStatus.NOT_FOUND, @@ -496,67 +508,47 @@ export class BaseLocaleService { } } - async recovery(baseLocale: BaseLocale) { - const now = new Date(); - const recoveredBaseLocale = await this.baseLocaleModel - .findByIdAndUpdate( - { _id: baseLocale._id }, - { $set: { _deleted: null, _updated: now } }, - { new: true }, - ) - .lean(); - - return recoveredBaseLocale; + async restore(baseLocale: BaseLocale) { + // On restore la Bal + await this.basesLocalesRepository.restore({ + id: baseLocale.id, + }); } async renewToken(baseLocale: BaseLocale) { + // On génère un token const token = generateBase62String(20); - const updatedBaseLocale = await this.baseLocaleModel - .findByIdAndUpdate( - { _id: baseLocale._id }, - { $set: { token } }, - { new: true }, - ) - .lean(); - - const editorUrl = getEditorUrl(updatedBaseLocale); - const apiUrl = getApiUrl(); - await this.mailerService.sendMail({ - to: updatedBaseLocale.emails, - subject: 'Renouvellement de jeton de Base Adresse Locale', - template: 'bal-renewal-notification', - bcc: this.configService.get('SMTP_BCC'), - context: { - baseLocale: updatedBaseLocale, - editorUrl, - apiUrl, - }, - }); - + // On update le token de la Bal dans postgres + const { affected }: UpdateResult = await this.basesLocalesRepository.update( + { id: baseLocale.id }, + { token }, + ); + // On récupère la Bal modifié + const updatedBaseLocale: BaseLocale = + await this.basesLocalesRepository.findOneBy({ + id: baseLocale.id, + }); + // Si la Bal a été modifié on envoie un mail avec le nouveau token + if (affected > 0) { + const editorUrl = getEditorUrl(updatedBaseLocale); + const apiUrl = getApiUrl(); + await this.mailerService.sendMail({ + to: updatedBaseLocale.emails, + subject: 'Renouvellement de jeton de Base Adresse Locale', + template: 'bal-renewal-notification', + bcc: this.configService.get('SMTP_BCC'), + context: { + baseLocale: updatedBaseLocale, + editorUrl, + apiUrl, + }, + }); + } + // On retourne la Bal mis a jour return updatedBaseLocale; } - async findMetas(balId: string) { - const voies: Voie[] = await this.voieService.findMany({ - _bal: balId, - }); - const toponymes: Toponyme[] = await this.toponymeService.findMany({ - _bal: balId, - }); - const numeros: Numero[] = await this.numeroService.findMany({ - _bal: balId, - }); - return { voies, toponymes, numeros }; - } - - async findDistinct(field: string): Promise { - return this.baseLocaleModel.distinct(field).exec(); - } - - touch(baseLocaleId: Types.ObjectId, _updated: Date = new Date()) { - return this.baseLocaleModel.updateOne( - { _id: baseLocaleId }, - { $set: { _updated } }, - ); + touch(balId: string, updatedAt: Date = new Date()) { + return this.basesLocalesRepository.update({ id: balId }, { updatedAt }); } } diff --git a/apps/api/src/modules/base_locale/dto/all_deleted_in_bal.dto.ts b/apps/api/src/modules/base_locale/dto/all_deleted_in_bal.dto.ts index 3510962e..b4073a09 100644 --- a/apps/api/src/modules/base_locale/dto/all_deleted_in_bal.dto.ts +++ b/apps/api/src/modules/base_locale/dto/all_deleted_in_bal.dto.ts @@ -1,15 +1,15 @@ -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { PopulateVoie } from '@/shared/schemas/voie/voie.populate'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Voie } from '@/shared/entities/voie.entity'; import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; export class AllDeletedInBalDTO { - @Type(() => PopulateVoie) + @Type(() => Voie) @ApiProperty({ - type: () => PopulateVoie, + type: () => Voie, isArray: true, }) - voies: PopulateVoie[]; + voies: Voie[]; @Type(() => Toponyme) @ApiProperty({ diff --git a/apps/api/src/modules/base_locale/dto/extended_base_locale.dto.ts b/apps/api/src/modules/base_locale/dto/extended_base_locale.dto.ts index 7d8f678c..856ef004 100644 --- a/apps/api/src/modules/base_locale/dto/extended_base_locale.dto.ts +++ b/apps/api/src/modules/base_locale/dto/extended_base_locale.dto.ts @@ -1,8 +1,7 @@ +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { Numero } from '@/shared/entities/numero.entity'; import { ApiProperty } from '@nestjs/swagger'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; - export class ExtendedBaseLocaleDTO extends BaseLocale { @ApiProperty() nbNumeros?: number; diff --git a/apps/api/src/modules/base_locale/dto/recover_base_locale.dto.ts b/apps/api/src/modules/base_locale/dto/recover_base_locale.dto.ts index 856f377b..fac0cf32 100644 --- a/apps/api/src/modules/base_locale/dto/recover_base_locale.dto.ts +++ b/apps/api/src/modules/base_locale/dto/recover_base_locale.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsEmail, IsMongoId, IsOptional } from 'class-validator'; +import { IsEmail, IsOptional, IsMongoId } from 'class-validator'; export class RecoverBaseLocaleDTO { @IsMongoId() diff --git a/apps/api/src/modules/base_locale/dto/update_base_locale.dto.ts b/apps/api/src/modules/base_locale/dto/update_base_locale.dto.ts index 418e8860..26d6a218 100644 --- a/apps/api/src/modules/base_locale/dto/update_base_locale.dto.ts +++ b/apps/api/src/modules/base_locale/dto/update_base_locale.dto.ts @@ -1,10 +1,8 @@ -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; import { ApiProperty } from '@nestjs/swagger'; import { ArrayMaxSize, ArrayNotEmpty, IsEmail, - IsEnum, IsNotEmpty, IsOptional, } from 'class-validator'; @@ -15,11 +13,6 @@ export class UpdateBaseLocaleDTO { @IsNotEmpty() nom?: string; - @IsOptional() - @ApiProperty({ required: false, nullable: false, enum: StatusBaseLocalEnum }) - @IsEnum(StatusBaseLocalEnum) - status?: StatusBaseLocalEnum; - @IsOptional() @ApiProperty({ required: false, nullable: false }) @ArrayNotEmpty() diff --git a/apps/api/src/modules/base_locale/pipe/search_query.pipe.ts b/apps/api/src/modules/base_locale/pipe/search_query.pipe.ts index e389aac0..0fa9299e 100644 --- a/apps/api/src/modules/base_locale/pipe/search_query.pipe.ts +++ b/apps/api/src/modules/base_locale/pipe/search_query.pipe.ts @@ -4,11 +4,13 @@ import { HttpException, HttpStatus, } from '@nestjs/common'; -import { FilterQuery } from 'mongoose'; +import { FindOptionsWhere, Not } from 'typeorm'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; import { getCommune } from '@/shared/utils/cog.utils'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; import { checkValidEmail } from '@/modules/base_locale/utils/base_locale.utils'; import { SearchBaseLocalQuery } from '../dto/search_base_locale.query'; @@ -16,24 +18,27 @@ import { SearchBaseLocalQuery } from '../dto/search_base_locale.query'; export type SearchQueryTransformed = { offset: number; limit: number; - filters: FilterQuery; + filters: FindOptionsWhere; + email?: string; }; @Injectable() export class SearchQueryPipe implements PipeTransform { transform(query: SearchBaseLocalQuery): SearchQueryTransformed { - const limit = query.limit ? Number.parseInt(query.limit, 10) : 20; - const offset = query.offset ? Number.parseInt(query.offset, 10) : 0; - const filters: FilterQuery = {}; + const res: SearchQueryTransformed = { + limit: query.limit ? Number.parseInt(query.limit, 10) : 20, + offset: query.offset ? Number.parseInt(query.offset, 10) : 0, + filters: {}, + }; - if (!Number.isInteger(limit) || limit > 100 || limit <= 0) { + if (!Number.isInteger(res.limit) || res.limit > 100 || res.limit <= 0) { throw new HttpException( 'La valeur du champ "limit" doit un entier compris en 1 et 100 (défaut : 20)', HttpStatus.BAD_REQUEST, ); } - if (!Number.isInteger(offset) || offset < 0) { + if (!Number.isInteger(res.offset) || res.offset < 0) { throw new HttpException( 'La valeur du champ "offset" doit être un entier positif (défaut : 0)', HttpStatus.BAD_REQUEST, @@ -41,9 +46,9 @@ export class SearchQueryPipe implements PipeTransform { } if (query.deleted === 'false') { - filters._deleted = { $eq: null }; + res.filters.deletedAt = null; } else if (query.deleted === 'true') { - filters._deleted = { $ne: null }; + res.filters.deletedAt = Not(null); } else if (query.deleted) { throw new HttpException( 'La valeur du champ "deleted" est invalide', @@ -53,7 +58,7 @@ export class SearchQueryPipe implements PipeTransform { if (query.commune) { if (typeof query.commune === 'string' && getCommune(query.commune)) { - filters.commune = { $eq: query.commune }; + res.filters.commune = query.commune; } else { throw new HttpException( 'La valeur du champ "commune" est invalide', @@ -64,7 +69,7 @@ export class SearchQueryPipe implements PipeTransform { if (query.email) { if (typeof query.email === 'string' && checkValidEmail(query.email)) { - filters.emails = { $regex: new RegExp(`^${query.email}$`, 'i') }; + res.email = query.email.toLowerCase(); } else { throw new HttpException( 'La valeur du champ "email" est invalide', @@ -83,7 +88,7 @@ export class SearchQueryPipe implements PipeTransform { StatusBaseLocalEnum.REPLACED, ].includes(query.status as StatusBaseLocalEnum) ) { - filters.status = { $eq: query.status }; + res.filters.status = query.status as StatusBaseLocalEnum; } else { throw new HttpException( 'La valeur du champ "status" est invalide', @@ -92,6 +97,6 @@ export class SearchQueryPipe implements PipeTransform { } } - return { offset, limit, filters }; + return res; } } diff --git a/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.controller.ts b/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.controller.ts index f69c7b2b..37f2360a 100644 --- a/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.controller.ts +++ b/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.controller.ts @@ -44,7 +44,7 @@ export class HabilitationController { ) { try { const isValid: boolean = await this.habilitationService.isValid( - req.baseLocale._habilitation, + req.baseLocale.habilitationId, ); res.status(HttpStatus.OK).json(isValid); } catch (err) { @@ -63,7 +63,7 @@ export class HabilitationController { @UseGuards(AdminGuard) async getHabilitation(@Req() req: CustomRequest, @Res() res: Response) { const result: Habilitation = await this.habilitationService.findOne( - req.baseLocale._habilitation, + req.baseLocale.habilitationId, ); res.status(HttpStatus.OK).json(result); } @@ -94,7 +94,7 @@ export class HabilitationController { @ApiBearerAuth('admin-token') @UseGuards(AdminGuard) async sendPinCode(@Req() req: CustomRequest, @Res() res: Response) { - await this.habilitationService.sendPinCode(req.baseLocale._habilitation); + await this.habilitationService.sendPinCode(req.baseLocale.habilitationId); res.sendStatus(HttpStatus.OK); } @@ -114,7 +114,7 @@ export class HabilitationController { @Res() res: Response, ) { await this.habilitationService.validatePinCode( - req.baseLocale._habilitation, + req.baseLocale.habilitationId, body.code, ); res.sendStatus(HttpStatus.OK); diff --git a/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.service.ts b/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.service.ts index d0f8af0f..b14ce05c 100644 --- a/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.service.ts +++ b/apps/api/src/modules/base_locale/sub_modules/habilitation/habilitation.service.ts @@ -5,7 +5,7 @@ import { StatusHabiliation, } from '@/shared/modules/api_depot/types/habilitation.type'; import { ApiDepotService } from '@/shared/modules/api_depot/api_depot.service'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { BaseLocaleService } from '../../base_locale.service'; @@ -23,12 +23,10 @@ export class HabilitationService { async isValid(habilitationId: string): Promise { const habilitation: Habilitation = await this.apiDepotService.findOneHabiliation(habilitationId); - // On verifie que l'habilitation est valide if (habilitation.status !== StatusHabiliation.ACCEPTED) { return false; } - // On verifie que l'habilitation n'est pas expirée if ( !habilitation.expiresAt || @@ -40,10 +38,8 @@ export class HabilitationService { } async createOne(baseLocale: BaseLocale): Promise { - if (baseLocale._habilitation) { - const habilitation: Habilitation = await this.findOne( - baseLocale._habilitation, - ); + if (baseLocale.habilitationId) { + const habilitation = await this.findOne(baseLocale.habilitationId); const now = new Date(); const { status, expiresAt } = habilitation; diff --git a/apps/api/src/modules/base_locale/sub_modules/populate/populate.service.ts b/apps/api/src/modules/base_locale/sub_modules/populate/populate.service.ts index ca1746ed..6e022cd4 100644 --- a/apps/api/src/modules/base_locale/sub_modules/populate/populate.service.ts +++ b/apps/api/src/modules/base_locale/sub_modules/populate/populate.service.ts @@ -1,4 +1,4 @@ -import { extractFromCsv } from '@/lib/utils/csv.utils'; +import { FromCsvType, extractFromCsv } from '@/lib/utils/csv.utils'; import { ApiDepotService } from '@/shared/modules/api_depot/api_depot.service'; import { BanPlateformService } from '@/shared/modules/ban_plateform/ban_plateform.service'; import { Inject, Injectable, forwardRef } from '@nestjs/common'; @@ -11,12 +11,12 @@ export class PopulateService { private banPlateformService: BanPlateformService, ) {} - private async extractFromApiDepot(codeCommune: string) { + private async extractFromApiDepot(codeCommune: string): Promise { try { const fileData = await this.apiDepotService.downloadCurrentRevisionFile(codeCommune); - const result = await extractFromCsv(fileData, codeCommune); + const result: FromCsvType = await extractFromCsv(fileData, codeCommune); if (!result.isValid) { throw new Error('Invalid CSV file'); @@ -26,7 +26,7 @@ export class PopulateService { } catch {} } - private async extractFromBAN(codeCommune: string) { + private async extractFromBAN(codeCommune: string): Promise { try { const file: Buffer = await this.banPlateformService.getBanAssemblage(codeCommune); @@ -41,7 +41,7 @@ export class PopulateService { } catch {} } - public async extract(codeCommune: string) { + public async extract(codeCommune: string): Promise { const data = (await this.extractFromApiDepot(codeCommune)) || (await this.extractFromBAN(codeCommune)); diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/const/zoom.const.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/const/zoom.const.ts index 4d170999..8437741c 100644 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/const/zoom.const.ts +++ b/apps/api/src/modules/base_locale/sub_modules/tiles/const/zoom.const.ts @@ -7,11 +7,8 @@ export const ZOOM = { minZoom: 13, maxZoom: 19, }, - TRACE_DISPLAY_ZOOM: { + TRACE_ZOOM: { minZoom: 13, maxZoom: 19, }, - TRACE_MONGO_ZOOM: { - zoom: 13, - }, }; diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.controller.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.controller.ts index 8defe655..828b28b5 100644 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.controller.ts +++ b/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.controller.ts @@ -13,12 +13,10 @@ import * as geojsonvt from 'geojson-vt'; import * as vtpbf from 'vt-pbf'; import * as zlib from 'zlib'; import { promisify } from 'util'; + import { CustomRequest } from '@/lib/types/request.type'; import { TilesService } from '@/modules/base_locale/sub_modules/tiles/tiles.service'; -import { - GeoJsonCollectionType, - TileType, -} from '@/modules/base_locale/sub_modules/tiles/types/features.type'; +import { GeoJsonCollectionType } from '@/modules/base_locale/sub_modules/tiles/types/features.type'; const gzip = promisify(zlib.gzip); @@ -45,29 +43,28 @@ export class TilesController { @Query('colorblindMode') colorblindMode: boolean, @Res() res: Response, ) { - const tile: TileType = { z: parseInt(z), x: parseInt(x), y: parseInt(y) }; - const geoJson: GeoJsonCollectionType = + const tile: number[] = [parseInt(x), parseInt(y), parseInt(z)]; + const { numeroPoints, voiePoints, voieLineStrings }: GeoJsonCollectionType = await this.tilesService.getGeoJsonByTile( - req.baseLocale._id, + req.baseLocale.id, tile, colorblindMode, ); const options = { maxZoom: 20 }; - - const numerosTiles = geojsonvt(geoJson.numeroPoints, options).getTile( - tile.z, - tile.x, - tile.y, + const numerosTiles = geojsonvt(numeroPoints, options).getTile( + tile[2], + tile[0], + tile[1], ); - const voieTiles = geojsonvt(geoJson.voiePoints, options).getTile( - tile.z, - tile.x, - tile.y, + const voieTiles = geojsonvt(voiePoints, options).getTile( + tile[2], + tile[0], + tile[1], ); - const voieTraceTiles = geojsonvt(geoJson.voieLineStrings, options).getTile( - tile.z, - tile.x, - tile.y, + const voieTraceTiles = geojsonvt(voieLineStrings, options).getTile( + tile[2], + tile[0], + tile[1], ); if (!numerosTiles && !voieTiles && !voieTraceTiles) { diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.service.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.service.ts index f2cb98bd..39ac6540 100644 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.service.ts +++ b/apps/api/src/modules/base_locale/sub_modules/tiles/tiles.service.ts @@ -1,27 +1,11 @@ import { Inject, Injectable, forwardRef } from '@nestjs/common'; -import { Types, FilterQuery } from 'mongoose'; -import * as turf from '@turf/turf'; -import { Position as PositionTurf } from '@turf/helpers'; - -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; +import { tileToBBOX } from '@mapbox/tilebelt'; import { VoieService } from '@/modules/voie/voie.service'; import { NumeroService } from '@/modules/numeros/numero.service'; -import { getPriorityPosition } from '@/lib/utils/positions.util'; -import { - TileType, - ModelsInTileType, - GeoJsonCollectionType, -} from '@/modules/base_locale/sub_modules/tiles/types/features.type'; +import { GeoJsonCollectionType } from '@/modules/base_locale/sub_modules/tiles/types/features.type'; import { ZOOM } from '@/modules/base_locale/sub_modules/tiles/const/zoom.const'; import { getGeoJson } from '@/modules/base_locale/sub_modules/tiles/utils/geojson.utils'; -import { - getTilesByPosition, - getTilesByLineString, - getParentTile, -} from '@/modules/base_locale/sub_modules/tiles/utils/tiles.utils'; @Injectable() export class TilesService { @@ -32,162 +16,29 @@ export class TilesService { private numeroService: NumeroService, ) {} - private async fetchModelsInTile( - balId: Types.ObjectId, - { x, y, z }: TileType, - ): Promise { - const fetchs: ModelsInTileType = { - numeros: [], - voies: [], - traces: [], - }; - - if (z >= ZOOM.NUMEROS_ZOOM.minZoom && z <= ZOOM.NUMEROS_ZOOM.maxZoom) { - const filter: FilterQuery = { - _bal: balId, - tiles: `${z}/${x}/${y}`, - _deleted: null, - }; - fetchs.numeros = await this.numeroService.findMany(filter); - } - - if (z >= ZOOM.VOIE_ZOOM.minZoom && z <= ZOOM.VOIE_ZOOM.maxZoom) { - const filter: FilterQuery = { - _bal: balId, - centroidTiles: `${z}/${x}/${y}`, - _deleted: null, - }; - fetchs.voies = await this.voieService.findMany(filter); - } - - if ( - z >= ZOOM.TRACE_DISPLAY_ZOOM.minZoom && - z <= ZOOM.TRACE_DISPLAY_ZOOM.maxZoom - ) { - const [xx, yy, zz] = getParentTile([x, y, z], ZOOM.TRACE_MONGO_ZOOM.zoom); - const filter: FilterQuery = { - _bal: balId, - typeNumerotation: TypeNumerotationEnum.METRIQUE, - trace: { $ne: null }, - traceTiles: `${zz}/${xx}/${yy}`, - _deleted: null, - }; - fetchs.traces = await this.voieService.findMany(filter); - } - - return fetchs; - } - public async getGeoJsonByTile( - balId: Types.ObjectId, - tile: TileType, + balId: string, + tile: number[], colorblindMode: boolean, ): Promise { - const modelsInTile: ModelsInTileType = await this.fetchModelsInTile( - balId, - tile, - ); - return getGeoJson(modelsInTile, colorblindMode); - } - - public async updateVoiesTiles(voieIds: Types.ObjectId[]) { - const voies = await this.voieService.findMany( - { - _id: { $in: voieIds }, - _deleted: null, - }, - { - _id: 1, - centroid: 1, - centroidTiles: 1, - typeNumerotation: 1, - trace: 1, - traceTiles: 1, - }, - ); - - return Promise.all(voies.map((v) => this.updateVoieTiles(v))); - } - - public async updateVoieTiles(voie: Voie) { - const voieSet = await this.calcMetaTilesVoie(voie); - return this.voieService.updateOne(voie._id, voieSet); - } - - public calcMetaTilesNumero( - numero: Numero | Record, - ): Numero | Record { - numero.tiles = null; - try { - if (numero.positions && numero.positions.length > 0) { - const position = getPriorityPosition(numero.positions); - numero.tiles = getTilesByPosition(position.point, ZOOM.NUMEROS_ZOOM); - } - } catch (error) { - console.error(error, numero); - } - - return numero; - } - - public async calcMetaTilesVoie(voie: Voie) { - voie.centroid = null; - voie.centroidTiles = null; - voie.traceTiles = null; + const z: number = tile[2]; + const bbox: number[] = tileToBBOX(tile); - try { - if ( - voie.typeNumerotation === TypeNumerotationEnum.METRIQUE && - voie.trace - ) { - this.calcMetaTilesVoieWithTrace(voie); - } else { - const numeros: Numero[] = await this.numeroService.findMany( - { - voie: voie._id, - _deleted: null, - }, - { - positions: 1, - }, - ); - this.calcMetaTilesVoieWithNumeros(voie, numeros); - } - } catch (error) { - console.error(error, voie); - } + const voies = + z >= ZOOM.VOIE_ZOOM.minZoom && z <= ZOOM.VOIE_ZOOM.maxZoom + ? await this.voieService.findManyWhereCentroidInBBox(balId, bbox) + : []; - return voie; - } - - public calcMetaTilesVoieWithNumeros(voie: Voie, numeros: Numero[]): Voie { - if (numeros.length > 0) { - const coordinatesNumeros: PositionTurf[] = numeros - .filter((n) => n.positions && n.positions.length > 0) - .map((n) => getPriorityPosition(n.positions)?.point?.coordinates); - // CALC CENTROID - if (coordinatesNumeros.length > 0) { - const collection = turf.featureCollection( - coordinatesNumeros.map((n) => turf.point(n)), - ); - voie.centroid = turf.centroid(collection); - voie.centroidTiles = getTilesByPosition( - voie.centroid.geometry, - ZOOM.VOIE_ZOOM, - ); - } - } - return voie; - } + const traces = + z >= ZOOM.TRACE_ZOOM.minZoom && z <= ZOOM.TRACE_ZOOM.maxZoom + ? await this.voieService.findManyWhereTraceInBBox(balId, bbox) + : []; - public calcMetaTilesVoieWithTrace(voie: Partial): Partial { - voie.centroid = turf.centroid(voie.trace); - voie.centroidTiles = getTilesByPosition( - voie.centroid.geometry, - ZOOM.VOIE_ZOOM, - ); - voie.traceTiles = getTilesByLineString(voie.trace); + const numeros = + z >= ZOOM.NUMEROS_ZOOM.minZoom && z <= ZOOM.NUMEROS_ZOOM.maxZoom + ? await this.numeroService.findManyWherePositionInBBox(balId, bbox) + : []; - return voie; + return getGeoJson(voies, traces, numeros, colorblindMode); } } diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/types/features.type.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/types/features.type.ts index 87dc6005..83ea44ea 100644 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/types/features.type.ts +++ b/apps/api/src/modules/base_locale/sub_modules/tiles/types/features.type.ts @@ -1,5 +1,3 @@ -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; import { FeatureCollection } from 'geojson'; export type GeoJsonCollectionType = { @@ -7,11 +5,3 @@ export type GeoJsonCollectionType = { voiePoints: FeatureCollection; voieLineStrings: FeatureCollection; }; - -export type ModelsInTileType = { - numeros: Numero[]; - voies: Voie[]; - traces: Voie[]; -}; - -export type TileType = { z: number; x: number; y: number }; diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/utils/geojson.utils.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/utils/geojson.utils.ts index 29c6271f..55a21291 100644 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/utils/geojson.utils.ts +++ b/apps/api/src/modules/base_locale/sub_modules/tiles/utils/geojson.utils.ts @@ -1,17 +1,13 @@ import * as turf from '@turf/turf'; import { Feature as FeatureTurf } from '@turf/helpers'; import * as randomColor from 'randomcolor'; -import { Types } from 'mongoose'; import { FeatureCollection } from 'geojson'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; import { getPriorityPosition } from '@/lib/utils/positions.util'; -import { - ModelsInTileType, - GeoJsonCollectionType, -} from '@/modules/base_locale/sub_modules/tiles/types/features.type'; +import { GeoJsonCollectionType } from '@/modules/base_locale/sub_modules/tiles/types/features.type'; // Paul Tol's vibrant palette for accessibility const colorblindFriendlyHues = [ @@ -23,26 +19,22 @@ const colorblindFriendlyHues = [ '#009988', ]; -function getColorById(id: Types.ObjectId): string { +function getColorById(id: string): string { return randomColor({ luminosity: 'dark', - seed: id.toHexString(), + seed: id, }); } // Returns a color of the palette based on the bal ID -function getColorblindFriendlyHue(id: Types.ObjectId): string { - const slicedId = id.toHexString().slice(19); - +function getColorblindFriendlyHue(id: string): string { + const slicedId = id.slice(16).replace(/-/g, ''); return colorblindFriendlyHues[ Number.parseInt(slicedId, 16) % colorblindFriendlyHues.length ]; } -function getFeatureColor( - id: Types.ObjectId, - colorblindMode: boolean = false, -): string { +function getFeatureColor(id: string, colorblindMode: boolean = false): string { return colorblindMode ? getColorblindFriendlyHue(id) : getColorById(id); } @@ -50,15 +42,15 @@ function numeroToPointFeature(n: Numero, colorblindMode: boolean): FeatureTurf { const position = getPriorityPosition(n.positions); return turf.feature(position.point, { type: 'adresse', - id: n._id.toHexString(), + id: n.id, numero: n.numero, suffixe: n.suffixe, typePosition: position.type, parcelles: n.parcelles, certifie: n.certifie, - idVoie: n.voie.toHexString(), - idToponyme: n.toponyme ? n.toponyme.toHexString() : null, - color: getFeatureColor(n.voie, colorblindMode), + idVoie: n.voieId, + idToponyme: n.toponymeId, + color: getFeatureColor(n.voieId, colorblindMode), }); } @@ -67,20 +59,20 @@ function voieToLineStringFeature( colorblindMode: boolean, ): FeatureTurf { return turf.feature(v.trace, { - id: v._id.toHexString(), + id: v.id, type: 'voie-trace', nom: v.nom, originalGeometry: v.trace, - color: getFeatureColor(v._id, colorblindMode), + color: getFeatureColor(v.id, colorblindMode), }); } function voieToPointFeature(v: Voie, colorblindMode: boolean): FeatureTurf { - return turf.feature(v.centroid.geometry, { - id: v._id.toHexString(), + return turf.feature(v.centroid, { + id: v.id, type: 'voie', nom: v.nom, - color: getFeatureColor(v._id, colorblindMode), + color: getFeatureColor(v.id, colorblindMode), }); } @@ -112,15 +104,14 @@ export function numerosPointsToGeoJSON( } export function getGeoJson( - modelsInTile: ModelsInTileType, + voies: Voie[], + traces: Voie[], + numeros: Numero[], colorblindMode: boolean, ): GeoJsonCollectionType { return { - numeroPoints: numerosPointsToGeoJSON(modelsInTile.numeros, colorblindMode), - voiePoints: voiesPointsToGeoJSON(modelsInTile.voies, colorblindMode), - voieLineStrings: voiesLineStringsToGeoJSON( - modelsInTile.traces, - colorblindMode, - ), + numeroPoints: numerosPointsToGeoJSON(numeros, colorblindMode), + voiePoints: voiesPointsToGeoJSON(voies, colorblindMode), + voieLineStrings: voiesLineStringsToGeoJSON(traces, colorblindMode), }; } diff --git a/apps/api/src/modules/base_locale/sub_modules/tiles/utils/tiles.utils.ts b/apps/api/src/modules/base_locale/sub_modules/tiles/utils/tiles.utils.ts deleted file mode 100644 index 57643bfe..00000000 --- a/apps/api/src/modules/base_locale/sub_modules/tiles/utils/tiles.utils.ts +++ /dev/null @@ -1,98 +0,0 @@ -import * as turf from '@turf/turf'; -import booleanIntersects from '@turf/boolean-intersects'; -import { range, union } from 'lodash'; -import { - LineString as LineStringTurf, - BBox as BboxTurf, - Geometry as GeometryTurf, -} from '@turf/helpers'; -import { - pointToTile, - bboxToTile, - getParent, - getChildren, - tileToBBOX, -} from '@mapbox/tilebelt'; - -import { Point } from '@/shared/schemas/geometry/point.schema'; - -import { ZOOM } from '@/modules/base_locale/sub_modules/tiles/const/zoom.const'; -import { roundCoordinate } from '@/shared/utils/coor.utils'; -import { getPriorityPosition } from '@/lib/utils/positions.util'; - -export function getParentTile(tile: number[], zoomFind: number) { - return tile[2] <= zoomFind ? tile : getParentTile(getParent(tile), zoomFind); -} - -export function getTilesByPosition( - point: Point, - { - minZoom, - maxZoom, - }: { minZoom: number; maxZoom: number } = ZOOM.NUMEROS_ZOOM, -): string[] { - if (!point || !minZoom || !maxZoom) { - return null; - } - - const lon: number = roundCoordinate(point.coordinates[0], 6); - const lat: number = roundCoordinate(point.coordinates[1], 6); - - const tiles: string[] = range(minZoom, maxZoom + 1).map((zoom) => { - const [x, y, z]: number[] = pointToTile(lon, lat, zoom); - return `${z}/${x}/${y}`; - }); - - return tiles; -} - -export function getTilesByLineString( - lineString: LineStringTurf, - { zoom }: { zoom: number } = ZOOM.TRACE_MONGO_ZOOM, -): string[] { - const bboxFeature: BboxTurf = turf.bbox(lineString); - const [x, y, z]: number[] = bboxToTile(bboxFeature); - const tiles: string[] = getTilesByBbox([x, y, z], lineString, zoom); - return tiles; -} - -export function getTilesByBbox( - [x, y, z]: number[], - geojson: GeometryTurf, - zoom: number, -): string[] { - const tiles = []; - if (z === zoom) { - return [`${z}/${x}/${y}`]; - } - - if (z < zoom) { - const childTiles: number[][] = getChildren([x, y, z]); - for (const childTile of childTiles) { - const childTileBbox = tileToBBOX(childTile); - const bboxPolygon = turf.bboxPolygon(childTileBbox); - if (booleanIntersects(geojson, bboxPolygon)) { - tiles.push(...getTilesByBbox(childTile, geojson, zoom)); - } - } - } else { - const parentTile = getParent([x, y, z]); - tiles.push(...getTilesByBbox(parentTile, geojson, zoom)); - } - - return union(tiles); -} - -export function calcMetaTilesNumero(numero) { - numero.tiles = null; - try { - if (numero.positions && numero.positions.length > 0) { - const position = getPriorityPosition(numero.positions); - numero.tiles = getTilesByPosition(position.point, ZOOM.NUMEROS_ZOOM); - } - } catch (error) { - console.error(error, numero); - } - - return numero; -} diff --git a/apps/api/src/modules/base_locale/utils/base_locale.utils.ts b/apps/api/src/modules/base_locale/utils/base_locale.utils.ts index 1ad6e5ca..a805cd3a 100644 --- a/apps/api/src/modules/base_locale/utils/base_locale.utils.ts +++ b/apps/api/src/modules/base_locale/utils/base_locale.utils.ts @@ -1,5 +1,5 @@ import { omit } from 'lodash'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { ExtendedBaseLocaleDTO } from '../dto/extended_base_locale.dto'; export function filterSensitiveFields( diff --git a/apps/api/src/modules/numeros/dto/create_numero.dto.ts b/apps/api/src/modules/numeros/dto/create_numero.dto.ts index a0c16081..4b6de152 100644 --- a/apps/api/src/modules/numeros/dto/create_numero.dto.ts +++ b/apps/api/src/modules/numeros/dto/create_numero.dto.ts @@ -1,17 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Position } from '@/shared/schemas/position.schema'; -import { Types } from 'mongoose'; import { Type } from 'class-transformer'; import { MaxLength, - IsMongoId, Validate, IsOptional, ArrayNotEmpty, ValidateNested, + IsMongoId, } from 'class-validator'; import { ValidatorBal } from '@/shared/validators/validator_bal.validator'; +import { Position } from '@/shared/entities/position.entity'; export class CreateNumeroDTO { @Validate(ValidatorBal, ['numero']) @@ -31,7 +30,7 @@ export class CreateNumeroDTO { @IsOptional() @IsMongoId() @ApiProperty({ type: String, required: false, nullable: true }) - toponyme?: Types.ObjectId; + toponymeId?: string; @IsOptional() @Validate(ValidatorBal, ['cad_parcelles']) diff --git a/apps/api/src/modules/numeros/dto/delete_batch_numero.dto.ts b/apps/api/src/modules/numeros/dto/delete_batch_numero.dto.ts index ca020f5e..c877ac6f 100644 --- a/apps/api/src/modules/numeros/dto/delete_batch_numero.dto.ts +++ b/apps/api/src/modules/numeros/dto/delete_batch_numero.dto.ts @@ -1,10 +1,9 @@ import { ApiProperty } from '@nestjs/swagger'; -import { IsMongoId, ArrayNotEmpty } from 'class-validator'; -import { Types } from 'mongoose'; +import { ArrayNotEmpty, IsMongoId } from 'class-validator'; export class DeleteBatchNumeroDTO { @ArrayNotEmpty() @IsMongoId({ each: true }) @ApiProperty({ type: String, required: true, nullable: false, isArray: true }) - numerosIds?: Types.ObjectId[]; + numerosIds?: string[]; } diff --git a/apps/api/src/modules/numeros/dto/update_batch_numero.dto.ts b/apps/api/src/modules/numeros/dto/update_batch_numero.dto.ts index 480e27f7..b84a67a4 100644 --- a/apps/api/src/modules/numeros/dto/update_batch_numero.dto.ts +++ b/apps/api/src/modules/numeros/dto/update_batch_numero.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { Types } from 'mongoose'; import { IsMongoId, ValidateNested, @@ -14,7 +13,7 @@ export class UpdateBatchNumeroDTO { @IsOptional() @IsMongoId({ each: true }) @ApiProperty({ type: String, required: true, nullable: false, isArray: true }) - numerosIds: Types.ObjectId[]; + numerosIds: string[]; @IsNotEmptyObject() @ValidateNested() diff --git a/apps/api/src/modules/numeros/dto/update_batch_numero_change.dto.ts b/apps/api/src/modules/numeros/dto/update_batch_numero_change.dto.ts index 0f2bdfe5..f9034b80 100644 --- a/apps/api/src/modules/numeros/dto/update_batch_numero_change.dto.ts +++ b/apps/api/src/modules/numeros/dto/update_batch_numero_change.dto.ts @@ -1,14 +1,14 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Types } from 'mongoose'; import { MaxLength, - IsMongoId, IsOptional, IsEnum, IsNotEmpty, + IsMongoId, + ValidateIf, } from 'class-validator'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; +import { PositionTypeEnum } from '@/shared/entities/position.entity'; export class UpdateBatchNumeroChangeDTO { @IsOptional() @@ -19,13 +19,14 @@ export class UpdateBatchNumeroChangeDTO { @IsOptional() @IsMongoId() @ApiProperty({ type: String, required: false, nullable: true }) - toponyme?: Types.ObjectId; + toponymeId?: string; - @IsOptional() - @IsNotEmpty() @IsMongoId() + @ValidateIf((object, value) => { + return value !== undefined; + }) @ApiProperty({ type: String, required: false, nullable: false }) - voie?: Types.ObjectId; + voieId?: string; @IsOptional() @IsNotEmpty() diff --git a/apps/api/src/modules/numeros/dto/update_numero.dto.ts b/apps/api/src/modules/numeros/dto/update_numero.dto.ts index 5b50714b..ab83d4bd 100644 --- a/apps/api/src/modules/numeros/dto/update_numero.dto.ts +++ b/apps/api/src/modules/numeros/dto/update_numero.dto.ts @@ -1,16 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; -import { Types } from 'mongoose'; import { MaxLength, - IsMongoId, Validate, IsOptional, ValidateNested, ArrayNotEmpty, + IsMongoId, } from 'class-validator'; -import { Position } from '@/shared/schemas/position.schema'; +import { Position } from '@/shared/entities/position.entity'; import { ValidatorBal } from '@/shared/validators/validator_bal.validator'; export class UpdateNumeroDTO { @@ -32,12 +31,12 @@ export class UpdateNumeroDTO { @IsOptional() @IsMongoId() @ApiProperty({ type: String, required: false, nullable: true }) - toponyme?: Types.ObjectId; + toponymeId?: string; @IsOptional() @IsMongoId() @ApiProperty({ type: String, required: false, nullable: false }) - voie?: Types.ObjectId; + voieId?: string; @IsOptional() @Validate(ValidatorBal, ['cad_parcelles']) diff --git a/apps/api/src/modules/numeros/numero.controller.ts b/apps/api/src/modules/numeros/numero.controller.ts index dcb96e62..09cec6ab 100644 --- a/apps/api/src/modules/numeros/numero.controller.ts +++ b/apps/api/src/modules/numeros/numero.controller.ts @@ -19,7 +19,7 @@ import { ApiBearerAuth, } from '@nestjs/swagger'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; +import { Numero } from '@/shared/entities/numero.entity'; import { filterSensitiveFields } from '@/shared/utils/numero.utils'; import { CustomRequest } from '@/lib/types/request.type'; @@ -41,9 +41,7 @@ export class NumeroController { @ApiResponse({ status: HttpStatus.OK, type: Numero }) @ApiBearerAuth('admin-token') find(@Req() req: CustomRequest, @Res() res: Response) { - const numero: Numero = ( - filterSensitiveFields(req.numero, !req.isAdmin) - ); + const numero: Numero = filterSensitiveFields(req.numero, !req.isAdmin); res.status(HttpStatus.OK).json(numero); } @@ -79,8 +77,8 @@ export class NumeroController { @ApiBearerAuth('admin-token') @UseGuards(AdminGuard) async softDelete(@Req() req: CustomRequest, @Res() res: Response) { - const result = await this.numeroService.softDelete(req.numero); - res.status(HttpStatus.OK).json(result); + await this.numeroService.softDelete(req.numero); + res.sendStatus(HttpStatus.NO_CONTENT); } @Delete(':numeroId') diff --git a/apps/api/src/modules/numeros/numero.middleware.ts b/apps/api/src/modules/numeros/numero.middleware.ts index 7d10efa7..95430ab3 100644 --- a/apps/api/src/modules/numeros/numero.middleware.ts +++ b/apps/api/src/modules/numeros/numero.middleware.ts @@ -1,13 +1,13 @@ import { Injectable, NestMiddleware, Inject, forwardRef } from '@nestjs/common'; import { Response, NextFunction } from 'express'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { CustomRequest } from '@/lib/types/request.type'; +import { isAdmin } from '@/lib/utils/is-admin.utils'; import { NumeroService } from '@/modules/numeros/numero.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { isAdmin } from '@/lib/utils/is-admin.utils'; @Injectable() export class NumeroMiddleware implements NestMiddleware { @@ -22,7 +22,7 @@ export class NumeroMiddleware implements NestMiddleware { if (numeroId) { const numero: Numero = await this.numeroService.findOneOrFail(numeroId); const basesLocale: BaseLocale = - await this.baseLocaleService.findOneOrFail(numero._bal.toString()); + await this.baseLocaleService.findOneOrFail(numero.balId.toString()); req.baseLocale = basesLocale; req.numero = numero; diff --git a/apps/api/src/modules/numeros/numero.module.ts b/apps/api/src/modules/numeros/numero.module.ts index 03eaa8b9..7bd6a178 100644 --- a/apps/api/src/modules/numeros/numero.module.ts +++ b/apps/api/src/modules/numeros/numero.module.ts @@ -1,7 +1,8 @@ import { Module, MiddlewareConsumer, forwardRef } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { Numero, NumeroSchema } from '@/shared/schemas/numero/numero.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Position } from '@/shared/entities/position.entity'; import { NumeroService } from '@/modules/numeros/numero.service'; import { NumeroController } from '@/modules/numeros/numero.controller'; @@ -13,7 +14,7 @@ import { TilesModule } from '@/modules/base_locale/sub_modules/tiles/tiles.modul @Module({ imports: [ - MongooseModule.forFeature([{ name: Numero.name, schema: NumeroSchema }]), + TypeOrmModule.forFeature([Numero, Position]), forwardRef(() => VoieModule), forwardRef(() => ToponymeModule), forwardRef(() => BaseLocaleModule), diff --git a/apps/api/src/modules/numeros/numero.service.ts b/apps/api/src/modules/numeros/numero.service.ts index 0377f208..27536637 100644 --- a/apps/api/src/modules/numeros/numero.service.ts +++ b/apps/api/src/modules/numeros/numero.service.ts @@ -5,16 +5,27 @@ import { Inject, forwardRef, } from '@nestjs/common'; -import { FilterQuery, Model, ProjectionType, SortOrder, Types } from 'mongoose'; -import { InjectModel } from '@nestjs/mongoose'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + DeleteResult, + FindOptionsOrder, + FindOptionsRelations, + FindOptionsSelect, + FindOptionsWhere, + In, + Point, + Repository, + UpdateResult, +} from 'typeorm'; import { v4 as uuid } from 'uuid'; -import { omit, uniq, chunk } from 'lodash'; +import { pick, chunk } from 'lodash'; +import { ObjectId } from 'mongodb'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { NumeroPopulate } from '@/shared/schemas/numero/numero.populate'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { normalizeSuffixe } from '@/shared/utils/numero.utils'; +import { Position } from '@/shared/entities/position.entity'; import { UpdateNumeroDTO } from '@/modules/numeros/dto/update_numero.dto'; import { CreateNumeroDTO } from '@/modules/numeros/dto/create_numero.dto'; @@ -22,17 +33,16 @@ import { UpdateBatchNumeroDTO } from '@/modules/numeros/dto/update_batch_numero. import { DeleteBatchNumeroDTO } from '@/modules/numeros/dto/delete_batch_numero.dto'; import { VoieService } from '@/modules/voie/voie.service'; import { ToponymeService } from '@/modules/toponyme/toponyme.service'; -import { TilesService } from '@/modules/base_locale/sub_modules/tiles/tiles.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { calcMetaTilesNumero } from '../base_locale/sub_modules/tiles/utils/tiles.utils'; import { BatchNumeroResponseDTO } from './dto/batch_numero_response.dto'; @Injectable() export class NumeroService { constructor( - @InjectModel(Numero.name) private numeroModel: Model, - @Inject(forwardRef(() => TilesService)) - private tilesService: TilesService, + @InjectRepository(Numero) + private numerosRepository: Repository, + @InjectRepository(Position) + private positionsRepository: Repository, @Inject(forwardRef(() => VoieService)) private voieService: VoieService, @Inject(forwardRef(() => ToponymeService)) @@ -42,163 +52,210 @@ export class NumeroService { ) {} async findOneOrFail(numeroId: string): Promise { - const filter = { - _id: numeroId, + // Créer le filtre where et lance la requète postgres + const where: FindOptionsWhere = { + id: numeroId, }; - const numero = await this.numeroModel - .findOne(filter) - .lean({ virtuals: true }) - .exec(); + const numero = await this.numerosRepository.findOne({ + where, + withDeleted: true, + }); + // Si len numero n'existe pas, on throw une erreur if (!numero) { throw new HttpException( `Numero ${numeroId} not found`, HttpStatus.NOT_FOUND, ); } - return numero; } - public async findManyPopulateVoie( - filters: FilterQuery, - ): Promise { - return this.numeroModel - .find({ ...filters, _deleted: null }) - .populate>('voie') - .exec(); - } - async findMany( - filter: FilterQuery, - projection: ProjectionType = null, - sort: { [key: string]: SortOrder } = null, + where: FindOptionsWhere, + select?: FindOptionsSelect, + order?: FindOptionsOrder, + relations?: FindOptionsRelations, + withDeleted?: boolean, ): Promise { - const query = this.numeroModel.find(filter); - if (projection) { - query.projection(projection); - query.sort(); - } - if (sort) { - query.sort(sort); - } + // Get les numeros en fonction du where, select, order et des relations + return this.numerosRepository.find({ + where, + ...(select && { select }), + ...(order && { order }), + ...(relations && { relations }), + ...(withDeleted && { withDeleted }), + }); + } - return query.lean({ virtuals: true }).exec(); + async findManyWithDeleted( + where: FindOptionsWhere, + ): Promise { + // Get les numeros en fonction du where archivé ou non + return this.numerosRepository.find({ + where, + withDeleted: true, + }); } async findDistinct( - filter: FilterQuery, + where: FindOptionsWhere, field: string, ): Promise { - return this.numeroModel.distinct(field, filter).exec(); + // Get la liste distinct du field dans l'enssemble where + const res = await this.numerosRepository + .createQueryBuilder() + .select(field) + .distinctOn([field]) + .where(where) + .withDeleted() + .getRawMany(); + return res.map((raw) => raw[field]); } - public async updateMany( - filters: FilterQuery, - update: Partial, - ): Promise { - return this.numeroModel.updateMany(filters, { $set: update }); + async findDistinctParcelles(balId: string): Promise { + const res: any[] = await this.numerosRepository.query( + `SELECT ARRAY_AGG(distinct elem) + FROM (select unnest(parcelles) as elem, bal_id, deleted_at from numeros) s + WHERE bal_id = '${balId}' AND deleted_at IS null`, + ); + return res[0]?.array_agg || []; } - public deleteMany(filters: FilterQuery): Promise { - return this.numeroModel.deleteMany(filters); + async findManyWherePositionInBBox( + balId: string, + bbox: number[], + ): Promise { + // Requète postgis qui permet de récupèré les voie dont le centroid est dans la bbox + const query = this.numerosRepository + .createQueryBuilder('numeros') + .leftJoinAndSelect('numeros.positions', 'positions') + .where('numeros.balId = :balId', { balId }) + .andWhere( + 'positions.point @ ST_MakeEnvelope(:xmin, :ymin, :xmax, :ymax, 4326)', + { + xmin: bbox[0], + ymin: bbox[1], + xmax: bbox[2], + ymax: bbox[3], + }, + ); + return query.getMany(); } - public async count(filters: FilterQuery): Promise { - return this.numeroModel.countDocuments(filters); + public async count(where: FindOptionsWhere): Promise { + return this.numerosRepository.count({ where }); } - async importMany(baseLocale: BaseLocale, rawNumeros: any[]): Promise { - const numeros = rawNumeros - .map((rawNumero) => { - if (!rawNumero.commune || !rawNumero.voie || !rawNumero.numero) { - return null; - } - - const numero = { - _bal: baseLocale._id, - banId: rawNumero.banId || uuid(), - numero: rawNumero.numero, - comment: rawNumero.comment, - toponyme: rawNumero.toponyme, - commune: rawNumero.commune, - voie: rawNumero.voie, - ...(rawNumero.suffixe && { - suffixe: normalizeSuffixe(rawNumero.suffixe), - }), - positions: rawNumero.positions || [], - parcelles: rawNumero.parcelles || [], - certifie: rawNumero.certifie || false, - } as Partial; - - calcMetaTilesNumero(numero); - - if (rawNumero._updated && rawNumero._created) { - numero._created = rawNumero._created; - numero._updated = rawNumero._updated; - } - - return numero; - }) - .filter(Boolean); + public async updateMany( + where: FindOptionsWhere, + update: Partial, + ): Promise { + return this.numerosRepository.update(where, update); + } + async importMany( + baseLocale: BaseLocale, + rawNumeros: Partial[], + ): Promise { + // On transforme les raw en numeros + const numeros = rawNumeros + // On garde seulement les numeros qui ont une voie et un numero + .filter(({ voieId, numero }) => Boolean(voieId && numero)) + .map((rawNumero) => ({ + id: rawNumero.id, + balId: baseLocale.id, + banId: rawNumero.banId || uuid(), + numero: rawNumero.numero, + comment: rawNumero.comment, + toponymeId: rawNumero.toponymeId, + voieId: rawNumero.voieId, + ...(rawNumero.suffixe && { + suffixe: normalizeSuffixe(rawNumero.suffixe), + }), + parcelles: rawNumero.parcelles || [], + certifie: rawNumero.certifie || false, + ...(rawNumero.updatedAt && { updatedAt: rawNumero.updatedAt }), + ...(rawNumero.createdAt && { createdAt: rawNumero.createdAt }), + })); + // On ne retourne rien si il n'y a pas de numeros a insert if (numeros.length === 0) { return; } - - // INSERT NUMEROS BY CHUNK OF 500 - // TO LIMIT MEMORY USAGE + // On insert les numeros 500 par 500 for (const numerosChunk of chunk(numeros, 500)) { - await this.numeroModel.insertMany(numerosChunk); + await this.numerosRepository + .createQueryBuilder() + .insert() + .into(Numero) + .values(numerosChunk) + .execute(); + } + // On créer les positions + const positions: Partial[] = []; + for (const rawNumero of rawNumeros) { + let rank = 0; + for (const { source, type, point } of rawNumero.positions) { + positions.push({ + id: new ObjectId().toHexString(), + numeroId: rawNumero.id, + source, + type, + point, + rank, + }); + rank++; + } + } + // On insert les positions 500 par 500 + for (const positionsChunk of chunk(positions, 500)) { + await this.numerosRepository + .createQueryBuilder() + .insert() + .into(Position) + .values(positionsChunk) + .execute(); } - // UPDATE TILES OF VOIES - const voieIds: string[] = uniq(numeros.map((n) => n.voie.toString())); - await this.voieService.updateTiles(voieIds); } public async create( voie: Voie, createNumeroDto: CreateNumeroDTO, ): Promise { - // CHECK IF VOIE EXIST - if (voie._deleted) { + // On vérifie que la voie ne soit pas archivé + if (voie.deletedAt) { throw new HttpException('Voie is archived', HttpStatus.NOT_FOUND); } - - // CHECK IF TOPO EXIST + // Si il y a un toponyme, on vérifie qu'il existe if ( - createNumeroDto.toponyme && - !(await this.toponymeService.isToponymeExist(createNumeroDto.toponyme)) + createNumeroDto.toponymeId && + !(await this.toponymeService.isToponymeExist(createNumeroDto.toponymeId)) ) { throw new HttpException('Toponyme not found', HttpStatus.NOT_FOUND); } - - // CREATE NUMERO + // On créer l'object numéro const numero: Partial = { - _bal: voie._bal, + balId: voie.balId, banId: uuid(), - commune: voie.commune, - voie: voie._id, + voieId: voie.id, numero: createNumeroDto.numero, suffixe: createNumeroDto.suffixe ? normalizeSuffixe(createNumeroDto.suffixe) : null, - toponyme: createNumeroDto.toponyme - ? new Types.ObjectId(createNumeroDto.toponyme) - : null, + toponymeId: createNumeroDto.toponymeId || null, positions: createNumeroDto.positions || [], comment: createNumeroDto.comment || null, parcelles: createNumeroDto.parcelles || [], certifie: createNumeroDto.certifie || false, }; - // SET TILES - this.tilesService.calcMetaTilesNumero(numero); - // REQUEST CREATE NUMERO - const numeroCreated: Numero = await this.numeroModel.create(numero); - // UPDATE TILES VOIE - await this.tilesService.updateVoieTiles(voie); - // SET _updated VOIE, TOPONYME AND BAL - await this.touch(numeroCreated, numeroCreated._updated); - + // Créer l'entité typeorm + const entityToSave: Numero = this.numerosRepository.create(numero); + // On insert l'object dans postgres + const numeroCreated: Numero = + await this.numerosRepository.save(entityToSave); + // On calcule le centroid de la voie + await this.voieService.calcCentroid(voie.id); + // On met a jour le updatedAt de la Bal + await this.baseLocaleService.touch(numero.balId); return numeroCreated; } @@ -206,216 +263,223 @@ export class NumeroService { numero: Numero, updateNumeroDto: UpdateNumeroDTO, ): Promise { - // CHECK IF VOIE EXIST + // Si il y a un changement de voie, on vérifie que cette derniere existe if ( - updateNumeroDto.voie && - !(await this.voieService.isVoieExist(updateNumeroDto.voie)) + updateNumeroDto.voieId && + !(await this.voieService.isVoieExist(updateNumeroDto.voieId)) ) { throw new HttpException('Voie not found', HttpStatus.NOT_FOUND); } - - // CHECK IF TOPO EXIST + // Si il y a un changement de toponyme, on vérifie que ce dernier existe if ( - updateNumeroDto.toponyme && - !(await this.toponymeService.isToponymeExist(updateNumeroDto.toponyme)) + updateNumeroDto.toponymeId && + !(await this.toponymeService.isToponymeExist(updateNumeroDto.toponymeId)) ) { throw new HttpException('Toponyme not found', HttpStatus.NOT_FOUND); } - - // SET TILES IF POSITIONS CHANGE - if (updateNumeroDto.positions) { - this.tilesService.calcMetaTilesNumero(updateNumeroDto); - } - - // SET SUFFIXE IF CHANGE + // On normalize le suffix if (updateNumeroDto.suffixe) { updateNumeroDto.suffixe = normalizeSuffixe(updateNumeroDto.suffixe); } + // On update le numéro dans postgres + const numeroToSave: Numero = this.numerosRepository.create({ + id: numero.id, + ...updateNumeroDto, + updatedAt: new Date(), + }); - // REQUEST UPDATE NUMERO - const numeroUpdated: Numero = await this.numeroModel.findOneAndUpdate( - { _id: numero._id, _deleted: null }, - { $set: { ...updateNumeroDto, _updated: new Date() } }, - { new: true }, - ); + await this.numerosRepository.save(numeroToSave); + const numeroUpdated: Numero = await this.numerosRepository.findOneBy({ + id: numero.id, + }); + // Si le numero a été modifié + if (updateNumeroDto.voieId) { + // On recalcule le centroid de l'ancienne et la nouvelle voie si le numero a changé de voie + await this.voieService.calcCentroid(numero.voieId); + await this.voieService.calcCentroid(numeroUpdated.voieId); + } else if (updateNumeroDto.positions) { + // On recalcule le centroid de la voie si les positions du numeros on changé + await this.voieService.calcCentroid(numero.voieId); + } + // On met a jour le updatedAt de la voie + await this.voieService.touch(numero.voieId); + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(numero.balId); - if (numeroUpdated) { - // UPDATE TILES VOIE IF VOIE OR POSITIONS CHANGE - if (updateNumeroDto.voie) { - await this.tilesService.updateVoiesTiles([ - numero.voie, - numeroUpdated.voie, - ]); - await this.voieService.touch(numero.voie, numeroUpdated._updated); - } else if (updateNumeroDto.positions) { - await this.tilesService.updateVoiesTiles([numero.voie]); - } + return numeroUpdated; + } - // SET _updated VOIE, TOPONYME AND BAL - await this.touch(numeroUpdated, numeroUpdated._updated); + public async delete(numero: Numero): Promise { + // On créer le where et on lance la requète + const where: FindOptionsWhere = { + id: numero.id, + }; + const { affected }: DeleteResult = + await this.numerosRepository.delete(where); + // Si le numero a été suprimé + if (affected > 0) { + // On met a jour le updatedAt de la bal, la voie et le toponyme + await this.touch(numero); } + } - return numeroUpdated; + public async deleteMany(where: FindOptionsWhere) { + // On supprime les numero + await this.numerosRepository.delete(where); } - public async delete(numero: Numero) { - const { deletedCount } = await this.numeroModel.deleteOne({ - _id: numero._id, + public async softDelete(numero: Numero): Promise { + // On créer le where et on lance la requète + const { affected }: UpdateResult = await this.numerosRepository.softDelete({ + id: numero.id, }); - if (deletedCount >= 1) { - // UPDATE TILES VOIE - await this.tilesService.updateVoiesTiles([numero.voie]); - // SET _updated VOIE, TOPONYME AND BAL + // Si le numero a été suprimé + if (affected > 0) { + // On recalcule le centroid de la voie du numéro + await this.voieService.calcCentroid(numero.voieId); + // On met a jour le updatedAt de la bal, la voie et le toponyme await this.touch(numero); } } - public async softDelete(numero: Numero): Promise { - const numeroUpdated: Numero = await this.numeroModel.findOneAndUpdate( - { _id: numero._id }, - { $set: { _deleted: new Date() } }, - { new: true }, - ); - - // UPDATE TILES VOIE - await this.tilesService.updateVoiesTiles([numeroUpdated.voie]); - // SET _updated VOIE, TOPONYME AND BAL - await this.touch(numero); + public async softDeleteByVoie(voieId: string): Promise { + await this.numerosRepository.softDelete({ + voieId, + }); + } - return numeroUpdated; + public async restore(where: FindOptionsWhere): Promise { + await this.numerosRepository.restore(where); } public async toggleCertifieNumeros( baseLocale: BaseLocale, certifie: boolean, ): Promise { - const numeros = await this.findMany( - { _bal: baseLocale._id, certifie: !certifie, _deleted: null }, - { _id: 1 }, - ); - const numerosIds = numeros.map((n) => n._id); - await this.numeroModel.updateMany( - { _id: { $in: numerosIds } }, - { $set: { certifie, _updated: new Date() } }, - ); - await this.baseLocaleService.touch(baseLocale._id); + const numeros = await this.findMany({ + balId: baseLocale.id, + certifie: !certifie, + }); + const numerosIds = numeros.map((n) => n.id); + await this.numerosRepository.update({ id: In(numerosIds) }, { certifie }); + await this.baseLocaleService.touch(baseLocale.id); } public async updateBatch( baseLocale: BaseLocale, { numerosIds, changes }: UpdateBatchNumeroDTO, ): Promise { - if (changes.voie === null) { - delete changes.voie; - } - - if (changes.toponyme === null) { - delete changes.toponyme; - } - - const { voieIds, toponymeIds } = - await this.getDistinctVoiesAndToponymesByNumeroIds( - numerosIds, - baseLocale._id, - ); - // CHECK IF VOIE EXIST (IN BAL) + // On récupère les différentes voies et toponymes des numeros qu'on va modifier + const where: FindOptionsWhere = { + id: In(numerosIds), + balId: baseLocale.id, + }; + const voieIds: string[] = await this.findDistinct(where, 'voie_id'); + const toponymeIds: string[] = await this.findDistinct(where, 'toponyme_id'); + // Si la voie des numéro est changé, on vérifie que cette derniere existe bien if ( - changes.voie && - !(await this.voieService.isVoieExist(changes.voie, baseLocale._id)) + changes.voieId && + !(await this.voieService.isVoieExist(changes.voieId, baseLocale.id)) ) { throw new HttpException('Voie not found', HttpStatus.NOT_FOUND); } - // CHECK IF TOPO EXIST (IN BAL) + // Si le toponyme des numéro est changé, on vérifie que cet dernier existe bien if ( - changes.toponyme && + changes.toponymeId && !(await this.toponymeService.isToponymeExist( - changes.toponyme, - baseLocale._id, + changes.toponymeId, + baseLocale.id, )) ) { throw new HttpException('Toponyme not found', HttpStatus.NOT_FOUND); } - - // CREATE BATCH CHANGES + // On créer le batch (en omettant positionType qui n'existe pas dans numero) const batchChanges: Partial = { - ...omit(changes, 'positionType'), + ...(changes.voieId && { voieId: changes.voieId }), + ...(changes.toponymeId !== undefined && { + toponymeId: changes.toponymeId, + }), + ...pick(changes, ['comment', 'certifie']), }; - + // Si le positionType est changé, on change le type de la première position dans le batch + let positionTypeAffected: number = 0; if (changes.positionType) { - batchChanges['positions.0.type'] = changes.positionType; + const { affected }: UpdateResult = await this.positionsRepository.update( + { numeroId: In(numerosIds), rank: 0 }, + { type: changes.positionType }, + ); + positionTypeAffected = affected; } - - // UPDATE NUMEROS - const { modifiedCount } = await this.numeroModel.updateMany( + // On lance la requète + const { affected }: UpdateResult = await this.numerosRepository.update( { - _id: { $in: numerosIds }, - _bal: baseLocale._id, - _deleted: null, + id: In(numerosIds), + balId: baseLocale.id, }, - { $set: { ...batchChanges, _updated: new Date() } }, + batchChanges, ); - if (modifiedCount > 0) { - await this.baseLocaleService.touch(baseLocale._id); - // UPDATES VOIE DOCUMENTS - if (voieIds.length > 0) { - // SET _updated VOIES + // Si il y a plus d'un numéro qui a changé + if (affected > 0 || positionTypeAffected > 0) { + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(baseLocale.id); + // Si la voie a changé + if (changes.voieId) { + // On met a jour le updatedAt de la BAL + await this.voieService.touch(changes.voieId); + // On recalcule tous les centroid des voies + await Promise.all( + voieIds.map((voieId) => this.voieService.calcCentroid(voieId)), + ); + await this.voieService.calcCentroid(changes.voieId); + } else { + // Sinon on met a jour les updatedAt des voies des numeros await Promise.all( voieIds.map((voieId) => this.voieService.touch(voieId)), ); } - if (changes.voie) { - await this.voieService.touch(changes.voie); - // UPDATE TILES OF VOIES IF VOIE OF NUMERO CHANGE - await this.tilesService.updateVoiesTiles([...voieIds, changes.voie]); + // Si on change le toponyme on met a jour son updatedAt + if (changes.toponymeId) { + await this.toponymeService.touch(changes.toponymeId); } - // UPDATE DOCUMENTS TOPONYMES + // Si les numeros avaient des toponyme, on met a jour leurs updatedAt if (toponymeIds.length > 0) { - // SET _updated TOPONYMES await Promise.all( toponymeIds.map((toponymeId) => this.toponymeService.touch(toponymeId), ), ); } - if (changes.toponyme) { - await this.toponymeService.touch(changes.toponyme); - } } - return { modifiedCount, changes }; + return { modifiedCount: affected, changes }; } public async softDeleteBatch( baseLocale: BaseLocale, { numerosIds }: DeleteBatchNumeroDTO, ): Promise { - const { voieIds, toponymeIds } = - await this.getDistinctVoiesAndToponymesByNumeroIds( - numerosIds, - baseLocale._id, + // On récupère les différentes voies et toponymes des numeros qu'on va modifier + const where: FindOptionsWhere = { + id: In(numerosIds), + balId: baseLocale.id, + }; + const voieIds: string[] = await this.findDistinct(where, 'voie_id'); + const toponymeIds: string[] = await this.findDistinct(where, 'toponyme_id'); + // On archive les numeros dans postgres + const { affected }: UpdateResult = await this.numerosRepository.softDelete({ + id: In(numerosIds), + balId: baseLocale.id, + }); + // Si des numeros ont été archivés + if (affected > 0) { + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(baseLocale.id); + // On met a jour les centroid des voies des numeros archivé + await Promise.all( + voieIds.map((voidId) => this.voieService.calcCentroid(voidId)), ); - - // REQUEST SOFT DELETE NUMEROS - const { modifiedCount } = await this.numeroModel.updateMany( - { - _id: { $in: numerosIds }, - _bal: baseLocale._id, - }, - { $set: { _updated: new Date(), _deleted: new Date() } }, - ); - - // UPDATE VOIE AND TOPONYME IF NUMEROS WERE SOFT DELETE - if (modifiedCount > 0) { - await this.baseLocaleService.touch(baseLocale._id); - // SET _updated AND tiles OF VOIES - if (voieIds.length > 0) { - await Promise.all( - voieIds.map((voieId) => this.voieService.touch(voieId)), - ); - await this.tilesService.updateVoiesTiles(voieIds); - } - // SET _updated OF TOPONYMES + // Si les numeros avaient des toponyme, on met a jour leurs updatedAt if (toponymeIds.length > 0) { await Promise.all( toponymeIds.map((toponymeId) => @@ -425,36 +489,34 @@ export class NumeroService { } } - return { modifiedCount }; + return { modifiedCount: affected }; } public async deleteBatch( baseLocale: BaseLocale, { numerosIds }: DeleteBatchNumeroDTO, - ): Promise { - const { voieIds, toponymeIds } = - await this.getDistinctVoiesAndToponymesByNumeroIds( - numerosIds, - baseLocale._id, - ); - - // REQUEST DELETE NUMEROS - const { deletedCount } = await this.numeroModel.deleteMany({ - _id: { $in: numerosIds }, - _bal: baseLocale._id, + ): Promise { + // On récupère les différentes voies et toponymes des numeros qu'on va modifier + const where: FindOptionsWhere = { + id: In(numerosIds), + balId: baseLocale.id, + }; + const voieIds: string[] = await this.findDistinct(where, 'voie_id'); + const toponymeIds: string[] = await this.findDistinct(where, 'toponyme_id'); + // On supprime les numero dans postgres + const { affected }: DeleteResult = await this.numerosRepository.delete({ + id: In(numerosIds), + balId: baseLocale.id, }); - - // UPDATE VOIE AND TOPONYME IF NUMEROS WERE SOFT DELETE - if (deletedCount > 0) { - await this.baseLocaleService.touch(baseLocale._id); - // SET _updated AND tiles OF VOIES - if (voieIds.length > 0) { - await Promise.all( - voieIds.map((voieId) => this.voieService.touch(voieId)), - ); - await this.tilesService.updateVoiesTiles(voieIds); - } - // SET _updated OF TOPONYMES + // Si des numeros ont été supprimé + if (affected > 0) { + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(baseLocale.id); + // On met a jour les updatedAt des voies des numeros archivé + await Promise.all( + voieIds.map((voieId) => this.voieService.touch(voieId)), + ); + // Si les numeros avaient des toponyme, on met a jour leurs updatedAt if (toponymeIds.length > 0) { await Promise.all( toponymeIds.map((toponymeId) => @@ -463,33 +525,23 @@ export class NumeroService { ); } } - return { deletedCount }; } - private async getDistinctVoiesAndToponymesByNumeroIds( - numeroIds: Types.ObjectId[], - _bal: Types.ObjectId, - ): Promise<{ voieIds: Types.ObjectId[]; toponymeIds: Types.ObjectId[] }> { - const voieIds = await this.numeroModel.distinct('voie', { - _id: { $in: numeroIds }, - _bal, - _deleted: null, - }); - - const toponymeIds = await this.numeroModel.distinct('toponyme', { - _id: { $in: numeroIds }, - _bal, - _deleted: null, - }); - - return { voieIds, toponymeIds }; + public async findCentroid(voieId: string): Promise { + const res: { st_asgeojson: string }[] = await this.numerosRepository + .createQueryBuilder('numeros') + .select('ST_AsGeoJSON(st_centroid(st_union(positions.point)))') + .leftJoin('numeros.positions', 'positions') + .where('numeros.voie_id = :voieId', { voieId }) + .execute(); + return JSON.parse(res[0].st_asgeojson); } - async touch(numero: Numero, _updated: Date = new Date()) { - await this.voieService.touch(numero.voie, _updated); - if (numero.toponyme) { - await this.toponymeService.touch(numero.toponyme, _updated); + async touch(numero: Numero, updatedAt: Date = new Date()) { + if (numero.toponymeId) { + await this.toponymeService.touch(numero.toponymeId, updatedAt); } - await this.baseLocaleService.touch(numero._bal, _updated); + await this.voieService.touch(numero.voieId, updatedAt); + await this.baseLocaleService.touch(numero.balId, updatedAt); } } diff --git a/apps/api/src/modules/stats/dto/bases_locales_status.dto.ts b/apps/api/src/modules/stats/dto/bases_locales_status.dto.ts index 9fd349da..c1269982 100644 --- a/apps/api/src/modules/stats/dto/bases_locales_status.dto.ts +++ b/apps/api/src/modules/stats/dto/bases_locales_status.dto.ts @@ -1,9 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; -import { Types } from 'mongoose'; export class BasesLocalesStatusDTO { @ApiProperty({ type: String }) - status: Types.ObjectId; + status: string; @ApiProperty() count: number; diff --git a/apps/api/src/modules/stats/stats.controller.ts b/apps/api/src/modules/stats/stats.controller.ts index 28b36535..512bf266 100644 --- a/apps/api/src/modules/stats/stats.controller.ts +++ b/apps/api/src/modules/stats/stats.controller.ts @@ -16,7 +16,7 @@ import { } from '@nestjs/swagger'; import { Response } from 'express'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { StatsService } from '@/modules/stats/stats.service'; import { CodeCommuneDTO } from '@/modules/stats/dto/code_commune.dto'; diff --git a/apps/api/src/modules/stats/stats.service.ts b/apps/api/src/modules/stats/stats.service.ts index be797320..775d2ed1 100644 --- a/apps/api/src/modules/stats/stats.service.ts +++ b/apps/api/src/modules/stats/stats.service.ts @@ -1,15 +1,17 @@ import { Injectable, Inject, forwardRef } from '@nestjs/common'; -import { FilterQuery, PipelineStage, Types } from 'mongoose'; import { groupBy, mapValues } from 'lodash'; import { format } from 'date-fns'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; import { filterSensitiveFields } from '@/modules/base_locale/utils/base_locale.utils'; import { BasesLocalesStatusDTO } from '@/modules/stats/dto/bases_locales_status.dto'; import { BasesLocalesCreationDTO } from '@/modules/stats/dto/bases_locales_creations.dto'; +import { Between, FindOptionsSelect, FindOptionsWhere, In, Not } from 'typeorm'; @Injectable() export class StatsService { @@ -22,50 +24,48 @@ export class StatsService { fields: string[] = [], codeCommunes: string[] = [], ): Promise[]> { - const filters: FilterQuery = { - status: { $ne: 'demo' }, - _deleted: null, + const where: FindOptionsWhere = { + status: Not(StatusBaseLocalEnum.DEMO), ...(codeCommunes && - codeCommunes.length > 0 && { commune: { $in: codeCommunes } }), + codeCommunes.length > 0 && { commune: In(codeCommunes) }), }; - const selector: Record = {}; + const select: FindOptionsSelect = { id: true }; if (fields.length > 0) { fields.forEach((f) => { - selector[f] = 1; + select[f] = 1; }); } const bals: BaseLocale[] = await this.baseLocaleService.findMany( - filters, - selector, + where, + select, ); return bals.map((bal) => filterSensitiveFields(bal)); } public async findBalsStatusRepartition(): Promise { - const aggregation: PipelineStage[] = [ - { $group: { _id: '$status', count: { $sum: 1 } } }, - ]; - const statusRepartition: { _id: Types.ObjectId; count: number }[] = - await this.baseLocaleService.aggregate(aggregation); - - return statusRepartition.map(({ _id, count }) => ({ status: _id, count })); + const result: any[] = await this.baseLocaleService.countGroupByStatus(); + return result.map(({ status, count }) => ({ + status, + count: Number(count), + })); } - public async findBalsCreationByDays(dates: { + public async findBalsCreationByDays({ + from, + to, + }: { from: Date; to: Date; }): Promise { - const filter: FilterQuery = { - _created: { - $gte: dates.from, - $lte: dates.to, - }, + const where: FindOptionsWhere = { + createdAt: Between(from, to), }; - const bals = await this.baseLocaleService.findMany(filter); - const balsGroupByDays: Record = groupBy(bals, (bal) => - format(bal._created, 'yyyy-MM-dd'), + const bals: BaseLocale[] = await this.baseLocaleService.findMany(where); + const balsGroupByDays: Record = groupBy( + bals, + (bal: BaseLocale) => format(bal.createdAt, 'yyyy-MM-dd'), ); return Object.entries(balsGroupByDays).map(([date, bals]) => { const balsGroupedByCommune = groupBy(bals, (bal) => bal.commune); diff --git a/apps/api/src/modules/toponyme/dto/create_toponyme.dto.ts b/apps/api/src/modules/toponyme/dto/create_toponyme.dto.ts index a8d9c9fc..558d3bcd 100644 --- a/apps/api/src/modules/toponyme/dto/create_toponyme.dto.ts +++ b/apps/api/src/modules/toponyme/dto/create_toponyme.dto.ts @@ -8,7 +8,7 @@ import { } from 'class-validator'; import { ValidatorBal } from '@/shared/validators/validator_bal.validator'; -import { Position } from '@/shared/schemas/position.schema'; +import { Position } from '@/shared/entities/position.entity'; export class CreateToponymeDTO { @Validate(ValidatorBal, ['nom']) diff --git a/apps/api/src/modules/toponyme/dto/extended_toponyme.dto.ts b/apps/api/src/modules/toponyme/dto/extended_toponyme.dto.ts index 12bbc380..31d62b71 100644 --- a/apps/api/src/modules/toponyme/dto/extended_toponyme.dto.ts +++ b/apps/api/src/modules/toponyme/dto/extended_toponyme.dto.ts @@ -1,8 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { BBox as BboxTurf } from '@turf/helpers'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Numero } from '@/shared/entities/numero.entity'; export class ExtentedToponymeDTO extends Toponyme { @ApiProperty() diff --git a/apps/api/src/modules/toponyme/dto/update_toponyme.dto.ts b/apps/api/src/modules/toponyme/dto/update_toponyme.dto.ts index bd7e3f6d..d6dfc902 100644 --- a/apps/api/src/modules/toponyme/dto/update_toponyme.dto.ts +++ b/apps/api/src/modules/toponyme/dto/update_toponyme.dto.ts @@ -8,7 +8,7 @@ import { } from 'class-validator'; import { ValidatorBal } from '@/shared/validators/validator_bal.validator'; -import { Position } from '@/shared/schemas/position.schema'; +import { Position } from '@/shared/entities/position.entity'; export class UpdateToponymeDTO { @IsOptional() diff --git a/apps/api/src/modules/toponyme/toponyme.controller.ts b/apps/api/src/modules/toponyme/toponyme.controller.ts index dc2575f4..667d269b 100644 --- a/apps/api/src/modules/toponyme/toponyme.controller.ts +++ b/apps/api/src/modules/toponyme/toponyme.controller.ts @@ -21,8 +21,7 @@ import { ApiBearerAuth, } from '@nestjs/swagger'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { NumeroPopulate } from '@/shared/schemas/numero/numero.populate'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; import { filterSensitiveFields } from '@/shared/utils/numero.utils'; import { CustomRequest } from '@/lib/types/request.type'; @@ -31,6 +30,7 @@ import { ToponymeService } from '@/modules/toponyme/toponyme.service'; import { ExtentedToponymeDTO } from '@/modules/toponyme/dto/extended_toponyme.dto'; import { UpdateToponymeDTO } from '@/modules/toponyme/dto/update_toponyme.dto'; import { NumeroService } from '@/modules/numeros/numero.service'; +import { Numero } from '@/shared/entities/numero.entity'; @ApiTags('toponymes') @Controller('toponymes') @@ -84,10 +84,8 @@ export class ToponymeController { @ApiBearerAuth('admin-token') @UseGuards(AdminGuard) async softDelete(@Req() req: CustomRequest, @Res() res: Response) { - const result: Toponyme = await this.toponymeService.softDelete( - req.toponyme, - ); - res.status(HttpStatus.OK).json(result); + await this.toponymeService.softDelete(req.toponyme); + res.sendStatus(HttpStatus.NO_CONTENT); } @Put(':toponymeId/restore') @@ -124,13 +122,17 @@ export class ToponymeController { operationId: 'findToponymeNumeros', }) @ApiParam({ name: 'toponymeId', required: true, type: String }) - @ApiResponse({ status: HttpStatus.OK, type: NumeroPopulate, isArray: true }) + @ApiResponse({ status: HttpStatus.OK, type: Numero, isArray: true }) @ApiBearerAuth('admin-token') async findByToponyme(@Req() req: CustomRequest, @Res() res: Response) { - const numeros: NumeroPopulate[] = - await this.numeroService.findManyPopulateVoie({ - toponyme: req.toponyme._id, - }); + const numeros: Numero[] = await this.numeroService.findMany( + { + toponymeId: req.toponyme.id, + }, + null, + null, + { voie: true }, + ); const result = numeros.map((n) => filterSensitiveFields(n, !req.isAdmin)); res.status(HttpStatus.OK).json(result); } diff --git a/apps/api/src/modules/toponyme/toponyme.middleware.ts b/apps/api/src/modules/toponyme/toponyme.middleware.ts index f4b4e283..82da3dc8 100644 --- a/apps/api/src/modules/toponyme/toponyme.middleware.ts +++ b/apps/api/src/modules/toponyme/toponyme.middleware.ts @@ -1,13 +1,13 @@ import { Injectable, NestMiddleware, forwardRef, Inject } from '@nestjs/common'; import { Response, NextFunction } from 'express'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { CustomRequest } from '@/lib/types/request.type'; +import { isAdmin } from '@/lib/utils/is-admin.utils'; import { ToponymeService } from '@/modules/toponyme/toponyme.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { isAdmin } from '@/lib/utils/is-admin.utils'; @Injectable() export class ToponymeMiddleware implements NestMiddleware { @@ -23,7 +23,7 @@ export class ToponymeMiddleware implements NestMiddleware { const toponyme: Toponyme = await this.toponymeService.findOneOrFail(toponymeId); const basesLocale: BaseLocale = - await this.baseLocaleService.findOneOrFail(toponyme._bal.toString()); + await this.baseLocaleService.findOneOrFail(toponyme.balId); req.baseLocale = basesLocale; req.toponyme = toponyme; diff --git a/apps/api/src/modules/toponyme/toponyme.module.ts b/apps/api/src/modules/toponyme/toponyme.module.ts index 61f87c3e..56c1ad81 100644 --- a/apps/api/src/modules/toponyme/toponyme.module.ts +++ b/apps/api/src/modules/toponyme/toponyme.module.ts @@ -1,10 +1,7 @@ import { MiddlewareConsumer, Module, forwardRef } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; +import { TypeOrmModule } from '@nestjs/typeorm'; -import { - Toponyme, - ToponymeSchema, -} from '@/shared/schemas/toponyme/toponyme.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; import { ToponymeController } from '@/modules/toponyme/toponyme.controller'; import { ToponymeService } from '@/modules/toponyme/toponyme.service'; @@ -14,9 +11,7 @@ import { BaseLocaleModule } from '@/modules/base_locale/base_locale.module'; @Module({ imports: [ - MongooseModule.forFeature([ - { name: Toponyme.name, schema: ToponymeSchema }, - ]), + TypeOrmModule.forFeature([Toponyme]), forwardRef(() => BaseLocaleModule), forwardRef(() => NumeroModule), ], diff --git a/apps/api/src/modules/toponyme/toponyme.service.ts b/apps/api/src/modules/toponyme/toponyme.service.ts index 59b953c4..43fa815e 100644 --- a/apps/api/src/modules/toponyme/toponyme.service.ts +++ b/apps/api/src/modules/toponyme/toponyme.service.ts @@ -5,31 +5,40 @@ import { Injectable, forwardRef, } from '@nestjs/common'; -import { FilterQuery, Model, ProjectionType, Types } from 'mongoose'; -import { InjectModel } from '@nestjs/mongoose'; import { groupBy } from 'lodash'; +import { + DeleteResult, + FindOptionsSelect, + FindOptionsWhere, + In, + Repository, + UpdateResult, +} from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import * as turf from '@turf/turf'; +import bbox from '@turf/bbox'; +import { Feature as FeatureTurf, BBox as BboxTurf } from '@turf/helpers'; import { v4 as uuid } from 'uuid'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { extendWithNumeros } from '@/shared/utils/numero.utils'; +import { Position } from '@/shared/entities/position.entity'; -import { ExtentedToponymeDTO } from '@/modules/toponyme/dto/extended_toponyme.dto'; import { cleanNom, cleanNomAlt, getNomAltDefault } from '@/lib/utils/nom.util'; +import { ExtentedToponymeDTO } from '@/modules/toponyme/dto/extended_toponyme.dto'; import { UpdateToponymeDTO } from '@/modules/toponyme/dto/update_toponyme.dto'; import { CreateToponymeDTO } from '@/modules/toponyme/dto/create_toponyme.dto'; import { NumeroService } from '@/modules/numeros/numero.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { extendWithNumeros } from '@/shared/utils/numero.utils'; -import { Position } from '@/shared/schemas/position.schema'; -import * as turf from '@turf/turf'; -import bbox from '@turf/bbox'; -import { Feature as FeatureTurf, BBox as BboxTurf } from '@turf/helpers'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; +import { ObjectId } from 'mongodb'; @Injectable() export class ToponymeService { constructor( - @InjectModel(Toponyme.name) private toponymeModel: Model, + @InjectRepository(Toponyme) + private toponymesRepository: Repository, @Inject(forwardRef(() => BaseLocaleService)) private baseLocaleService: BaseLocaleService, @Inject(forwardRef(() => NumeroService)) @@ -37,11 +46,15 @@ export class ToponymeService { ) {} async findOneOrFail(toponymeId: string): Promise { - const filter = { - _id: toponymeId, + // Créer le filtre where et lance la requète postgres + const where: FindOptionsWhere = { + id: toponymeId, }; - const toponyme = await this.toponymeModel.findOne(filter).lean().exec(); - + const toponyme = await this.toponymesRepository.findOne({ + where, + withDeleted: true, + }); + // Si le toponyme n'existe pas, on throw une erreur if (!toponyme) { throw new HttpException( `Toponyme ${toponymeId} not found`, @@ -53,48 +66,51 @@ export class ToponymeService { } async findMany( - filter: FilterQuery, - projection: ProjectionType = null, + where: FindOptionsWhere, + select?: FindOptionsSelect, ): Promise { - const query = this.toponymeModel.find(filter); - if (projection) { - query.projection(projection); - } - - return query.lean().exec(); + return this.toponymesRepository.find({ where, ...(select && { select }) }); } - async findDistinct( - filter: FilterQuery, - field: string, - ): Promise { - return this.toponymeModel.distinct(field, filter).exec(); + async findManyWithDeleted( + where: FindOptionsWhere, + ): Promise { + // Get les numeros en fonction du where archivé ou non + return this.toponymesRepository.find({ + where, + withDeleted: true, + }); } - public deleteMany(filters: FilterQuery): Promise { - return this.toponymeModel.deleteMany(filters); + async findDistinctParcelles(balId: string): Promise { + const res: any[] = await this.toponymesRepository.query( + `SELECT ARRAY_AGG(distinct elem) + FROM (select unnest(parcelles) as elem, bal_id, deleted_at from toponymes) s + WHERE bal_id = '${balId}' AND deleted_at IS null`, + ); + return res[0]?.array_agg || []; } async extendToponymes(toponymes: Toponyme[]): Promise { + // On recupère les numeros des toponymes + const toponymesIds: string[] = toponymes.map(({ id }) => id); const numeros = await this.numeroService.findMany({ - toponyme: { $in: toponymes.map(({ _id }) => _id) }, - _deleted: null, + toponymeId: In(toponymesIds), }); - - const numerosByToponymes = groupBy(numeros, 'toponyme'); - - return toponymes.map((voie) => ({ - ...extendWithNumeros(voie, numerosByToponymes[voie._id] || []), - bbox: this.getBBOX(voie, numerosByToponymes[voie._id] || []), + const numerosByToponymes = groupBy(numeros, 'toponymeId'); + // On renvoie les toponyme avec la bbox et les metas numeros + return toponymes.map((t) => ({ + ...extendWithNumeros(t, numerosByToponymes[t.id] || []), + bbox: this.getBBOX(t, numerosByToponymes[t.id] || []), })); } async extendToponyme(toponyme: Toponyme): Promise { + // On recupère les numeros du toponymes const numeros = await this.numeroService.findMany({ - toponyme: toponyme._id, - _deleted: null, + toponymeId: toponyme.id, }); - + // On renvoie le toponyme avec la bbox et les metas numeros return { ...extendWithNumeros(toponyme, numeros), bbox: this.getBBOX(toponyme, numeros), @@ -105,24 +121,25 @@ export class ToponymeService { bal: BaseLocale, createToponymeDto: CreateToponymeDTO | Partial, ): Promise { - // CREATE OBJECT TOPONYME + // On créer l'object toponyme const toponyme: Partial = { - _bal: bal._id, + balId: bal.id, banId: uuid(), - commune: bal.commune, nom: createToponymeDto.nom, - positions: createToponymeDto.positions || [], - parcelles: createToponymeDto.parcelles || [], nomAlt: createToponymeDto.nomAlt ? cleanNomAlt(createToponymeDto.nomAlt) : null, + positions: createToponymeDto.positions || [], + parcelles: createToponymeDto.parcelles || [], }; - - // REQUEST CREATE TOPONYME - const toponymeCreated: Toponyme = await this.toponymeModel.create(toponyme); - // SET _updated BAL - await this.baseLocaleService.touch(bal._id, toponymeCreated._updated); - + // Créer l'entité typeorm + const entityToSave: Toponyme = this.toponymesRepository.create(toponyme); + // On insert l'object dans postgres + const toponymeCreated: Toponyme = + await this.toponymesRepository.save(entityToSave); + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(bal.id, toponymeCreated.updatedAt); + // On retourne le toponyme créé return toponymeCreated; } @@ -130,146 +147,167 @@ export class ToponymeService { toponyme: Toponyme, updateToponymeDto: UpdateToponymeDTO, ): Promise { + // On clean le nom et le nomAlt si ils sont présent dans le dto if (updateToponymeDto.nom) { updateToponymeDto.nom = cleanNom(updateToponymeDto.nom); } - if (updateToponymeDto.nomAlt) { updateToponymeDto.nomAlt = cleanNomAlt(updateToponymeDto.nomAlt); } + // On update le numéro dans postgres + Object.assign(toponyme, updateToponymeDto); + const toponymeUpdated: Toponyme = + await this.toponymesRepository.save(toponyme); - const toponymeUpdated = await this.toponymeModel.findOneAndUpdate( - { _id: toponyme._id, _deleted: null }, - { $set: { ...updateToponymeDto, _updated: new Date() } }, - { new: true }, - ); - - // SET _updated BAL - await this.baseLocaleService.touch( - toponymeUpdated._bal, - toponymeUpdated._updated, - ); + await this.baseLocaleService.touch(toponyme.balId); + // On retourne le toponyme mis a jour return toponymeUpdated; } - public async softDelete(toponyme: Toponyme): Promise { - // SET _deleted OF TOPONYME - const toponymeUpdated: Toponyme = await this.toponymeModel.findOneAndUpdate( - { _id: toponyme._id }, - { $set: { _deleted: new Date(), _updated: new Date() } }, - { new: true }, - ); - - await this.numeroService.updateMany( - { toponyme: toponyme._id }, - { - toponyme: null, - _updated: toponymeUpdated._updated, - }, - ); - - // SET _updated OF TOPONYME - await this.baseLocaleService.touch(toponyme._bal); - return toponymeUpdated; + public async softDelete(toponyme: Toponyme): Promise { + // On archive le toponyme + const { affected }: UpdateResult = + await this.toponymesRepository.softDelete({ + id: toponyme.id, + }); + // Si le toponyme a bien été archivé + if (affected) { + // On détache le numéro qui appartenait a ce toponyme + await this.numeroService.updateMany( + { toponymeId: toponyme.id }, + { + toponymeId: null, + }, + ); + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(toponyme.balId); + } } public async restore(toponyme: Toponyme): Promise { - const updatedToponyme = await this.toponymeModel.findOneAndUpdate( - { _id: toponyme._id }, - { $set: { _deleted: null, _updated: new Date() } }, - { new: true }, - ); - // SET _updated OF TOPONYME - await this.baseLocaleService.touch(toponyme._bal); - - return updatedToponyme; + // On rétabli le toponyme + const { affected }: UpdateResult = await this.toponymesRepository.restore({ + id: toponyme.id, + }); + // Si le toponyme a été rétabli on met a jour le updateAt de la BAL + if (affected) { + await this.baseLocaleService.touch(toponyme.balId); + } + // On retourne le toponyme rétabli + return this.toponymesRepository.findOneBy({ + id: toponyme.id, + }); } public async delete(toponyme: Toponyme) { - // DELETE TOPONYME - const { deletedCount } = await this.toponymeModel.deleteOne({ - _id: toponyme._id, + // On supprime le toponyme + const { affected }: DeleteResult = await this.toponymesRepository.delete({ + id: toponyme.id, }); - - if (deletedCount >= 1) { - // SET _updated OF TOPONYME - await this.baseLocaleService.touch(toponyme._bal); + // Si le toponyme a été supprimer on met a jour le updateAt de la BAL + if (affected > 0) { + await this.baseLocaleService.touch(toponyme.balId); } } - async importMany(baseLocale: BaseLocale, rawToponymes: any[]) { - const toponymes = rawToponymes - .map((rawToponyme) => { - if (!rawToponyme.commune || !rawToponyme.nom) { - return null; - } - - const toponyme = { - _id: rawToponyme._id, - _bal: baseLocale._id, - banId: rawToponyme.banId || uuid(), - nom: cleanNom(rawToponyme.nom), - positions: rawToponyme.positions || [], - parcelles: rawToponyme.parcelles || [], - code: rawToponyme.code || null, - commune: rawToponyme.commune, - nomAlt: getNomAltDefault(rawToponyme.nomAlt), - } as Partial; - - if (rawToponyme._updated && rawToponyme._created) { - toponyme._created = rawToponyme._created; - toponyme._updated = rawToponyme._updated; - } - - return toponyme; - }) - .filter(Boolean); + public async deleteMany(where: FindOptionsWhere) { + // On supprime les toponyme + await this.toponymesRepository.delete(where); + } + async importMany(baseLocale: BaseLocale, rawToponymes: Partial[]) { + // On transforme les raw en toponymes + const toponymes: Partial[] = rawToponymes + // On garde seulement les toponymes qui ont un nom + .filter(({ nom }) => Boolean(nom)) + // On map les raw pour obtenir de vrai topnymes + .map((rawToponyme) => ({ + id: rawToponyme.id, + balId: baseLocale.id, + banId: rawToponyme.banId || uuid(), + nom: cleanNom(rawToponyme.nom), + parcelles: rawToponyme.parcelles || [], + nomAlt: getNomAltDefault(rawToponyme.nomAlt), + ...(rawToponyme.updatedAt && { updatedAt: rawToponyme.updatedAt }), + ...(rawToponyme.createdAt && { createdAt: rawToponyme.createdAt }), + })); + // On ne retourne rien si il n'y a pas de topnyme a insert if (toponymes.length === 0) { return; } - - await this.toponymeModel.insertMany(toponymes); + // On insert les toponymes + await this.toponymesRepository + .createQueryBuilder() + .insert() + .into(Toponyme) + .values(toponymes) + .execute(); + // On créer les positions + const positions: Partial[] = []; + for (const rawToponyme of rawToponymes) { + let rank = 0; + for (const { source, type, point } of rawToponyme.positions) { + positions.push({ + id: new ObjectId().toHexString(), + toponymeId: rawToponyme.id, + source, + type, + point, + rank, + }); + rank++; + } + } + if (positions.length === 0) { + return; + } + // On insert les positions + await this.toponymesRepository + .createQueryBuilder() + .insert() + .into(Position) + .values(positions) + .execute(); } - async isToponymeExist( - _id: Types.ObjectId, - _bal: Types.ObjectId = null, + public async isToponymeExist( + id: string, + balId: string = null, ): Promise { - const query = { _id, _deleted: null }; - if (_bal) { - query['_bal'] = _bal; - } - const toponymeExist = await this.toponymeModel.exists(query).exec(); - return toponymeExist !== null; + // On créer le where avec id et balId et lance la requète + const where: FindOptionsWhere = { + id, + ...(balId && { balId }), + }; + return this.toponymesRepository.exists({ where }); } getBBOX(toponyme: Toponyme, numeros: Numero[]): BboxTurf { + // On concat toutes les positions de tous les numeros const allPositions: Position[] = numeros .filter((n) => n.positions && n.positions.length > 0) .reduce((acc, n) => [...acc, ...n.positions], []); if (allPositions.length > 0) { + // On créer une feature collection avec toutes les positions des numeros const features: FeatureTurf[] = allPositions.map(({ point }) => turf.feature(point), ); const featuresCollection = turf.featureCollection(features); - + // On renvoie la bbox de la feature collection return bbox(featuresCollection); } else if (toponyme.positions && toponyme.positions.length > 0) { + // On créer une feature collection avec toutes les positions du toponyme const features: FeatureTurf[] = toponyme.positions.map(({ point }) => turf.feature(point), ); const featuresCollection = turf.featureCollection(features); - + // On renvoie la bbox de la feature collection return bbox(featuresCollection); } } - touch(toponymeId: Types.ObjectId, _updated: Date = new Date()) { - return this.toponymeModel.updateOne( - { _id: toponymeId }, - { $set: { _updated } }, - ); + touch(toponymeId: string, updatedAt: Date = new Date()) { + return this.toponymesRepository.update({ id: toponymeId }, { updatedAt }); } } diff --git a/apps/api/src/modules/voie/dto/create_voie.dto.ts b/apps/api/src/modules/voie/dto/create_voie.dto.ts index 012abff6..f3b11fa1 100644 --- a/apps/api/src/modules/voie/dto/create_voie.dto.ts +++ b/apps/api/src/modules/voie/dto/create_voie.dto.ts @@ -9,8 +9,8 @@ import { IsEnum, } from 'class-validator'; -import { LineString } from '@/shared/schemas/geometry/line_string.schema'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; +import { TypeNumerotationEnum } from '@/shared/entities/voie.entity'; +import { LineString } from './line_string'; export class CreateVoieDTO { @Validate(ValidatorBal, ['nom']) diff --git a/apps/api/src/modules/voie/dto/extended_voie.dto.ts b/apps/api/src/modules/voie/dto/extended_voie.dto.ts index 52122dce..02dd03b3 100644 --- a/apps/api/src/modules/voie/dto/extended_voie.dto.ts +++ b/apps/api/src/modules/voie/dto/extended_voie.dto.ts @@ -1,8 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { BBox as BboxTurf } from '@turf/helpers'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; export class ExtendedVoieDTO extends Voie { @ApiProperty() diff --git a/libs/shared/src/schemas/geometry/line_string.schema.ts b/apps/api/src/modules/voie/dto/line_string.ts similarity index 52% rename from libs/shared/src/schemas/geometry/line_string.schema.ts rename to apps/api/src/modules/voie/dto/line_string.ts index 2895bcb9..c4e74c2d 100644 --- a/libs/shared/src/schemas/geometry/line_string.schema.ts +++ b/apps/api/src/modules/voie/dto/line_string.ts @@ -1,25 +1,17 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; import { ApiProperty } from '@nestjs/swagger'; -import { HydratedDocument, SchemaTypes } from 'mongoose'; import { Equals, Validate } from 'class-validator'; -import { LineStringValidator } from '../../validators/coord.validator'; +import { LineStringValidator } from '@/shared/validators/coord.validator'; import { LineString as LineStringTurf, Position as PositionTurf, } from '@turf/helpers'; -export type LineStringDocument = HydratedDocument; - -@Schema({ - _id: false, -}) export class LineString implements LineStringTurf { @Equals('LineString') @ApiProperty({ enum: ['LineString'], type: String, }) - @Prop({ type: SchemaTypes.String, required: true, nullable: false }) type: 'LineString'; @Validate(LineStringValidator) @@ -32,8 +24,5 @@ export class LineString implements LineStringTurf { }, }, }) - @Prop({ type: [[SchemaTypes.Number]], required: true, nullable: false }) coordinates: PositionTurf[]; } - -export const LineStringSchema = SchemaFactory.createForClass(LineString); diff --git a/apps/api/src/modules/voie/dto/restore_voie.dto.ts b/apps/api/src/modules/voie/dto/restore_voie.dto.ts index 1334f5f7..e12a0b79 100644 --- a/apps/api/src/modules/voie/dto/restore_voie.dto.ts +++ b/apps/api/src/modules/voie/dto/restore_voie.dto.ts @@ -1,9 +1,8 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsMongoId } from 'class-validator'; -import { Types } from 'mongoose'; export class RestoreVoieDTO { @IsMongoId({ each: true }) @ApiProperty({ type: String, required: true, nullable: true, isArray: true }) - numerosIds?: Types.ObjectId[]; + numerosIds?: string[]; } diff --git a/apps/api/src/modules/voie/dto/update_voie.dto.ts b/apps/api/src/modules/voie/dto/update_voie.dto.ts index 6cad04f7..7ba5bb84 100644 --- a/apps/api/src/modules/voie/dto/update_voie.dto.ts +++ b/apps/api/src/modules/voie/dto/update_voie.dto.ts @@ -9,8 +9,8 @@ import { IsEnum, } from 'class-validator'; -import { LineString } from '@/shared/schemas/geometry/line_string.schema'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; +import { TypeNumerotationEnum } from '@/shared/entities/voie.entity'; +import { LineString } from './line_string'; export class UpdateVoieDTO { @IsOptional() diff --git a/apps/api/src/modules/voie/voie.controller.ts b/apps/api/src/modules/voie/voie.controller.ts index 4f0a7983..99838b01 100644 --- a/apps/api/src/modules/voie/voie.controller.ts +++ b/apps/api/src/modules/voie/voie.controller.ts @@ -23,9 +23,10 @@ import { ApiQuery, } from '@nestjs/swagger'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Numero } from '@/shared/entities/numero.entity'; import { filterSensitiveFields } from '@/shared/utils/numero.utils'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; import { CustomRequest } from '@/lib/types/request.type'; import { AdminGuard } from '@/lib/guards/admin.guard'; @@ -35,7 +36,6 @@ import { UpdateVoieDTO } from '@/modules/voie/dto/update_voie.dto'; import { RestoreVoieDTO } from '@/modules/voie/dto/restore_voie.dto'; import { CreateNumeroDTO } from '@/modules/numeros/dto/create_numero.dto'; import { NumeroService } from '@/modules/numeros/numero.service'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; @ApiTags('voies') @Controller('voies') @@ -84,8 +84,8 @@ export class VoieController { @ApiBearerAuth('admin-token') @UseGuards(AdminGuard) async softDelete(@Req() req: CustomRequest, @Res() res: Response) { - const result: Voie = await this.voieService.softDelete(req.voie); - res.status(HttpStatus.OK).json(result); + await this.voieService.softDelete(req.voie); + res.sendStatus(HttpStatus.NO_CONTENT); } @Put(':voieId/restore') @@ -130,11 +130,16 @@ export class VoieController { async findNumerosByVoie(@Req() req: CustomRequest, @Res() res: Response) { const numeros: Numero[] = await this.numeroService.findMany( { - voie: req.voie._id, - _deleted: null, + voieId: req.voie.id, }, null, - { numero: 1 }, + { + numero: 1, + suffixe: { + direction: 'ASC', + nulls: 'FIRST', + }, + }, ); const result = numeros.map((n) => filterSensitiveFields(n, !req.isAdmin)); res.status(HttpStatus.OK).json(result); diff --git a/apps/api/src/modules/voie/voie.middleware.ts b/apps/api/src/modules/voie/voie.middleware.ts index c64f62f7..7afa1553 100644 --- a/apps/api/src/modules/voie/voie.middleware.ts +++ b/apps/api/src/modules/voie/voie.middleware.ts @@ -1,13 +1,13 @@ import { Injectable, NestMiddleware, Inject, forwardRef } from '@nestjs/common'; import { Response, NextFunction } from 'express'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Voie } from '@/shared/entities/voie.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { CustomRequest } from '@/lib/types/request.type'; +import { isAdmin } from '@/lib/utils/is-admin.utils'; import { VoieService } from '@/modules/voie/voie.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { isAdmin } from '@/lib/utils/is-admin.utils'; @Injectable() export class VoieMiddleware implements NestMiddleware { @@ -22,7 +22,7 @@ export class VoieMiddleware implements NestMiddleware { if (voieId) { const voie: Voie = await this.voieService.findOneOrFail(voieId); const basesLocale: BaseLocale = - await this.baseLocaleService.findOneOrFail(voie._bal.toString()); + await this.baseLocaleService.findOneOrFail(voie.balId); req.baseLocale = basesLocale; req.voie = voie; diff --git a/apps/api/src/modules/voie/voie.module.ts b/apps/api/src/modules/voie/voie.module.ts index b1d49448..16a14f17 100644 --- a/apps/api/src/modules/voie/voie.module.ts +++ b/apps/api/src/modules/voie/voie.module.ts @@ -1,6 +1,7 @@ import { Module, MiddlewareConsumer, forwardRef } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; -import { Voie, VoieSchema } from '@/shared/schemas/voie/voie.schema'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { Voie } from '@/shared/entities/voie.entity'; import { VoieMiddleware } from '@/modules/voie/voie.middleware'; import { VoieController } from '@/modules/voie/voie.controller'; @@ -12,7 +13,7 @@ import { TilesModule } from '@/modules/base_locale/sub_modules/tiles/tiles.modul @Module({ imports: [ - MongooseModule.forFeature([{ name: Voie.name, schema: VoieSchema }]), + TypeOrmModule.forFeature([Voie]), forwardRef(() => NumeroModule), forwardRef(() => BaseLocaleModule), forwardRef(() => TilesModule), diff --git a/apps/api/src/modules/voie/voie.service.ts b/apps/api/src/modules/voie/voie.service.ts index 3c91ff7d..c5a8c330 100644 --- a/apps/api/src/modules/voie/voie.service.ts +++ b/apps/api/src/modules/voie/voie.service.ts @@ -5,371 +5,310 @@ import { Injectable, forwardRef, } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { FilterQuery, Model, ProjectionType, Types } from 'mongoose'; +import { InjectRepository } from '@nestjs/typeorm'; +import { + DeleteResult, + FindOptionsRelations, + FindOptionsSelect, + FindOptionsWhere, + In, + Point, + Repository, + UpdateResult, +} from 'typeorm'; import { groupBy } from 'lodash'; +import * as turf from '@turf/turf'; +import bbox from '@turf/bbox'; +import { Feature as FeatureTurf, BBox as BboxTurf } from '@turf/helpers'; import { v4 as uuid } from 'uuid'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { extendWithNumeros } from '@/shared/utils/numero.utils'; +import { Position } from '@/shared/entities/position.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie, TypeNumerotationEnum } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { cleanNom, cleanNomAlt, getNomAltDefault } from '@/lib/utils/nom.util'; import { ExtendedVoieDTO } from '@/modules/voie/dto/extended_voie.dto'; import { UpdateVoieDTO } from '@/modules/voie/dto/update_voie.dto'; import { CreateVoieDTO } from '@/modules/voie/dto/create_voie.dto'; import { RestoreVoieDTO } from '@/modules/voie/dto/restore_voie.dto'; -import { cleanNom, cleanNomAlt, getNomAltDefault } from '@/lib/utils/nom.util'; import { NumeroService } from '@/modules/numeros/numero.service'; -import { TilesService } from '@/modules/base_locale/sub_modules/tiles/tiles.service'; import { BaseLocaleService } from '@/modules/base_locale/base_locale.service'; -import { extendWithNumeros } from '@/shared/utils/numero.utils'; -import { Position } from '@/shared/schemas/position.schema'; -import * as turf from '@turf/turf'; -import bbox from '@turf/bbox'; -import { Feature as FeatureTurf, BBox as BboxTurf } from '@turf/helpers'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; import { ToponymeService } from '@/modules/toponyme/toponyme.service'; -import { - getTilesByLineString, - getTilesByPosition, -} from '../base_locale/sub_modules/tiles/utils/tiles.utils'; -import { getPriorityPosition } from '@/lib/utils/positions.util'; -import { ZOOM } from '../base_locale/sub_modules/tiles/const/zoom.const'; @Injectable() export class VoieService { constructor( - @InjectModel(Voie.name) private voieModel: Model, + @InjectRepository(Voie) + private voiesRepository: Repository, @Inject(forwardRef(() => BaseLocaleService)) private baseLocaleService: BaseLocaleService, @Inject(forwardRef(() => NumeroService)) private numeroService: NumeroService, - @Inject(forwardRef(() => TilesService)) - private tilesService: TilesService, @Inject(forwardRef(() => ToponymeService)) private toponymeService: ToponymeService, ) {} async findOneOrFail(voieId: string): Promise { - const filter = { - _id: voieId, + // Créer le filtre where et lance la requète postgres + const where: FindOptionsWhere = { + id: voieId, }; - const voie = await this.voieModel.findOne(filter).lean().exec(); - + const voie = await this.voiesRepository.findOne({ + where, + withDeleted: true, + }); + // Si la voie n'existe pas, on throw une erreur if (!voie) { throw new HttpException(`Voie ${voieId} not found`, HttpStatus.NOT_FOUND); } - return voie; } async findMany( - filter: FilterQuery, - projection?: ProjectionType, + where: FindOptionsWhere, + select?: FindOptionsSelect, + relations?: FindOptionsRelations, ): Promise { - const query = this.voieModel.find(filter); - if (projection) { - query.projection(projection); - } - - return query.lean().exec(); - } - - public deleteMany(filters: FilterQuery): Promise { - return this.voieModel.deleteMany(filters); + return this.voiesRepository.find({ + where, + ...(select && { select }), + ...(relations && { relations }), + }); } - async extendVoies(voies: Voie[]): Promise { - const numeros = await this.numeroService.findMany({ - voie: { $in: voies.map(({ _id }) => _id) }, - _deleted: null, + async findManyWithDeleted( + where: FindOptionsWhere | FindOptionsWhere[], + ): Promise { + // Get les voies en fonction du where archiver ou non + return this.voiesRepository.find({ + where, + withDeleted: true, }); - - const numerosByVoies = groupBy(numeros, 'voie'); - - return voies.map((voie) => ({ - ...extendWithNumeros(voie, numerosByVoies[voie._id] || []), - bbox: this.getBBOX(voie, numerosByVoies[voie._id] || []), - })); } - async extendVoie(voie: Voie): Promise { - const numeros = await this.numeroService.findMany({ - voie: voie._id, - }); + async findManyWhereCentroidInBBox( + balId: string, + bbox: number[], + ): Promise { + // Requète postgis qui permet de récupèré les voie dont le centroid est dans la bbox + return this.voiesRepository + .createQueryBuilder('voies') + .where('bal_id = :balId', { balId }) + .andWhere( + 'centroid @ ST_MakeEnvelope(:xmin, :ymin, :xmax, :ymax, 4326)', + { + xmin: bbox[0], + ymin: bbox[1], + xmax: bbox[2], + ymax: bbox[3], + }, + ) + .getMany(); + } - return { - ...extendWithNumeros(voie, numeros), - bbox: this.getBBOX(voie, numeros), - }; + async findManyWhereTraceInBBox( + balId: string, + bbox: number[], + ): Promise { + // Requète postgis qui permet de récupèré les voie dont le centroid est dans la bbox + return this.voiesRepository + .createQueryBuilder() + .where('bal_id = :balId', { balId }) + .andWhere( + 'ST_Intersects(trace, ST_MakeEnvelope(:xmin, :ymin, :xmax, :ymax, 4326) )', + { + xmin: bbox[0], + ymin: bbox[1], + xmax: bbox[2], + ymax: bbox[3], + }, + ) + .getMany(); } public async create( bal: BaseLocale, createVoieDto: CreateVoieDTO, ): Promise { - // CREATE OBJECT VOIE + // Créer l'object Voie a partir du dto const voie: Partial = { - _bal: bal._id, + balId: bal.id, banId: uuid(), - commune: bal.commune, nom: createVoieDto.nom, typeNumerotation: createVoieDto.typeNumerotation || TypeNumerotationEnum.NUMERIQUE, trace: createVoieDto.trace || null, nomAlt: createVoieDto.nomAlt ? cleanNomAlt(createVoieDto.nomAlt) : null, centroid: null, - centroidTiles: null, - traceTiles: null, }; - // CALC CENTROID AND TILES IF METRIQUE + // Calculer le centroid si la trace et le type de numerotation est metrique if (voie.trace && voie.typeNumerotation === TypeNumerotationEnum.METRIQUE) { - this.tilesService.calcMetaTilesVoieWithTrace(voie); + voie.centroid = turf.centroid(voie.trace)?.geometry; } - // REQUEST CREATE VOIE - const voieCreated: Voie = await this.voieModel.create(voie); - // SET _updated BAL - await this.baseLocaleService.touch(bal._id, voieCreated._updated); - + // Créer l'entité typeorm + const entityToSave: Voie = this.voiesRepository.create(voie); + // On insert l'object dans postgres + const voieCreated: Voie = await this.voiesRepository.save(entityToSave); + // Mettre a jour le updatedAt de la BAL + await this.baseLocaleService.touch(bal.id, voieCreated.updatedAt); + // On retourne la voie créé return voieCreated; } - async importMany(baseLocale: BaseLocale, rawVoies: any[]) { - const voies = rawVoies - .map((rawVoie) => { - if (!rawVoie.commune || !rawVoie.nom) { - return null; - } - - const voie = { - _id: rawVoie._id, - _bal: baseLocale._id, - banId: rawVoie.banId || uuid(), - nom: cleanNom(rawVoie.nom), - code: rawVoie.code || null, - commune: rawVoie.commune, - nomAlt: getNomAltDefault(rawVoie.nomAlt), - typeNumerotation: rawVoie.typeNumerotation, - trace: rawVoie.trace || null, - } as Partial; - - if (rawVoie._updated && rawVoie._created) { - voie._created = rawVoie._created; - voie._updated = rawVoie._updated; - } - - return voie; - }) - .filter(Boolean); - + public async importMany(baseLocale: BaseLocale, rawVoies: Partial[]) { + // On transforme les raw en voies + const voies: Partial[] = rawVoies + // On garde seulement les voies qui ont un nom + .filter(({ nom }) => Boolean(nom)) + .map((rawVoie: Partial) => ({ + id: rawVoie.id, + balId: baseLocale.id, + banId: rawVoie.banId || uuid(), + nom: cleanNom(rawVoie.nom), + nomAlt: getNomAltDefault(rawVoie.nomAlt), + typeNumerotation: rawVoie.typeNumerotation, + trace: rawVoie.trace || null, + ...(rawVoie.updatedAt && { updatedAt: rawVoie.updatedAt }), + ...(rawVoie.createdAt && { createdAt: rawVoie.createdAt }), + })); + // On ne retourne rien si il n'y a pas de voies a insert if (voies.length === 0) { return; } - - await this.voieModel.insertMany(voies); - } - - async updateTiles(voieIds: string[]) { - const voies: Voie[] = await this.findMany({ _id: { $in: voieIds } }); - return Promise.all( - voies.map(async (voie) => { - const voieSet = await this.calcMetaTilesVoie(voie); - return this.voieModel.updateOne({ _id: voie._id }, { $set: voieSet }); - }), - ); - } - - async calcMetaTilesVoie(voie: Voie) { - voie.centroid = null; - voie.centroidTiles = null; - voie.traceTiles = null; - - try { - if (voie.typeNumerotation === 'metrique' && voie.trace) { - voie.centroid = turf.centroid(voie.trace); - voie.centroidTiles = getTilesByPosition( - voie.centroid.geometry, - ZOOM.VOIE_ZOOM, - ); - voie.traceTiles = getTilesByLineString(voie.trace); - } else { - const numeros = await this.numeroService.findMany( - { voie: voie._id, _deleted: null }, - { positions: 1, voie: 1 }, - ); - if (numeros.length > 0) { - const coordinatesNumeros = numeros - .filter((n) => n.positions && n.positions.length > 0) - .map((n) => getPriorityPosition(n.positions)?.point?.coordinates); - // CALC CENTROID - if (coordinatesNumeros.length > 0) { - const featureCollection = turf.featureCollection( - coordinatesNumeros.map((n) => turf.point(n)), - ); - voie.centroid = turf.centroid(featureCollection); - voie.centroidTiles = getTilesByPosition( - voie.centroid.geometry, - ZOOM.VOIE_ZOOM, - ); - } - } - } - } catch (error) { - console.error(error, voie); - } - - return voie; + // On insert les voies + await this.voiesRepository + .createQueryBuilder() + .insert() + .into(Voie) + .values(voies) + .execute(); } - async updateOne( - voieId: Types.ObjectId, - update: Partial, - ): Promise { - return this.voieModel - .findOneAndUpdate({ _id: voieId }, { $set: update }) - .exec(); - } - - async update(voie: Voie, updateVoieDto: UpdateVoieDTO): Promise { + public async update(voie: Voie, updateVoieDto: UpdateVoieDTO): Promise { + // Si le nom a été modifier, on le clean if (updateVoieDto.nom) { updateVoieDto.nom = cleanNom(updateVoieDto.nom); } - + // Si les noms alternatif on été modifier if (updateVoieDto.nomAlt) { updateVoieDto.nomAlt = cleanNomAlt(updateVoieDto.nomAlt); } - - const voieUpdated = await this.voieModel.findOneAndUpdate( - { _id: voie._id, _deleted: null }, - { $set: { ...updateVoieDto, _updated: new Date() } }, - { new: true }, + // Créer le where et lancer la requète + const where: FindOptionsWhere = { + id: voie.id, + }; + const res: UpdateResult = await this.voiesRepository.update( + where, + updateVoieDto, ); - // SET TILES OF VOIES - await this.tilesService.updateVoieTiles(voieUpdated); - // SET _updated BAL - await this.baseLocaleService.touch(voieUpdated._bal, voieUpdated._updated); + // On récupère la voie modifiée + const voieUpdated: Voie = await this.voiesRepository.findOneBy(where); + // Si la voie a été modifiée + if (res.affected > 0) { + // On met a jour le centroid de la voie si la trace a été mis a jour + if ( + updateVoieDto.trace && + voieUpdated.typeNumerotation === TypeNumerotationEnum.METRIQUE + ) { + await this.calcCentroidWithTrace(voieUpdated); + } + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch( + voieUpdated.balId, + voieUpdated.updatedAt, + ); + } + // On retourne la voie modifiée return voieUpdated; } public async delete(voie: Voie) { - // DELETE VOIE - const { deletedCount } = await this.voieModel.deleteOne({ - _id: voie._id, + // On lance la requète postgres pour supprimer définitivement la voie + // Les numéros sont supprimé en cascade par postgres + const { affected }: DeleteResult = await this.voiesRepository.delete({ + id: voie.id, }); - if (deletedCount >= 1) { - // SET _updated OF VOIE - await this.baseLocaleService.touch(voie._bal); - // DELETE NUMEROS OF VOIE - await this.numeroService.deleteMany({ - voie: voie._id, - _bal: voie._bal, - }); + if (affected >= 1) { + // On supprime egalement les numeros de la voie + await this.numeroService.deleteMany({ voieId: voie.id }); + // Si une voie a bien été supprimé on met a jour le updatedAt de la Bal + await this.baseLocaleService.touch(voie.balId); } } - public async softDelete(voie: Voie): Promise { - // SET _deleted OF VOIE - const voieUpdated: Voie = await this.voieModel.findOneAndUpdate( - { _id: voie._id }, - { $set: { _deleted: new Date(), _updated: new Date() } }, - { new: true }, - ); + public deleteMany(where: FindOptionsWhere): Promise { + return this.voiesRepository.delete(where); + } - // SET _updated OF VOIE - await this.baseLocaleService.touch(voie._bal); - // SET _deleted NUMERO FROM VOIE - await this.numeroService.updateMany( - { voie: voie._id }, - { - _deleted: voieUpdated._updated, - _updated: voieUpdated._updated, - }, - ); - return voieUpdated; + public async softDelete(voie: Voie): Promise { + // On créer le where et lance le softDelete typeorm + // Le softDelete va mettre a jour le deletedAt + await this.voiesRepository.softDelete({ id: voie.id }); + // On archive également tous le numéros de la voie + await this.numeroService.softDeleteByVoie(voie.id); + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(voie.balId); } public async restore( voie: Voie, { numerosIds }: RestoreVoieDTO, ): Promise { - const updatedVoie = await this.voieModel.findOneAndUpdate( - { _id: voie._id }, - { $set: { _deleted: null, _updated: new Date() } }, - { new: true }, - ); - // SET _updated OF VOIE - await this.baseLocaleService.touch(voie._bal); + // On créer le where et on restore la voie + // Le restore met a null le deletedAt de la voie + const where: FindOptionsWhere = { + id: voie.id, + }; + await this.voiesRepository.restore(where); + // Si des numéros sont également restauré if (numerosIds.length > 0) { - // SET _updated NUMERO FROM VOIE - const { modifiedCount } = await this.numeroService.updateMany( - { voie: voie._id, _id: { $in: numerosIds } }, - { _deleted: null, _updated: updatedVoie._updated }, - ); - if (modifiedCount > 0) { - await this.tilesService.updateVoieTiles(updatedVoie); - } - } - - return updatedVoie; - } - - async isVoieExist( - _id: Types.ObjectId, - _bal: Types.ObjectId = null, - ): Promise { - const query = { _id, _deleted: null }; - if (_bal) { - query['_bal'] = _bal; + // On restaure le numéros + await this.numeroService.restore({ + id: In(numerosIds), + }); + // On met a jour le centroid de la voie + this.calcCentroid(voie.id); } - const voieExist = await this.voieModel.exists(query).exec(); - return voieExist !== null; + // On met a jour le updatedAt de la BAL + await this.baseLocaleService.touch(voie.balId); + // On retourne la voie restaurée + return this.voiesRepository.findOne({ where }); } - getBBOX(voie: Voie, numeros: Numero[]): BboxTurf { - const allPositions: Position[] = numeros - .filter((n) => n.positions && n.positions.length > 0) - .reduce((acc, n) => [...acc, ...n.positions], []); - - if (allPositions.length > 0) { - const features: FeatureTurf[] = allPositions.map(({ point }) => - turf.feature(point), - ); - const featuresCollection = turf.featureCollection(features); - return bbox(featuresCollection); - } else if ( - voie.trace && - voie.typeNumerotation === TypeNumerotationEnum.NUMERIQUE - ) { - return bbox(voie.trace); - } + public async isVoieExist(id: string, balId: string = null): Promise { + // On créer le where avec id et balId et lance la requète + const where: FindOptionsWhere = { + id, + ...(balId && { balId }), + }; + return this.voiesRepository.exists({ where }); } - async convertToToponyme(voie: Voie): Promise { - if (!(await this.isVoieExist(voie._id))) { + public async convertToToponyme(voie: Voie): Promise { + // On lance une erreur si la voie n'existe pas + if (!(await this.isVoieExist(voie.id))) { throw new HttpException( - `Voie ${voie._id} is deleted`, + `Voie ${voie.id} is deleted`, HttpStatus.BAD_REQUEST, ); } - + // On lance une erreur si la voie a des numeros const numerosCount: number = await this.numeroService.count({ - voie: voie._id, - _deleted: null, + voieId: voie.id, }); if (numerosCount > 0) { throw new HttpException( - `Voie ${voie._id} has numero(s)`, + `Voie ${voie.id} has numero(s)`, HttpStatus.BAD_REQUEST, ); } - - const baseLocale = await this.baseLocaleService.findOneOrFail( - voie._bal.toString(), - ); - - // CREATE TOPONYME + // On recupère la Bal + const baseLocale = await this.baseLocaleService.findOneOrFail(voie.balId); + // On créer un toponyme avec les noms de la voie const payload: Partial = { nom: voie.nom, nomAlt: voie.nomAlt, @@ -379,13 +318,89 @@ export class VoieService { baseLocale, payload, ); - // DELETE VOIE + // On supprimer la voie de postgres await this.delete(voie); - // RETURN NEW TOPONYME + // On retourne le toponyme créé return toponyme; } - touch(voieId: Types.ObjectId, _updated: Date = new Date()) { - return this.voieModel.updateOne({ _id: voieId }, { $set: { _updated } }); + public async extendVoies(voies: Voie[]): Promise { + const numeros = await this.numeroService.findMany( + { + voieId: In(voies.map(({ id }) => id)), + }, + { certifie: true, comment: true, voieId: true }, + ); + const numerosByVoies = groupBy(numeros, 'voieId'); + + return voies.map((voie) => ({ + ...extendWithNumeros(voie, numerosByVoies[voie.id] || []), + bbox: this.getBBOX(voie, numerosByVoies[voie.id] || []), + })); + } + + public async extendVoie(voie: Voie): Promise { + const numeros = await this.numeroService.findMany({ + voieId: voie.id, + }); + + return { + ...extendWithNumeros(voie, numeros), + bbox: this.getBBOX(voie, numeros), + }; + } + + public async touch(voieId: string, updatedAt: Date = new Date()) { + return this.voiesRepository.update({ id: voieId }, { updatedAt }); + } + + public async calcCentroid(voieId: string): Promise { + // On récupère la voie + const voie: Voie = await this.findOneOrFail(voieId); + if (voie.typeNumerotation === TypeNumerotationEnum.NUMERIQUE) { + // On calcule la voie avec les numero si la voie est numerique + await this.calcCentroidWithNumeros(voieId); + } else if ( + voie.trace && + voie.typeNumerotation === TypeNumerotationEnum.METRIQUE + ) { + // On calcul la voie avec la trace si la voie est metrique + await this.calcCentroidWithTrace(voie); + } + } + + private async calcCentroidWithNumeros(voieId: string): Promise { + const centroid: Point = await this.numeroService.findCentroid(voieId); + await this.voiesRepository.update({ id: voieId }, { centroid }); + } + + private async calcCentroidWithTrace(voie: Voie): Promise { + const centroid = turf.centroid(voie.trace)?.geometry; + await this.voiesRepository.update({ id: voie.id }, { centroid }); + } + + private getBBOX(voie: Voie, numeros: Numero[]): BboxTurf { + // On récupère toutes les positions des numeros de la voie + const allPositions: Position[] = numeros + .filter((n) => n.positions && n.positions.length > 0) + .reduce((acc, n) => [...acc, ...n.positions], []); + + if (allPositions.length > 0) { + // Si il y a des positions de numeros + // On créer un feature collection avec turf + const features: FeatureTurf[] = allPositions.map(({ point }) => + turf.feature(point), + ); + const featuresCollection = turf.featureCollection(features); + // On créer un bbox a partir de la feature collection + return bbox(featuresCollection); + } else if ( + voie.trace && + voie.typeNumerotation === TypeNumerotationEnum.METRIQUE + ) { + // Si la voie a une trace et est de type metrique + // On créer un bbox a partir de la trace + return bbox(voie.trace); + } } } diff --git a/apps/api/test/base_locale.e2e-spec.ts b/apps/api/test/base_locale.e2e-spec.ts index cc05e30f..093f640f 100644 --- a/apps/api/test/base_locale.e2e-spec.ts +++ b/apps/api/test/base_locale.e2e-spec.ts @@ -1,202 +1,230 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import { v4 as uuid } from 'uuid'; +import { ObjectId } from 'mongodb'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; -import { Position } from '@/shared/schemas/position.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie, TypeNumerotationEnum } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; import { BaseLocaleModule } from '@/modules/base_locale/base_locale.module'; import { UpdateBatchNumeroDTO } from '@/modules/numeros/dto/update_batch_numero.dto'; import { DeleteBatchNumeroDTO } from '@/modules/numeros/dto/delete_batch_numero.dto'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; import { CreateVoieDTO } from '@/modules/voie/dto/create_voie.dto'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; import { CreateToponymeDTO } from '@/modules/toponyme/dto/create_toponyme.dto'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; +import { Point, Repository } from 'typeorm'; const BAN_API_URL = 'BAN_API_URL'; process.env.BAN_API_URL = BAN_API_URL; const baseLocaleAdminProperties = ['token', 'emails']; const baseLocalePublicProperties = [ + 'id', + 'banId', 'nom', 'commune', - '_updated', 'nbNumeros', 'nbNumerosCertifies', 'isAllCertified', 'commentedNumeros', 'status', - '_created', - '_deleted', - '_id', - '__v', + 'updatedAt', + 'createdAt', + 'deletedAt', + 'habilitationId', + 'sync', ]; describe('BASE LOCAL MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; - let toponymeModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); // AXIOS const axiosMock = new MockAdapter(axios); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), BaseLocaleModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + BaseLocaleModule, + MailerModule, + ], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); - toponymeModel = app.get>(getModelToken(Toponyme.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { axiosMock.reset(); - await toponymeModel.deleteMany({}); - await voieModel.deleteMany({}); - await balModel.deleteMany({}); - await numeroModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); async function createBal(props: Partial = {}) { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, status: props.status ?? StatusBaseLocalEnum.DRAFT, token, ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, - _created, - _updated, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createToponyme(props: Partial = {}) { - const toponymeId = new Types.ObjectId(); - const toponyme: Partial = { - _id: toponymeId, - _created, - _updated, + async function createToponyme(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await toponymeModel.create(toponyme); - return toponymeId; + const entityToInsert = toponymeRepository.create(payload); + const result = await toponymeRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, - _created, - _updated, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } - function createPositions(coordinates: number[] = [8, 42]): Position[] { - return [ - { - type: PositionTypeEnum.INCONNUE, - source: 'ban', - point: { - type: 'Point', - coordinates, - }, - }, - ]; + function createPositions(coordinates: number[] = [8, 42]): Position { + const id = new ObjectId().toHexString(); + const point: Point = { + type: 'Point', + coordinates, + }; + return { + id, + type: PositionTypeEnum.INCONNUE, + source: 'ban', + point, + } as Position; } describe('PUT /bases-locales/numeros/batch', () => { it('Batch 200 numeros change voie', async () => { - const balId = await createBal(); - const voieId1 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const toponymeId1 = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId1 = await createVoie(balId, { nom: 'rue de la paix' }); + const toponymeId1 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const toponymeId2 = await createToponyme({ + const toponymeId2 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const voieId2 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const voieId3 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId1, + const voieId2 = await createVoie(balId, { nom: 'rue de la paix' }); + const voieId3 = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId1 = await createNumero(balId, voieId1, { numero: 99, - positions: createPositions(), - toponyme: toponymeId1, + toponymeId: toponymeId1, + positions: [createPositions()], }); - const numeroId2 = await createNumero({ - _bal: balId, - voie: voieId2, + const numeroId2 = await createNumero(balId, voieId2, { numero: 99, - positions: createPositions(), - toponyme: toponymeId2, + toponymeId: toponymeId2, + positions: [createPositions()], }); - const updateBtach: UpdateBatchNumeroDTO = { numerosIds: [numeroId1, numeroId2], changes: { - voie: voieId3, - toponyme: toponymeId2, + voieId: voieId3, + toponymeId: toponymeId2, positionType: PositionTypeEnum.DELIVRANCE_POSTALE, certifie: true, comment: 'coucou', }, }; - const response = await request(app.getHttpServer()) .put(`/bases-locales/${balId}/numeros/batch`) .send(updateBtach) @@ -205,72 +233,67 @@ describe('BASE LOCAL MODULE', () => { expect(response.body.modifiedCount).toEqual(2); expect(response.body.changes).toEqual({ - voie: voieId3.toString(), - toponyme: toponymeId2.toString(), + voieId: voieId3.toString(), + toponymeId: toponymeId2.toString(), positionType: PositionTypeEnum.DELIVRANCE_POSTALE, certifie: true, comment: 'coucou', }); - const numero1After: Numero = await numeroModel.findOne({ - _id: numeroId1, + const numero1After: Numero = await numeroRepository.findOneBy({ + id: numeroId1, }); - expect(numero1After._updated).not.toEqual(_updated.toISOString()); - expect(numero1After.voie).toEqual(voieId3); + expect(numero1After.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(numero1After.voieId).toEqual(voieId3); expect(numero1After.positions[0].type).toEqual( PositionTypeEnum.DELIVRANCE_POSTALE, ); expect(numero1After.certifie).toBeTruthy(); expect(numero1After.comment).toEqual('coucou'); - const numero2After: Numero = await numeroModel.findOne({ - _id: numeroId2, + const numero2After: Numero = await numeroRepository.findOneBy({ + id: numeroId2, }); - expect(numero2After._updated).not.toEqual(_updated.toISOString()); - expect(numero2After.voie).toEqual(voieId3); + expect(numero2After.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(numero2After.voieId).toEqual(voieId3); expect(numero2After.positions[0].type).toEqual( PositionTypeEnum.DELIVRANCE_POSTALE, ); expect(numero2After.certifie).toBeTruthy(); expect(numero2After.comment).toEqual('coucou'); - const voie1After: Voie = await voieModel.findOne({ _id: voieId1 }); - expect(voie1After._updated).not.toEqual(_updated.toISOString()); + const voie1After: Voie = await voieRepository.findOneBy({ id: voieId1 }); + expect(voie1After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie1After.centroid).toBeNull(); - expect(voie1After.centroidTiles).toBeNull(); - const voie2After: Voie = await voieModel.findOne({ _id: voieId2 }); - expect(voie2After._updated).not.toEqual(_updated.toISOString()); + const voie2After: Voie = await voieRepository.findOneBy({ id: voieId2 }); + expect(voie2After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie2After.centroid).toBeNull(); - expect(voie2After.centroidTiles).toBeNull(); - const voie3After: Voie = await voieModel.findOne({ _id: voieId3 }); - expect(voie3After._updated).not.toEqual(_updated.toISOString()); + const voie3After: Voie = await voieRepository.findOneBy({ id: voieId3 }); + expect(voie3After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie3After.centroid).not.toBeNull(); - expect(voie3After.centroidTiles).not.toBeNull(); - const toponymeAfter1: Toponyme = await toponymeModel.findOne({ - _id: toponymeId1, + const toponymeAfter1: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId1, }); - expect(toponymeAfter1._updated).not.toEqual(_updated.toISOString()); + expect(toponymeAfter1.updatedAt).not.toEqual(updatedAt.toISOString()); - const toponymeAfter2: Toponyme = await toponymeModel.findOne({ - _id: toponymeId2, + const toponymeAfter2: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId2, }); - expect(toponymeAfter2._updated).not.toEqual(_updated.toISOString()); + expect(toponymeAfter2.updatedAt).not.toEqual(updatedAt.toISOString()); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(balAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('PUT /bases-locales/numeros/certify-all', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, - positions: createPositions(), + positions: [createPositions()], certifie: false, }); @@ -279,20 +302,18 @@ describe('BASE LOCAL MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(200); - const numeroAfter: Numero = await numeroModel.findOne({ - _id: numeroId, + const numeroAfter: Numero = await numeroRepository.findOneBy({ + id: numeroId, }); expect(numeroAfter.certifie).toBeTruthy(); }); it('Batch 400 without changes', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, - positions: createPositions(), + positions: [createPositions()], }); const updateBtach: UpdateBatchNumeroDTO = { numerosIds: [numeroId], @@ -307,18 +328,16 @@ describe('BASE LOCAL MODULE', () => { }); it('Batch 404 numeros: Bad voie', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, - positions: createPositions(), + positions: [createPositions()], }); const updateBtach: UpdateBatchNumeroDTO = { numerosIds: [numeroId], changes: { - voie: new Types.ObjectId(), + voieId: new ObjectId().toHexString(), }, }; @@ -330,18 +349,16 @@ describe('BASE LOCAL MODULE', () => { }); it('Batch 404 numeros: Bad toponyme', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, - positions: createPositions(), + positions: [createPositions()], }); const updateBtach: UpdateBatchNumeroDTO = { numerosIds: [numeroId], changes: { - toponyme: new Types.ObjectId(), + toponymeId: new ObjectId().toHexString(), }, }; @@ -355,85 +372,68 @@ describe('BASE LOCAL MODULE', () => { describe('PUT /bases-locales/numeros/batch/soft-delete', () => { it('Soft Delete 200', async () => { - const balId = await createBal(); - const voieId1 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const toponymeId1 = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId1 = await createVoie(balId, { nom: 'rue de la paix' }); + const toponymeId1 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const toponymeId2 = await createToponyme({ + const toponymeId2 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const voieId2 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId1, + const voieId2 = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId1 = await createNumero(balId, voieId1, { numero: 99, - positions: createPositions(), - toponyme: toponymeId1, + positions: [createPositions()], + toponymeId: toponymeId1, }); - const numeroId2 = await createNumero({ - _bal: balId, - voie: voieId2, + const numeroId2 = await createNumero(balId, voieId2, { numero: 99, - positions: createPositions(), - toponyme: toponymeId2, + positions: [createPositions()], + toponymeId: toponymeId2, }); - const deleteBtach: DeleteBatchNumeroDTO = { numerosIds: [numeroId1, numeroId2], }; - - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .put(`/bases-locales/${balId}/numeros/batch/soft-delete`) .send(deleteBtach) .set('authorization', `Bearer ${token}`) - .expect(200); - - expect(response.body.modifiedCount).toEqual(2); - const numero1After: Numero = await numeroModel.findOne({ - _id: numeroId1, - }); - expect(numero1After._updated).not.toEqual(_updated.toISOString()); - expect(numero1After._deleted).toBeDefined(); - - const numero2After: Numero = await numeroModel.findOne({ - _id: numeroId2, - }); - expect(numero2After._updated).not.toEqual(_updated.toISOString()); - expect(numero2After._deleted).toBeDefined(); - - const voie1After: Voie = await voieModel.findOne({ _id: voieId1 }); - expect(voie1After._updated).not.toEqual(_updated.toISOString()); + .expect(204); + const numero1After: Numero = await numeroRepository.findOne({ + where: { id: numeroId1 }, + withDeleted: true, + }); + expect(numero1After.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(numero1After.deletedAt).toBeDefined(); + const numero2After: Numero = await numeroRepository.findOne({ + where: { id: numeroId2 }, + withDeleted: true, + }); + expect(numero2After.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(numero2After.deletedAt).toBeDefined(); + const voie1After: Voie = await voieRepository.findOneBy({ id: voieId1 }); + expect(voie1After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie1After.centroid).toBeNull(); - expect(voie1After.centroidTiles).toBeNull(); - - const voie2After: Voie = await voieModel.findOne({ _id: voieId2 }); - expect(voie2After._updated).not.toEqual(_updated.toISOString()); + const voie2After: Voie = await voieRepository.findOneBy({ id: voieId2 }); + expect(voie2After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie2After.centroid).toBeNull(); - expect(voie2After.centroidTiles).toBeNull(); - - const toponymeAfter1: Toponyme = await toponymeModel.findOne({ - _id: toponymeId1, + const toponymeAfter1: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId1, }); - expect(toponymeAfter1._updated).not.toEqual(_updated.toISOString()); - - const toponymeAfter2: Toponyme = await toponymeModel.findOne({ - _id: toponymeId2, + expect(toponymeAfter1.updatedAt).not.toEqual(updatedAt.toISOString()); + const toponymeAfter2: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId2, }); - expect(toponymeAfter2._updated).not.toEqual(_updated.toISOString()); - - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + expect(toponymeAfter2.updatedAt).not.toEqual(updatedAt.toISOString()); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(balAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('Soft Delete 400: Bad request', async () => { - const balId = await createBal(); + const balId = await createBal({ nom: 'bal', commune: '91400' }); const deleteBtach: DeleteBatchNumeroDTO = { numerosIds: [], }; - await request(app.getHttpServer()) .put(`/bases-locales/${balId}/numeros/batch/soft-delete`) .send(deleteBtach) @@ -444,30 +444,24 @@ describe('BASE LOCAL MODULE', () => { describe('DELETE /bases-locales/numeros/batch', () => { it('Delete 204', async () => { - const balId = await createBal(); - const voieId1 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const toponymeId1 = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId1 = await createVoie(balId, { nom: 'rue de la paix' }); + const toponymeId1 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const toponymeId2 = await createToponyme({ + const toponymeId2 = await createToponyme(balId, { nom: 'allée', - _bal: balId, }); - const voieId2 = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId1, + const voieId2 = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId1 = await createNumero(balId, voieId1, { numero: 99, - positions: createPositions(), - toponyme: toponymeId1, + positions: [createPositions()], + toponymeId: toponymeId1, }); - const numeroId2 = await createNumero({ - _bal: balId, - voie: voieId2, + const numeroId2 = await createNumero(balId, voieId2, { numero: 99, - positions: createPositions(), - toponyme: toponymeId2, + positions: [createPositions()], + toponymeId: toponymeId2, }); const deleteBtach: DeleteBatchNumeroDTO = { @@ -480,42 +474,40 @@ describe('BASE LOCAL MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(204); - const numero1After: Numero = await numeroModel.findOne({ - _id: numeroId1, + const numero1After: Numero = await numeroRepository.findOneBy({ + id: numeroId1, }); expect(numero1After).toBeNull(); - const numero2After: Numero = await numeroModel.findOne({ - _id: numeroId2, + const numero2After: Numero = await numeroRepository.findOneBy({ + id: numeroId2, }); expect(numero2After).toBeNull(); - const voie1After: Voie = await voieModel.findOne({ _id: voieId1 }); - expect(voie1After._updated).not.toEqual(_updated.toISOString()); + const voie1After: Voie = await voieRepository.findOneBy({ id: voieId1 }); + expect(voie1After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie1After.centroid).toBeNull(); - expect(voie1After.centroidTiles).toBeNull(); - const voie2After: Voie = await voieModel.findOne({ _id: voieId2 }); - expect(voie2After._updated).not.toEqual(_updated.toISOString()); + const voie2After: Voie = await voieRepository.findOneBy({ id: voieId2 }); + expect(voie2After.updatedAt).not.toEqual(updatedAt.toISOString()); expect(voie2After.centroid).toBeNull(); - expect(voie2After.centroidTiles).toBeNull(); - const toponymeAfter1: Toponyme = await toponymeModel.findOne({ - _id: toponymeId1, + const toponymeAfter1: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId1, }); - expect(toponymeAfter1._updated).not.toEqual(_updated.toISOString()); + expect(toponymeAfter1.updatedAt).not.toEqual(updatedAt.toISOString()); - const toponymeAfter2: Toponyme = await toponymeModel.findOne({ - _id: toponymeId2, + const toponymeAfter2: Toponyme = await toponymeRepository.findOneBy({ + id: toponymeId2, }); - expect(toponymeAfter2._updated).not.toEqual(_updated.toISOString()); + expect(toponymeAfter2.updatedAt).not.toEqual(updatedAt.toISOString()); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(balAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('Delete 400: Bad request', async () => { - const balId = await createBal(); + const balId = await createBal({ nom: 'bal', commune: '91400' }); const deleteBtach: DeleteBatchNumeroDTO = { numerosIds: [], }; @@ -529,138 +521,113 @@ describe('BASE LOCAL MODULE', () => { }); describe('GET /bases-locales/csv', () => { - it('Delete 204', async () => { - const communeUuid = uuid(); - const balId = await createBal({ - banId: communeUuid, + it('GET CSV 200', async () => { + const balId = await createBal({ nom: 'bal', commune: '91534' }); + const { banId: communeUuid } = await balRepository.findOneBy({ + id: balId, }); - const voieUuid1 = uuid(); - const voieId1 = await createVoie({ + const voieId1 = await createVoie(balId, { nom: 'rue de la paix', - commune: '91534', - _bal: balId, - banId: voieUuid1, }); - const voieUuid2 = uuid(); - const voieId2 = await createVoie({ + const { banId: voieUuid1 } = await voieRepository.findOneBy({ + id: voieId1, + }); + const voieId2 = await createVoie(balId, { nom: 'rue de paris', - commune: '91534', - _bal: balId, - banId: voieUuid2, }); - const toponymeUuid1 = uuid(); - const toponymeId1 = await createToponyme({ + const { banId: voieUuid2 } = await voieRepository.findOneBy({ + id: voieId2, + }); + const toponymeId1 = await createToponyme(balId, { nom: 'allée', - commune: '91534', - _bal: balId, - banId: toponymeUuid1, - }); - const numeroUuid1 = uuid(); - const numeroId1 = await createNumero({ - _bal: balId, - banId: numeroUuid1, - voie: voieId1, + }); + const { banId: toponymeUuid1 } = await toponymeRepository.findOneBy({ + id: toponymeId1, + }); + const numeroId1 = await createNumero(balId, voieId1, { numero: 1, suffixe: 'bis', - positions: createPositions(), - toponyme: toponymeId1, + positions: [createPositions()], + toponymeId: toponymeId1, certifie: true, - commune: '91534', }); - const numeroUuid2 = uuid(); - const numeroId2 = await createNumero({ - _bal: balId, - banId: numeroUuid2, - voie: voieId2, + const { banId: numeroUuid1 } = await numeroRepository.findOneBy({ + id: numeroId1, + }); + const numeroId2 = await createNumero(balId, voieId2, { numero: 1, suffixe: 'ter', - positions: createPositions(), - toponyme: toponymeId1, + positions: [createPositions()], + toponymeId: toponymeId1, certifie: false, - commune: '91534', }); - + const { banId: numeroUuid2 } = await numeroRepository.findOneBy({ + id: numeroId2, + }); const deleteBtach: DeleteBatchNumeroDTO = { numerosIds: [numeroId1, numeroId2], }; - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}/csv`) .send(deleteBtach) .set('token', token) .expect(200); - expect(response.headers['content-disposition']).toEqual( 'attachment; filename="bal.csv"', ); expect(response.headers['content-type']).toEqual( 'text/csv; charset=utf-8', ); - const csvFile = `cle_interop;id_ban_commune;id_ban_toponyme;id_ban_adresse;voie_nom;lieudit_complement_nom;numero;suffixe;certification_commune;commune_insee;commune_nom;position;long;lat;x;y;cad_parcelles;source;date_der_maj -91534_xxxx_00001_bis;${communeUuid};${voieUuid1};${numeroUuid1};rue de la paix;allée;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-02 -91534_xxxx_00001_ter;${communeUuid};${voieUuid2};${numeroUuid2};rue de paris;allée;1;ter;0;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-02 -91534_xxxx_99999;${communeUuid};${toponymeUuid1};;allée;;99999;;;91534;Saclay;;;;;;;commune;2000-01-02`; + 91534_xxxx_00001_bis;${communeUuid};${voieUuid1};${numeroUuid1};rue de la paix;allée;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-02 + 91534_xxxx_00001_ter;${communeUuid};${voieUuid2};${numeroUuid2};rue de paris;allée;1;ter;0;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-02 + 91534_xxxx_99999;${communeUuid};${toponymeUuid1};;allée;;99999;;;91534;Saclay;;;;;;;commune;2000-01-02`; expect(response.text.replace(/\s/g, '')).toEqual( csvFile.replace(/\s/g, ''), ); }); }); - describe('GET /bases-locales/csv', () => { - it('Delete 204', async () => { - const balId = await createBal(); - const voieId1 = await createVoie({ + describe('GET /bases-locales/${balId}/voies/csv', () => { + it('GET 200 CSV', async () => { + const balId = await createBal({ nom: 'bal', commune: '91534' }); + const voieId1 = await createVoie(balId, { nom: 'rue de la paix', - commune: '91534', - _bal: balId, }); - const voieId2 = await createVoie({ + const voieId2 = await createVoie(balId, { nom: 'rue de paris', - commune: '91534', - _bal: balId, }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId1, + const numeroId1 = await createNumero(balId, voieId1, { numero: 1, suffixe: 'bis', - positions: createPositions(), + positions: [createPositions()], certifie: true, - commune: '91534', - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), }); - const numeroId2 = await createNumero({ - _bal: balId, - voie: voieId2, + const numeroId2 = await createNumero(balId, voieId2, { numero: 1, suffixe: 'ter', - positions: createPositions(), + positions: [createPositions()], certifie: false, - commune: '91534', - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), }); - const deleteBtach: DeleteBatchNumeroDTO = { numerosIds: [numeroId1, numeroId2], }; - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}/voies/csv`) .send(deleteBtach) .set('token', token) .expect(200); - expect(response.headers['content-disposition']).toEqual( 'attachment; filename="liste-des-voies.csv"', ); expect(response.headers['content-type']).toEqual( 'text/csv; charset=utf-8', ); - const csvFile = `type;nom;nombre_de_numeros;numeros -voie;rue de la paix;1;1bis -voie;rue de paris;1;1ter`; + voie;rue de la paix;1;1bis + voie;rue de paris;1;1ter`; expect(response.text.replace(/\s/g, '')).toEqual( csvFile.replace(/\s/g, ''), ); @@ -670,6 +637,7 @@ voie;rue de paris;1;1ter`; describe('GET /bases-locales/search', () => { it('Search 200', async () => { const balId1 = await createBal({ + nom: 'bal', token: 'coucou', emails: ['living@data.com'], commune: '55326', @@ -679,26 +647,26 @@ voie;rue de paris;1;1ter`; .get(`/bases-locales/search?commune=55326&email=living@data.com`) .expect(200); - const results = [ - { - _id: balId1.toString(), - commune: '55326', - __v: 0, - nbNumeros: 0, - nbNumerosCertifies: 0, - isAllCertified: false, - commentedNumeros: [], - status: 'draft', - _created: _created.toISOString(), - _updated: _updated.toISOString(), - _deleted: null, - }, - ]; + const results = { + id: balId1.toString(), + nom: 'bal', + commune: '55326', + nbNumeros: 0, + nbNumerosCertifies: 0, + isAllCertified: false, + commentedNumeros: [], + status: StatusBaseLocalEnum.DRAFT, + sync: null, + habilitationId: null, + createdAt: createdAt.toISOString(), + updatedAt: updatedAt.toISOString(), + deletedAt: null, + }; expect(response.body.count).toEqual(1); expect(response.body.offset).toEqual(0); expect(response.body.limit).toEqual(20); - expect(response.body.results).toEqual(results); + expect(response.body.results).toMatchObject([results]); }); }); @@ -732,7 +700,7 @@ voie;rue de paris;1;1ter`; emails: ['me@domain.co'], status: 'draft', commune: '27115', - _deleted: null, + deletedAt: null, }); }); @@ -781,12 +749,10 @@ voie;rue de paris;1;1ter`; commune: '27115', emails: ['me@domain.co'], }); - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}`) .set('authorization', `Bearer ${token}`) .expect(200); - expect( Object.keys(response.body).sort((a, b) => a.localeCompare(b)), ).toEqual( @@ -795,118 +761,97 @@ voie;rue de paris;1;1ter`; ), ); }); - it('Get 200 without admin token', async () => { const balId = await createBal({ nom: 'foo', commune: '27115', emails: ['me@domain.co'], }); - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}`) .expect(200); - expect( Object.keys(response.body).sort((a, b) => a.localeCompare(b)), ).toEqual(baseLocalePublicProperties.sort((a, b) => a.localeCompare(b))); }); - it('Get 404 with invalid ID', async () => { const response = await request(app.getHttpServer()) .get(`/bases-locales/5f7b3b7b5d6e4a0017d0d0d0`) .expect(404); - expect(response.body).toEqual({ message: 'BaseLocale 5f7b3b7b5d6e4a0017d0d0d0 not found', statusCode: 404, }); }); - it('Count numeros & certified numeros', async () => { const balId = await createBal({ nom: 'foo', commune: '27115', emails: ['me@domain.co'], }); - - await createNumero({ - _bal: balId, + const voidId = await createVoie(balId, { + nom: 'rue', + }); + await createNumero(balId, voidId, { numero: 1, certifie: true, }); - - await createNumero({ - _bal: balId, + await createNumero(balId, voidId, { numero: 2, certifie: false, }); - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}`) .expect(200); - expect(response.body.nbNumeros).toEqual(2); expect(response.body.nbNumerosCertifies).toEqual(1); expect(response.body.isAllCertified).toEqual(false); }); - it('Count numeros & certified numeros / all certified', async () => { const balId = await createBal({ nom: 'foo', commune: '27115', emails: ['me@domain.co'], }); - - await createNumero({ - _bal: balId, + const voidId = await createVoie(balId, { + nom: 'rue', + }); + await createNumero(balId, voidId, { numero: 1, certifie: true, }); - - await createNumero({ - _bal: balId, + await createNumero(balId, voidId, { numero: 2, certifie: true, }); - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}`) .expect(200); - expect(response.body.nbNumeros).toEqual(2); expect(response.body.nbNumerosCertifies).toEqual(2); expect(response.body.isAllCertified).toEqual(true); }); - it('Commented numeros', async () => { const balId = await createBal({ nom: 'foo', commune: '27115', emails: ['me@domain.co'], }); - - await createNumero({ - _bal: balId, + const voidId = await createVoie(balId, { + nom: 'rue', + }); + await createNumero(balId, voidId, { numero: 1, comment: 'blabla', }); - - await createNumero({ - _bal: balId, + await createNumero(balId, voidId, { numero: 2, comment: 'blibli', }); - const response = await request(app.getHttpServer()) .get(`/bases-locales/${balId}`) .expect(200); - expect(response.body.commentedNumeros.length).toEqual(2); - expect(response.body.commentedNumeros[0].numero).toEqual(1); - expect(response.body.commentedNumeros[0].comment).toEqual('blabla'); - expect(response.body.commentedNumeros[1].numero).toEqual(2); - expect(response.body.commentedNumeros[1].comment).toEqual('blibli'); }); }); @@ -963,7 +908,6 @@ voie;rue de paris;1;1ter`; const updateBALDTO = { nom: '', emails: [], - status: 'blabla', }; const response = await request(app.getHttpServer()) @@ -974,11 +918,7 @@ voie;rue de paris;1;1ter`; expect(response.body).toEqual({ error: 'Bad Request', - message: [ - 'nom should not be empty', - 'status must be one of the following values: draft, published, demo, replaced', - 'emails should not be empty', - ], + message: ['nom should not be empty', 'emails should not be empty'], statusCode: 400, }); }); @@ -1001,25 +941,6 @@ voie;rue de paris;1;1ter`; .send(updateBALDTO) .expect(412); }); - - it('Update 412 modify a published base locale', async () => { - const balId = await createBal({ - nom: 'foo', - commune: '27115', - emails: ['me@domain.co'], - status: StatusBaseLocalEnum.PUBLISHED, - }); - - const updateBALDTO = { - status: StatusBaseLocalEnum.DEMO, - }; - - await request(app.getHttpServer()) - .put(`/bases-locales/${balId}`) - .set('authorization', `Bearer ${token}`) - .send(updateBALDTO) - .expect(412); - }); }); describe('PUT /bases-locales/:id/transform-to-draft', () => { @@ -1067,23 +988,6 @@ voie;rue de paris;1;1ter`; }); describe('DELETE /bases-locales/:id', () => { - it('Delete BAL with admin token (soft delete) 204', async () => { - const balId = await createBal({ - nom: 'foo', - commune: '27115', - emails: ['me@domain.co'], - }); - - await request(app.getHttpServer()) - .delete(`/bases-locales/${balId}`) - .set('authorization', `Bearer ${token}`) - .expect(204); - - const baseLocale = await balModel.findOne({ _id: balId }); - - expect(baseLocale._deleted).not.toBeNull(); - }); - it('Delete demo BAL with admin token (hard delete) 204', async () => { const balId = await createBal({ nom: 'foo', @@ -1097,7 +1001,7 @@ voie;rue de paris;1;1ter`; .set('authorization', `Bearer ${token}`) .expect(204); - const baseLocale = await balModel.findOne({ _id: balId }); + const baseLocale = await balRepository.findOneBy({ id: balId }); expect(baseLocale).toBeNull(); }); @@ -1121,16 +1025,16 @@ voie;rue de paris;1;1ter`; nom: 'foo', commune: '27115', emails: ['me@domain.co'], - _deleted: new Date(), + deletedAt: new Date(), }); await request(app.getHttpServer()) .get(`/bases-locales/${balId}/${token}/recovery`) .expect(307); - const baseLocale = await balModel.findOne({ _id: balId }); + const baseLocale = await balRepository.findOneBy({ id: balId }); - expect(baseLocale._deleted).toBeNull(); + expect(baseLocale.deletedAt).toBeNull(); }); it('Restore deleted BAL / invalid token', async () => { @@ -1138,7 +1042,7 @@ voie;rue de paris;1;1ter`; nom: 'foo', commune: '27115', emails: ['me@domain.co'], - _deleted: new Date(), + deletedAt: new Date(), }); await request(app.getHttpServer()) @@ -1148,12 +1052,12 @@ voie;rue de paris;1;1ter`; }); describe('POST /bases-locales/recovery', () => { - it('Renew token', async () => { + it('Renew token / dele', async () => { const balId = await createBal({ nom: 'foo', commune: '27115', emails: ['me@domain.co'], - _deleted: new Date(), + deletedAt: new Date(), }); const body = { @@ -1183,22 +1087,19 @@ voie;rue de paris;1;1ter`; describe('GET /bases-locales/:id/parcelles', () => { it('GET all assigned parcelles', async () => { const balId = await createBal({ - nom: 'foo', + nom: 'bal', commune: '27115', emails: ['me@domain.co'], - _deleted: new Date(), + deletedAt: new Date(), }); - - await createNumero({ - _bal: balId, + const voieId = await createVoie(balId, { nom: 'rue de hou' }); + await createNumero(balId, voieId, { numero: 1, - commune: '27115', parcelles: ['12345000AA0002'], }); - await createToponyme({ - _bal: balId, - commune: '27115', + await createToponyme(balId, { + nom: 'lieu dit', parcelles: ['12345000AA0002', '12345000AA0005'], }); @@ -1218,26 +1119,22 @@ voie;rue de paris;1;1ter`; emails: ['me@domain.co'], }); - const voieId = await createVoie({ - _bal: balId, + const voieId = await createVoie(balId, { nom: 'rue de la paix', }); - createNumero({ - voie: voieId, + createNumero(balId, voieId, { numero: 1, }); - createNumero({ - voie: voieId, + createNumero(balId, voieId, { numero: 1, certifie: true, }); - createNumero({ - voie: voieId, + createNumero(balId, voieId, { numero: 1, - _deleted: new Date(), + deletedAt: new Date(), }); const response = await request(app.getHttpServer()) @@ -1245,7 +1142,7 @@ voie;rue de paris;1;1ter`; .expect(200); expect(response.body).toHaveLength(1); - expect(response.body[0]._id).toEqual(voieId.toString()); + expect(response.body[0].id).toEqual(voieId); expect(response.body[0].nbNumeros).toEqual(2); expect(response.body[0].nbNumerosCertifies).toEqual(1); expect(response.body[0].isAllCertified).toBeFalsy(); @@ -1273,19 +1170,17 @@ voie;rue de paris;1;1ter`; .send(voie) .expect(201); - expect(response.body._id).toBeDefined(); + expect(response.body.id).toBeDefined(); expect(response.body.nom).toEqual('rue de la paix'); expect(response.body.nomAlt).toBeNull(); expect(response.body.trace).toBeNull(); expect(response.body.typeNumerotation).toEqual( TypeNumerotationEnum.NUMERIQUE, ); - expect(response.body.traceTiles).toBeNull(); expect(response.body.centroid).toBeNull(); - expect(response.body.centroidTiles).toBeNull(); - expect(response.body._deleted).toBeNull(); - expect(response.body._updated).toBeDefined(); - expect(response.body._created).toBeDefined(); + expect(response.body.deletedAt).toBeNull(); + expect(response.body.updatedAt).toBeDefined(); + expect(response.body.createdAt).toBeDefined(); }); it('POST 403', async () => { @@ -1317,15 +1212,13 @@ voie;rue de paris;1;1ter`; emails: ['me@domain.co'], }); - const toponymeId = await createToponyme({ - _bal: balId, + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', }); - await createToponyme({ - _bal: balId, + await createToponyme(balId, { nom: 'rue de paris', - _deleted: new Date(), + deletedAt: new Date(), }); const response = await request(app.getHttpServer()) @@ -1333,7 +1226,7 @@ voie;rue de paris;1;1ter`; .expect(200); expect(response.body).toHaveLength(1); - expect(response.body[0]._id).toEqual(toponymeId.toString()); + expect(response.body[0].id).toEqual(toponymeId.toString()); }); }); @@ -1356,12 +1249,11 @@ voie;rue de paris;1;1ter`; .send(toponyme) .expect(201); - expect(response.body._id).toBeDefined(); + expect(response.body.id).toBeDefined(); expect(response.body.nom).toEqual('rue de la paix'); expect(response.body.nomAlt).toBeNull(); expect(response.body.positions).toEqual([]); expect(response.body.positions).toEqual([]); - expect(response.body.commune).toEqual('27115'); }); it('POST 403', async () => { @@ -1390,36 +1282,28 @@ voie;rue de paris;1;1ter`; commune: '27115', emails: ['me@domain.co'], }); - const voieId = await createVoie({ - _bal: balId, + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _deleted: new Date(), + deletedAt: new Date(), }); - await createVoie({ - _bal: balId, + await createVoie(balId, { nom: 'rue de la paix', }); - const toponymeId = await createToponyme({ - _bal: balId, + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _deleted: new Date(), + deletedAt: new Date(), }); - await createToponyme({ - _bal: balId, + await createToponyme(balId, { nom: 'rue de la paix', }); - const numeroId = await createNumero({ - _bal: balId, + const numeroId = await createNumero(balId, voieId, { numero: 1, - voie: voieId, - _deleted: new Date(), + deletedAt: new Date(), }); - await createNumero({ - _bal: balId, + await createNumero(balId, voieId, { numero: 1, - voie: voieId, }); const response = await request(app.getHttpServer()) @@ -1427,13 +1311,11 @@ voie;rue de paris;1;1ter`; .expect(200); expect(response.body.toponymes).toHaveLength(1); - expect(response.body.toponymes[0]._id).toEqual(toponymeId.toString()); + expect(response.body.toponymes[0].id).toEqual(toponymeId); expect(response.body.voies).toHaveLength(1); - expect(response.body.voies[0]._id).toEqual(voieId.toString()); + expect(response.body.voies[0].id).toEqual(voieId.toString()); expect(response.body.voies[0].numeros).toHaveLength(1); - expect(response.body.voies[0].numeros[0]._id).toEqual( - numeroId.toString(), - ); + expect(response.body.voies[0].numeros[0].id).toEqual(numeroId); }); it('GET 200 voie not deleted', async () => { @@ -1442,35 +1324,27 @@ voie;rue de paris;1;1ter`; commune: '27115', emails: ['me@domain.co'], }); - const voieId = await createVoie({ - _bal: balId, + const voieId = await createVoie(balId, { nom: 'rue de la paix', }); - await createVoie({ - _bal: balId, + await createVoie(balId, { nom: 'rue de la paix', }); - const toponymeId = await createToponyme({ - _bal: balId, + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _deleted: new Date(), + deletedAt: new Date(), }); - await createToponyme({ - _bal: balId, + await createToponyme(balId, { nom: 'rue de la paix', }); - const numeroId = await createNumero({ - _bal: balId, + const numeroId = await createNumero(balId, voieId, { numero: 1, - voie: voieId, - _deleted: new Date(), + deletedAt: new Date(), }); - await createNumero({ - _bal: balId, + await createNumero(balId, voieId, { numero: 1, - voie: voieId, }); const response = await request(app.getHttpServer()) @@ -1478,13 +1352,11 @@ voie;rue de paris;1;1ter`; .expect(200); expect(response.body.toponymes).toHaveLength(1); - expect(response.body.toponymes[0]._id).toEqual(toponymeId.toString()); + expect(response.body.toponymes[0].id).toEqual(toponymeId); expect(response.body.voies).toHaveLength(1); - expect(response.body.voies[0]._id).toEqual(voieId.toString()); + expect(response.body.voies[0].id).toEqual(voieId.toString()); expect(response.body.voies[0].numeros).toHaveLength(1); - expect(response.body.voies[0].numeros[0]._id).toEqual( - numeroId.toString(), - ); + expect(response.body.voies[0].numeros[0].id).toEqual(numeroId); }); }); }); diff --git a/apps/api/test/habilitation.e2e-spec.ts b/apps/api/test/habilitation.e2e-spec.ts index 1cf46b55..1f6b7906 100644 --- a/apps/api/test/habilitation.e2e-spec.ts +++ b/apps/api/test/habilitation.e2e-spec.ts @@ -1,13 +1,23 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; +import { ObjectId } from 'mongodb'; +import { v4 as uuid } from 'uuid'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position } from '@/shared/entities/position.entity'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; import { HabilitationModule } from '@/modules/base_locale/sub_modules/habilitation/habilitation.module'; import MockAdapter from 'axios-mock-adapter'; import axios from 'axios'; @@ -17,66 +27,96 @@ import { } from '@/shared/modules/api_depot/types/habilitation.type'; import { add } from 'date-fns'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { Repository } from 'typeorm'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; describe('HABILITATION MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let balModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); - + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); + // AXIOS const axiosMock = new MockAdapter(axios); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), HabilitationModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + HabilitationModule, + MailerModule, + ], }).compile(); - app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - balModel = app.get>(getModelToken(BaseLocale.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - jest.clearAllMocks(); axiosMock.reset(); - await balModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); async function createBal(props: Partial = {}) { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, status: props.status ?? StatusBaseLocalEnum.DRAFT, token, ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } describe('GET /bases-locales/:id/habilitation', () => { it('expect 200 with admin token', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -84,11 +124,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.PUBLISHED, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -109,7 +149,7 @@ describe('HABILITATION MODULE', () => { }); it('expect 403 without admin token', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -117,11 +157,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.PUBLISHED, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -137,7 +177,7 @@ describe('HABILITATION MODULE', () => { }); it('expect 404 with admin token', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -145,7 +185,7 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.PUBLISHED, - _habilitation: habilitationId.toString(), + habilitationId, }); const responseBody = { code: 404, @@ -171,7 +211,7 @@ describe('HABILITATION MODULE', () => { describe('POST /bases-locales/:id/habilitation', () => { it('expect 201 Create habilitation', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -182,7 +222,7 @@ describe('HABILITATION MODULE', () => { }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -201,12 +241,12 @@ describe('HABILITATION MODULE', () => { JSON.stringify(habilitation), ); - const updatedBAL = await balModel.findById(balId); - expect(updatedBAL._habilitation).toBe(habilitation._id); + const updatedBAL = await balRepository.findOneBy({ id: balId }); + expect(updatedBAL.habilitationId).toBe(habilitation._id); }); it('expect 412 BAL already has habilitation', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -214,11 +254,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -243,7 +283,7 @@ describe('HABILITATION MODULE', () => { describe('POST /bases-locales/:id/habilitation/email/send-pin-code', () => { it('expect 200', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -251,11 +291,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.PENDING, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -282,7 +322,7 @@ describe('HABILITATION MODULE', () => { }); it('expect 412 no pending habilitation', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -290,11 +330,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -319,7 +359,7 @@ describe('HABILITATION MODULE', () => { describe('POST /bases-locales/:id/habilitation/email/validate-pin-code', () => { it('expect 200', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -327,11 +367,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.PENDING, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -356,7 +396,7 @@ describe('HABILITATION MODULE', () => { }); it('expect 200 incorrect PIN code', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -364,11 +404,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.PENDING, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -403,7 +443,7 @@ describe('HABILITATION MODULE', () => { }); it('expect 412 no pending habilitation', async () => { - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const commune = '91534'; const balId = await createBal({ nom: 'BAL de test', @@ -411,11 +451,11 @@ describe('HABILITATION MODULE', () => { emails: ['test@test.fr'], token, status: StatusBaseLocalEnum.DRAFT, - _habilitation: habilitationId.toString(), + habilitationId, }); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.REJECTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, diff --git a/apps/api/test/jest-e2e.json b/apps/api/test/jest-e2e.json index 2179991b..7cddb5e6 100644 --- a/apps/api/test/jest-e2e.json +++ b/apps/api/test/jest-e2e.json @@ -1,8 +1,13 @@ { - "moduleFileExtensions": ["js", "json", "ts"], + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], "rootDir": ".", "testEnvironment": "node", "testRegex": ".e2e-spec.ts$", + "testTimeout": 6000000, "transform": { "^.+\\.(t|j)s$": "ts-jest" }, @@ -11,4 +16,4 @@ "^@/modules/(.*)$": "/../src/modules/$1", "^@/shared/(.*)$": "/../../../libs/shared/src/$1" } -} +} \ No newline at end of file diff --git a/apps/api/test/numero.e2e-spec.ts b/apps/api/test/numero.e2e-spec.ts index 39904ae9..c1bcd2fa 100644 --- a/apps/api/test/numero.e2e-spec.ts +++ b/apps/api/test/numero.e2e-spec.ts @@ -1,39 +1,72 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; - -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; +import { ObjectId } from 'mongodb'; +import { v4 as uuid } from 'uuid'; + +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; import { NumeroModule } from '@/modules/numeros/numero.module'; import { UpdateNumeroDTO } from '@/modules/numeros/dto/update_numero.dto'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; describe('NUMERO', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), NumeroModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + NumeroModule, + MailerModule, + ], }).compile(); app = moduleFixture.createNestApplication(); @@ -41,88 +74,93 @@ describe('NUMERO', () => { await app.init(); // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - await voieModel.deleteMany({}); - await balModel.deleteMany({}); - await numeroModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); - async function createBal() { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + async function createBal(props: Partial = {}) { + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, + ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, - _created, - _updated, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, - _created, - _updated, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } describe('GET /numero', () => { it('Return 200 numero', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); const response = await request(app.getHttpServer()) .get(`/numeros/${numeroId}`) .expect(200); - expect(response.body._id).toEqual(numeroId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(numeroId); + expect(response.body.balId).toEqual(balId); expect(response.body.numero).toEqual(99); - expect(response.body.voie).toEqual(voieId.toString()); - expect(response.body.parcelles).toEqual([]); + expect(response.body.voieId).toEqual(voieId); + expect(response.body.parcelles).toBeNull(); expect(response.body.positions).toEqual([]); - expect(response.body.voie).toEqual(voieId.toString()); }); it('Return 200 numero without comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, comment: 'coucou', }); @@ -134,11 +172,9 @@ describe('NUMERO', () => { }); it('Return 200 numero with comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, comment: 'coucou', }); @@ -151,7 +187,7 @@ describe('NUMERO', () => { }); it('Return 404', async () => { - const numeroId = new Types.ObjectId(); + const numeroId = new ObjectId(); await request(app.getHttpServer()) .get(`/numeros/${numeroId}`) .expect(404); @@ -160,11 +196,9 @@ describe('NUMERO', () => { describe('PUT /numero', () => { it('Update 200 numero', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); @@ -178,18 +212,21 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._id).toEqual(numeroId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(numeroId); + expect(response.body.balId).toEqual(balId); expect(response.body.numero).toEqual(100); - expect(response.body.voie).toEqual(voieId.toString()); - expect(response.body.parcelles).toEqual([]); + expect(response.body.voieId).toEqual(voieId); + expect(response.body.parcelles).toBeNull(); expect(response.body.positions).toEqual([]); - expect(response.body.voie).toEqual(voieId.toString()); - const voieDbAfter = await voieModel.findOne({ _id: voieId }); - const balDbAfter = await balModel.findOne({ _id: balId }); - expect(voieDbAfter._updated).not.toEqual(_updated.toISOString()); - expect(balDbAfter._updated).not.toEqual(_updated.toISOString()); + const voieDbAfter = await voieRepository.findOneBy({ id: voieId }); + const balDbAfter = await balRepository.findOneBy({ id: balId }); + expect(voieDbAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); + expect(balDbAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); }); it('Update 404 Numero Not Found', async () => { @@ -197,7 +234,7 @@ describe('NUMERO', () => { numero: 100, }; - const numeroId = new Types.ObjectId(); + const numeroId = new ObjectId(); await request(app.getHttpServer()) .put(`/numeros/${numeroId}`) .send(updatedNumero) @@ -206,16 +243,14 @@ describe('NUMERO', () => { }); it('Update 404 Voie Not Found', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); const updatedNumero: UpdateNumeroDTO = { - voie: new Types.ObjectId(), + voieId: new ObjectId().toHexString(), }; await request(app.getHttpServer()) @@ -226,16 +261,14 @@ describe('NUMERO', () => { }); it('Update 404 Toponyme Not Found', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); const updatedNumero: UpdateNumeroDTO = { - toponyme: new Types.ObjectId(), + toponymeId: new ObjectId().toHexString(), }; await request(app.getHttpServer()) @@ -246,11 +279,9 @@ describe('NUMERO', () => { }); it('Update 403 Forbiden', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); @@ -263,25 +294,25 @@ describe('NUMERO', () => { .send(updatedNumero) .expect(403); - const voieDbAfter = await voieModel.findOne({ _id: voieId }); - const balDbAfter = await balModel.findOne({ _id: balId }); - expect(voieDbAfter._updated.toISOString()).toEqual( - _updated.toISOString(), + const voieDbAfter = await voieRepository.findOneBy({ id: voieId }); + const balDbAfter = await balRepository.findOneBy({ id: balId }); + expect(voieDbAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), + ); + expect(balDbAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), ); - expect(balDbAfter._updated.toISOString()).toEqual(_updated.toISOString()); }); - it('Update 200 check field _updated of voie and bal', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + it('Update 200 check field updatedAt of voie and bal', async () => { + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); - const voieDbBefore = await voieModel.findOne({ _id: voieId }); - const balDbBefore = await balModel.findOne({ _id: balId }); + const voieDbBefore = await voieRepository.findOneBy({ id: voieId }); + const balDbBefore = await balRepository.findOneBy({ id: balId }); const updatedNumero: UpdateNumeroDTO = { numero: 100, @@ -293,18 +324,20 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(200); - const voieDbAfter = await voieModel.findOne({ _id: voieId }); - const balDbAfter = await balModel.findOne({ _id: balId }); - expect(voieDbBefore._updated).not.toEqual(voieDbAfter._updated); - expect(balDbBefore._updated).not.toEqual(balDbAfter._updated); + const voieDbAfter = await voieRepository.findOneBy({ id: voieId }); + const balDbAfter = await balRepository.findOneBy({ id: balId }); + expect(voieDbBefore.updatedAt.toISOString()).not.toEqual( + voieDbAfter.updatedAt.toISOString(), + ); + expect(balDbBefore.updatedAt.toISOString()).not.toEqual( + balDbAfter.updatedAt.toISOString(), + ); }); - it('Update 200 check field _updated is UPDATE', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + it('Update 200 check field updatedAt is UPDATE', async () => { + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); @@ -318,35 +351,33 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(200); - const numeroDbAfter = await numeroModel.findOne({ _id: numeroId }); - const voieDbAfter = await voieModel.findOne({ _id: voieId }); - const balDbAfter = await balModel.findOne({ _id: balId }); + const numeroDbAfter = await numeroRepository.findOneBy({ id: numeroId }); + const voieDbAfter = await voieRepository.findOneBy({ id: voieId }); + const balDbAfter = await balRepository.findOneBy({ id: balId }); - expect(numeroDbAfter._updated).not.toEqual(_updated.toISOString()); - expect(voieDbAfter._updated).not.toEqual(_updated.toISOString()); - expect(balDbAfter._updated).not.toEqual(_updated.toISOString()); + expect(numeroDbAfter.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(voieDbAfter.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(balDbAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('Update 200 check field tiles Numero is UPDATE and centroid, centroidTiles voie is UPDATE', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); - const numeroDbBefore = await numeroModel.findOne({ _id: numeroId }); - const voieDbBefore = await voieModel.findOne({ _id: voieId }); + const voieDbBefore = await voieRepository.findOneBy({ id: voieId }); const updatedNumero: UpdateNumeroDTO = { positions: [ { - type: PositionTypeEnum.ENTREE, + id: new ObjectId().toHexString(), + type: PositionTypeEnum.BATIMENT, source: 'ban', point: { type: 'Point', - coordinates: [8, 42], + coordinates: [9, 42], }, }, ], @@ -358,23 +389,19 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(200); - const numeroDbAfter = await numeroModel.findOne({ _id: numeroId }); - const voieDbAfter = await voieModel.findOne({ _id: voieId }); - expect(numeroDbBefore.tiles).not.toEqual(numeroDbAfter.tiles); + const voieDbAfter = await voieRepository.findOneBy({ id: voieId }); expect(voieDbBefore.centroid).not.toEqual(voieDbAfter.centroid); - expect(voieDbBefore.centroidTiles).not.toEqual(voieDbAfter.centroidTiles); }); it('Update 200 replace voie', async () => { - const balId = await createBal(); - const voieId1 = await createVoie({ nom: 'rue de la paix' }); - const voieId2 = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId1, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId1 = await createVoie(balId, { nom: 'rue de la paix' }); + const voieId2 = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId1, { numero: 99, positions: [ { + id: new ObjectId().toHexString(), type: PositionTypeEnum.INCONNUE, source: 'ban', point: { @@ -385,10 +412,8 @@ describe('NUMERO', () => { ], }); - const voie1DbBefore: Voie = await voieModel.findOne({ _id: voieId1 }); - const updatedNumero: UpdateNumeroDTO = { - voie: voieId2, + voieId: voieId2, }; await request(app.getHttpServer()) @@ -397,28 +422,25 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(200); - const voie1DbAfter: Voie = await voieModel.findOne({ _id: voieId1 }); - const voie2DbAfter: Voie = await voieModel.findOne({ _id: voieId2 }); - - expect(voie1DbBefore.centroid).not.toBe(null); - expect(voie1DbBefore.centroidTiles).not.toBe(null); + const voie1DbAfter: Voie = await voieRepository.findOneBy({ + id: voieId1, + }); + const voie2DbAfter: Voie = await voieRepository.findOneBy({ + id: voieId2, + }); expect(voie1DbAfter.centroid).toBe(null); - expect(voie1DbAfter.centroidTiles).toBe(null); expect(voie2DbAfter.centroid).not.toBe(null); - expect(voie2DbAfter.centroidTiles).not.toBe(null); - expect(voie1DbAfter._updated).not.toEqual(_updated.toISOString()); - expect(voie2DbAfter._updated).not.toEqual(_updated.toISOString()); + expect(voie1DbAfter.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(voie2DbAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); }); describe('DELETE /numero', () => { it('Delete 204 numero', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); @@ -427,19 +449,19 @@ describe('NUMERO', () => { .set('authorization', `Bearer ${token}`) .expect(204); - const numeroDeleted: Numero = await numeroModel.findOne({ - _id: numeroId, + const numeroDeleted: Numero = await numeroRepository.findOneBy({ + id: numeroId, }); expect(numeroDeleted).toBe(null); - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated).not.toEqual(_updated.toISOString()); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(balAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('Delete 404 NOT FOUND', async () => { - const numeroId = new Types.ObjectId(); + const numeroId = new ObjectId(); await request(app.getHttpServer()) .delete(`/numeros/${numeroId}`) .set('authorization', `Bearer ${token}`) @@ -447,11 +469,9 @@ describe('NUMERO', () => { }); it('Delete 403 FORBIDEN', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); @@ -459,47 +479,46 @@ describe('NUMERO', () => { .delete(`/numeros/${numeroId}`) .expect(403); - const numeroDeleted: Numero = await numeroModel.findOne({ - _id: numeroId, + const numeroDeleted: Numero = await numeroRepository.findOneBy({ + id: numeroId, }); expect(numeroDeleted).not.toBe(null); - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated.toISOString()).toEqual(_updated.toISOString()); - expect(balAfter._updated.toISOString()).toEqual(_updated.toISOString()); + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('SOFT DELETE /numero', () => { it('Soft Delete 200 numero', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, }); await request(app.getHttpServer()) .put(`/numeros/${numeroId}/soft-delete`) .set('authorization', `Bearer ${token}`) - .expect(200); + .expect(204); - const numeroDeleted: Numero = await numeroModel.findOne({ - _id: numeroId, + const numeroDeleted: Numero = await numeroRepository.findOneBy({ + id: numeroId, }); + expect(numeroDeleted).toBeNull(); - expect(numeroDeleted._deleted).not.toBe(null); - - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated).not.toEqual(_updated.toISOString()); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(balAfter.updatedAt).not.toEqual(updatedAt.toISOString()); }); it('Soft Delete 404 NOT FOUND', async () => { - const numeroId = new Types.ObjectId(); + const numeroId = new ObjectId(); await request(app.getHttpServer()) .put(`/numeros/${numeroId}/soft-delete`) .set('authorization', `Bearer ${token}`) @@ -507,27 +526,27 @@ describe('NUMERO', () => { }); it('Soft Delete 403 FORBIDEN', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix' }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId = await createNumero(balId, voieId, { numero: 99, - _deleted: null, + deletedAt: null, }); await request(app.getHttpServer()) .put(`/numeros/${numeroId}/soft-delete`) .expect(403); - const numeroDeleted: Numero = await numeroModel.findOne({ - _id: numeroId, + const numeroDeleted: Numero = await numeroRepository.findOneBy({ + id: numeroId, }); - expect(numeroDeleted._deleted).toBe(null); - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated.toISOString()).toEqual(_updated.toISOString()); - expect(balAfter._updated.toISOString()).toEqual(_updated.toISOString()); + expect(numeroDeleted.deletedAt).toBe(null); + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); }); diff --git a/apps/api/test/publication.e2e-spec.ts b/apps/api/test/publication.e2e-spec.ts index 943d2bcf..76290366 100644 --- a/apps/api/test/publication.e2e-spec.ts +++ b/apps/api/test/publication.e2e-spec.ts @@ -1,24 +1,26 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; +import { ObjectId } from 'mongodb'; import axios from 'axios'; import { add, sub } from 'date-fns'; import MockAdapter from 'axios-mock-adapter'; import { v4 as uuid } from 'uuid'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; -import { Position } from '@/shared/schemas/position.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; import { + BaseLocale, StatusBaseLocalEnum, StatusSyncEnum, -} from '@/shared/schemas/base_locale/status.enum'; +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; import { Habilitation, StatusHabiliation, @@ -30,140 +32,172 @@ import { import { BaseLocaleModule } from '@/modules/base_locale/base_locale.module'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; +import { Point, Repository } from 'typeorm'; describe('PUBLICATION MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; - let toponymeModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); // AXIOS const axiosMock = new MockAdapter(axios); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), BaseLocaleModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + BaseLocaleModule, + MailerModule, + ], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); - toponymeModel = app.get>(getModelToken(Toponyme.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - jest.clearAllMocks(); axiosMock.reset(); - await toponymeModel.deleteMany({}); - await voieModel.deleteMany({}); - await balModel.deleteMany({}); - await numeroModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); async function createBal(props: Partial = {}) { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, - _created, - _updated, ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } - function createPositions(coordinates: number[] = [8, 42]): Position[] { - return [ - { - type: PositionTypeEnum.INCONNUE, - source: 'ban', - point: { - type: 'Point', - coordinates, - }, - }, - ]; + function createPositions(coordinates: number[] = [8, 42]): Position { + const id = new ObjectId().toHexString(); + const point: Point = { + type: 'Point', + coordinates, + }; + return { + id, + type: PositionTypeEnum.INCONNUE, + source: 'ban', + point, + } as Position; } describe('POST /bases-locales/sync/exec', () => { it('Publish 200 DRAFT', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); - const communeUuid = uuid(); + const habilitationId = new ObjectId().toHexString(); const balId = await createBal({ + nom: 'bal', commune, - banId: communeUuid, - _habilitation: habilitationId.toString(), + habilitationId: habilitationId, status: StatusBaseLocalEnum.DRAFT, emails: ['test@test.fr'], }); - const toponymeUuid = uuid(); - const voieId = await createVoie({ + const { banId: communeUuid } = await balRepository.findOneBy({ + id: balId, + }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - commune, - _bal: balId, - banId: toponymeUuid, }); - const numeroUuid = uuid(); - await createNumero({ - _bal: balId, - banId: numeroUuid, - voie: voieId, + const { banId: voieUuid } = await voieRepository.findOneBy({ + id: voieId, + }); + const numeroId = await createNumero(balId, voieId, { numero: 1, suffixe: 'bis', - positions: createPositions(), + positions: [createPositions()], certifie: true, - commune, - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), + }); + const { banId: numeroUuid } = await numeroRepository.findOneBy({ + id: numeroId, }); - // MOCK AXIOS const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -173,9 +207,9 @@ describe('PUBLICATION MODULE', () => { .onGet(`habilitations/${habilitationId}`) .reply(200, habilitation); - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PENDING, ready: false, @@ -191,7 +225,7 @@ describe('PUBLICATION MODULE', () => { axiosMock.onPost(`/revisions/${revisionId}/compute`).reply(200, revision); const csvFile = `cle_interop;id_ban_commune;id_ban_toponyme;id_ban_adresse;voie_nom;lieudit_complement_nom;numero;suffixe;certification_commune;commune_insee;commune_nom;position;long;lat;x;y;cad_parcelles;source;date_der_maj - 91534_xxxx_00001_bis;${communeUuid};${toponymeUuid};${numeroUuid};rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; + 91534_xxxx_00001_bis;${communeUuid};${voieUuid};${numeroUuid};rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; axiosMock .onPut(`/revisions/${revisionId}/files/bal`) .reply(({ data }) => { @@ -200,7 +234,7 @@ describe('PUBLICATION MODULE', () => { }); const publishedRevision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PUBLISHED, ready: true, @@ -212,9 +246,7 @@ describe('PUBLICATION MODULE', () => { }, }; axiosMock.onPost(`/revisions/${revisionId}/publish`).reply(({ data }) => { - expect(JSON.parse(data).habilitationId).toEqual( - habilitationId.toString(), - ); + expect(JSON.parse(data).habilitationId).toEqual(habilitationId); return [200, publishedRevision]; }); @@ -225,25 +257,25 @@ describe('PUBLICATION MODULE', () => { .expect(200); const syncExpected = { - currentUpdated: _updated.toISOString(), status: StatusSyncEnum.SYNCED, isPaused: false, - lastUploadedRevisionId: revisionId.toString(), + lastUploadedRevisionId: revisionId, }; - expect(response.body._id).toEqual(balId.toString()); + expect(response.body.id).toEqual(balId); expect(response.body.commune).toEqual(commune); expect(response.body.status).toEqual(StatusBaseLocalEnum.PUBLISHED); - expect(response.body.sync).toEqual(syncExpected); + expect(response.body.sync).toMatchObject(syncExpected); + expect(response.body.sync.currentUpdated).toBeDefined(); }); it('Publish 200 OUTDATED', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PENDING, ready: false, @@ -262,11 +294,10 @@ describe('PUBLICATION MODULE', () => { }; // BAL - const communeUuid = uuid(); const balId = await createBal({ + nom: 'bal', commune, - banId: communeUuid, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { @@ -274,24 +305,24 @@ describe('PUBLICATION MODULE', () => { lastUploadedRevisionId: revisionId, }, }); - const toponymeUuid = uuid(); - const voieId = await createVoie({ + const { banId: communeUuid } = await balRepository.findOneBy({ + id: balId, + }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - commune, - _bal: balId, - banId: toponymeUuid, }); - const numeroUuid = uuid(); - await createNumero({ - _bal: balId, - banId: numeroUuid, - voie: voieId, + const { banId: toponymeUuid } = await voieRepository.findOneBy({ + id: voieId, + }); + const numeroId = await createNumero(balId, voieId, { numero: 1, suffixe: 'bis', - positions: createPositions(), + positions: [createPositions()], certifie: true, - commune, - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), + }); + const { banId: numeroUuid } = await numeroRepository.findOneBy({ + id: numeroId, }); // MOCK AXIOS @@ -300,7 +331,7 @@ describe('PUBLICATION MODULE', () => { .reply(200, revision); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -315,7 +346,7 @@ describe('PUBLICATION MODULE', () => { axiosMock.onPost(`/revisions/${revisionId}/compute`).reply(200, revision); const csvFile = `cle_interop;id_ban_commune;id_ban_toponyme;id_ban_adresse;voie_nom;lieudit_complement_nom;numero;suffixe;certification_commune;commune_insee;commune_nom;position;long;lat;x;y;cad_parcelles;source;date_der_maj - 91534_xxxx_00001_bis;${communeUuid};${toponymeUuid};${numeroUuid};rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; + 91534_xxxx_00001_bis;${communeUuid};${toponymeUuid};${numeroUuid};rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; axiosMock .onPut(`/revisions/${revisionId}/files/bal`) .reply(({ data }) => { @@ -324,7 +355,7 @@ describe('PUBLICATION MODULE', () => { }); const publishedRevision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PUBLISHED, ready: true, @@ -336,9 +367,7 @@ describe('PUBLICATION MODULE', () => { }, }; axiosMock.onPost(`/revisions/${revisionId}/publish`).reply(({ data }) => { - expect(JSON.parse(data).habilitationId).toEqual( - habilitationId.toString(), - ); + expect(JSON.parse(data).habilitationId).toEqual(habilitationId); return [200, publishedRevision]; }); @@ -349,25 +378,25 @@ describe('PUBLICATION MODULE', () => { .expect(200); const syncExpected = { - currentUpdated: _updated.toISOString(), status: StatusSyncEnum.SYNCED, isPaused: false, - lastUploadedRevisionId: revisionId.toString(), + lastUploadedRevisionId: revisionId, }; - expect(response.body._id).toEqual(balId.toString()); + expect(response.body.id).toEqual(balId); expect(response.body.commune).toEqual(commune); expect(response.body.status).toEqual(StatusBaseLocalEnum.PUBLISHED); - expect(response.body.sync).toEqual(syncExpected); + expect(response.body.sync).toMatchObject(syncExpected); + expect(response.body.sync.currentUpdated).toBeDefined(); }); it('Publish 200 OUTDATED same hash', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PENDING, ready: false, @@ -380,15 +409,17 @@ describe('PUBLICATION MODULE', () => { files: [ { type: 'bal', - hash: '8d0cda05e7b8b58a92a18cd40d0549c1d3f8ac1ac9586243aa0e3f885bb870c4', + hash: '0c5d808a7e5612c9467607c574cb2317a76fe04d493efbd61b55a31bbd194227', }, ], }; // BAL const balId = await createBal({ + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { @@ -396,20 +427,17 @@ describe('PUBLICATION MODULE', () => { lastUploadedRevisionId: revisionId, }, }); - const voieId = await createVoie({ + const voieId = await createVoie(balId, { nom: 'rue de la paix', - commune, - _bal: balId, + banId: '26734c2d-2a14-4eeb-ac5b-1be055c0a5ae', }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 1, suffixe: 'bis', - positions: createPositions(), + banId: '2da3bb47-1a10-495a-8c29-6b8d0e79f9af', + positions: [createPositions()], certifie: true, - commune, - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), }); // MOCK AXIOS @@ -418,7 +446,7 @@ describe('PUBLICATION MODULE', () => { .reply(200, revision); const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -435,24 +463,25 @@ describe('PUBLICATION MODULE', () => { .expect(200); const syncExpected = { - currentUpdated: _updated.toISOString(), status: StatusSyncEnum.SYNCED, isPaused: false, - lastUploadedRevisionId: revisionId.toString(), + lastUploadedRevisionId: revisionId, }; - expect(response.body._id).toEqual(balId.toString()); + expect(response.body.id).toEqual(balId); expect(response.body.commune).toEqual(commune); expect(response.body.status).toEqual(StatusBaseLocalEnum.PUBLISHED); - expect(response.body.sync).toEqual(syncExpected); + expect(response.body.sync).toMatchObject(syncExpected); + expect(response.body.sync.currentUpdated).toBeDefined(); }); it('Publish 412 status DEMO', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const balId = await createBal({ + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.DEMO, emails: ['test@test.fr'], }); @@ -475,6 +504,7 @@ describe('PUBLICATION MODULE', () => { it('Publish 412 no habilitation', async () => { const commune = '91534'; const balId = await createBal({ + nom: 'bal', commune, status: StatusBaseLocalEnum.DRAFT, emails: ['test@test.fr'], @@ -496,17 +526,18 @@ describe('PUBLICATION MODULE', () => { it('Publish 412 habilitation PENDING', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const balId = await createBal({ + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.DRAFT, emails: ['test@test.fr'], }); // MOCK AXIOS const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.PENDING, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, @@ -532,17 +563,18 @@ describe('PUBLICATION MODULE', () => { it('Publish 412 habilitation expired', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const balId = await createBal({ + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.DRAFT, emails: ['test@test.fr'], }); // MOCK AXIOS const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: sub(new Date(), { months: 1 }), codeCommune: commune, @@ -568,17 +600,18 @@ describe('PUBLICATION MODULE', () => { it('Publish 412 no numero', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); const balId = await createBal({ + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.DRAFT, emails: ['test@test.fr'], }); // MOCK AXIOS const habilitation: Habilitation = { - _id: habilitationId.toString(), + _id: habilitationId, status: StatusHabiliation.ACCEPTED, expiresAt: add(new Date(), { months: 1 }), codeCommune: commune, diff --git a/apps/api/test/stats.e2e-spec.ts b/apps/api/test/stats.e2e-spec.ts index 19db76c3..3398bc30 100644 --- a/apps/api/test/stats.e2e-spec.ts +++ b/apps/api/test/stats.e2e-spec.ts @@ -1,79 +1,118 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; +import { v4 as uuid } from 'uuid'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position } from '@/shared/entities/position.entity'; import { StatsModule } from '@/modules/stats/stats.module'; import { CodeCommuneDTO } from '@/modules/stats/dto/code_commune.dto'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { Repository } from 'typeorm'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; describe('STATS MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let balModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let balRepository: Repository; + // VAR const token = 'xxxx'; + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), StatsModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + StatsModule, + MailerModule, + ], }).compile(); - app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - // INIT MODEL - balModel = app.get>(getModelToken(BaseLocale.name)); + // INIT REPOSITORY + balRepository = app.get(getRepositoryToken(BaseLocale)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - await balModel.deleteMany({}); + await balRepository.delete({}); }); async function createBal(props: Partial = {}) { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } describe('GET /stats/bals', () => { it('Return 200', async () => { await createBal({ + nom: 'bal', commune: '54084', - _created: new Date('2019-01-01'), + createdAt: new Date('2019-01-01'), status: StatusBaseLocalEnum.DRAFT, }); const balId1 = await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-02'), + createdAt: new Date('2019-01-02'), status: StatusBaseLocalEnum.DRAFT, }); const balId2 = await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-03'), + createdAt: new Date('2019-01-03'), status: StatusBaseLocalEnum.PUBLISHED, }); @@ -88,12 +127,12 @@ describe('STATS MODULE', () => { const expectedRes = [ { - _id: balId1.toString(), + id: balId1, commune: '37003', status: StatusBaseLocalEnum.DRAFT, }, { - _id: balId2.toString(), + id: balId2, commune: '37003', status: StatusBaseLocalEnum.PUBLISHED, }, @@ -106,18 +145,21 @@ describe('STATS MODULE', () => { describe('GET /stats/bals/status', () => { it('Return 200', async () => { await createBal({ + nom: 'bal', commune: '54084', - _created: new Date('2019-01-01'), + createdAt: new Date('2019-01-01'), status: StatusBaseLocalEnum.DRAFT, }); await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-02'), + createdAt: new Date('2019-01-02'), status: StatusBaseLocalEnum.DRAFT, }); await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-03'), + createdAt: new Date('2019-01-03'), status: StatusBaseLocalEnum.PUBLISHED, }); @@ -135,7 +177,6 @@ describe('STATS MODULE', () => { count: 1, }, ]; - expect(response.body).toContainEqual(expectedRes[0]); expect(response.body).toContainEqual(expectedRes[1]); }); @@ -144,18 +185,21 @@ describe('STATS MODULE', () => { describe('GET /stats/bals/creations', () => { it('Return 200', async () => { await createBal({ + nom: 'bal', commune: '54084', - _created: new Date('2019-01-01'), + createdAt: new Date('2019-01-01'), status: StatusBaseLocalEnum.DRAFT, }); await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-02'), + createdAt: new Date('2019-01-02'), status: StatusBaseLocalEnum.DRAFT, }); await createBal({ + nom: 'bal', commune: '37003', - _created: new Date('2019-01-02'), + createdAt: new Date('2019-01-02'), status: StatusBaseLocalEnum.PUBLISHED, }); diff --git a/apps/api/test/toponyme.e2e-spec.ts b/apps/api/test/toponyme.e2e-spec.ts index 1cb5339e..c324ae42 100644 --- a/apps/api/test/toponyme.e2e-spec.ts +++ b/apps/api/test/toponyme.e2e-spec.ts @@ -1,148 +1,183 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; - -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { Point, Repository } from 'typeorm'; +import { ObjectId } from 'mongodb'; +import { v4 as uuid } from 'uuid'; + +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; import { ToponymeModule } from '@/modules/toponyme/toponyme.module'; import { UpdateToponymeDTO } from '@/modules/toponyme/dto/update_toponyme.dto'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; -import { Position } from '@/shared/schemas/position.schema'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; describe('TOPONYME MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; - let toponymeModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), ToponymeModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + ToponymeModule, + MailerModule, + ], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); - toponymeModel = app.get>(getModelToken(Toponyme.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - await toponymeModel.deleteMany({}); - await voieModel.deleteMany({}); - await balModel.deleteMany({}); - await numeroModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); - async function createBal() { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + async function createBal(props: Partial = {}) { + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, + ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, - _created, - _updated, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createToponyme(props: Partial = {}) { - const toponymeId = new Types.ObjectId(); - const toponyme: Partial = { - _id: toponymeId, - _created, - _updated, + async function createToponyme(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await toponymeModel.create(toponyme); - return toponymeId; + const entityToInsert = toponymeRepository.create(payload); + const result = await toponymeRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, - _created, - _updated, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } - function createPositions(coordinates: number[] = [8, 42]): Position[] { - return [ - { - type: PositionTypeEnum.BATIMENT, - source: 'ban', - point: { - type: 'Point', - coordinates, - }, - }, - ]; + function createPositions(coordinates: number[] = [8, 42]): Position { + const id = new ObjectId().toHexString(); + const point: Point = { + type: 'Point', + coordinates, + }; + return { + id, + type: PositionTypeEnum.INCONNUE, + source: 'ban', + point, + } as Position; } describe('GET /toponymes/numeros', () => { it('Return 200 numero without comment', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ nom: 'allée', _bal: balId }); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'allée' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + await createNumero(balId, voieId, { numero: 1, comment: 'coucou', - toponyme: toponymeId, + toponymeId, }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 1, comment: 'coucou', - toponyme: toponymeId, + toponymeId, }); const response = await request(app.getHttpServer()) @@ -154,27 +189,23 @@ describe('TOPONYME MODULE', () => { expect(response.body[1].numero).toEqual(1); expect(response.body[0].comment).toBeNull(); expect(response.body[1].comment).toBeNull(); - expect(response.body[0].voie._id).toEqual(voieId.toString()); - expect(response.body[1].voie._id).toEqual(voieId.toString()); + expect(response.body[0].voie.id).toEqual(voieId); + expect(response.body[1].voie.id).toEqual(voieId); }); it('Return 200 numero with comment', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ nom: 'allée', _bal: balId }); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'allée' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + await createNumero(balId, voieId, { numero: 1, comment: 'coucou', - toponyme: toponymeId, + toponymeId, }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 1, comment: 'coucou', - toponyme: toponymeId, + toponymeId, }); const response = await request(app.getHttpServer()) @@ -187,24 +218,22 @@ describe('TOPONYME MODULE', () => { expect(response.body[1].numero).toEqual(1); expect(response.body[0].comment).toEqual('coucou'); expect(response.body[1].comment).toEqual('coucou'); - expect(response.body[0].voie._id).toEqual(voieId.toString()); - expect(response.body[1].voie._id).toEqual(voieId.toString()); + expect(response.body[0].voie.id).toEqual(voieId.toString()); + expect(response.body[1].voie.id).toEqual(voieId.toString()); }); }); describe('PUT /toponymes', () => { it('Return 200', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', }); const changes: UpdateToponymeDTO = { nom: 'coucou', nomAlt: null, parcelles: ['12345000AA0002', '12345000AA0005'], - positions: createPositions(), + positions: [createPositions()], }; const response = await request(app.getHttpServer()) @@ -213,8 +242,8 @@ describe('TOPONYME MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._id).toEqual(toponymeId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(toponymeId); + expect(response.body.balId).toEqual(balId); expect(response.body.nom).toEqual('coucou'); expect(response.body.parcelles).toEqual([ '12345000AA0002', @@ -222,23 +251,21 @@ describe('TOPONYME MODULE', () => { ]); expect(response.body.positions).toBeDefined(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', }); const changes: UpdateToponymeDTO = { nom: 'coucou', nomAlt: null, parcelles: ['12345000AA0002', '12345000AA0005'], - positions: createPositions(), + positions: [createPositions()], }; await request(app.getHttpServer()) @@ -246,139 +273,132 @@ describe('TOPONYME MODULE', () => { .send(changes) .expect(403); - const toponyme = await toponymeModel.findOne(toponymeId); + const toponyme = await toponymeRepository.findOneBy({ id: toponymeId }); expect(toponyme.nom).toEqual('rue de la paix'); - expect(toponyme.parcelles).toEqual([]); + expect(toponyme.parcelles).toBeNull(); expect(toponyme.positions).toEqual([]); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('PUT /soft-delete', () => { - it('Return 200', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + it('Return 204', async () => { + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', }); - const numeroId = await createNumero({ + const voidId = await createVoie(balId, { + nom: 'rue de la paix', + }); + const numeroId = await createNumero(balId, voidId, { numero: 1, - toponyme: toponymeId, + toponymeId: toponymeId, }); - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .put(`/toponymes/${toponymeId}/soft-delete`) .set('authorization', `Bearer ${token}`) - .expect(200); + .expect(204); - expect(response.body._deleted).not.toBeNull(); + const toponyme = await toponymeRepository.findOne({ + where: { id: toponymeId }, + withDeleted: true, + }); + expect(toponyme.deletedAt).not.toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); - const numero = await numeroModel.findOne(numeroId); - expect(numero.toponyme).toBeNull(); + const numero = await numeroRepository.findOneBy({ id: numeroId }); + expect(numero.toponymeId).toBeNull(); }); it('Return 403', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', - _deleted: null, + deletedAt: null, }); await request(app.getHttpServer()) .put(`/toponymes/${toponymeId}/soft-delete`) .expect(403); - const toponyme = await toponymeModel.findOne(toponymeId); - expect(toponyme._deleted).toBeNull(); + const toponyme = await toponymeRepository.findOneBy({ id: toponymeId }); + expect(toponyme.deletedAt).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('PUT /restore', () => { it('Return 200', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', - _deleted: _updated, }); const response = await request(app.getHttpServer()) .put(`/toponymes/${toponymeId}/restore`) .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._deleted).toBeNull(); + expect(response.body.deletedAt).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', - _deleted: _updated, }); const response = await request(app.getHttpServer()) .put(`/toponymes/${toponymeId}/restore`) .expect(403); - expect(response.body._deleted).not.toBeNull(); + expect(response.body.deletedAt).not.toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('DELETE /toponymes', () => { it('Return 200', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', }); await request(app.getHttpServer()) .delete(`/toponymes/${toponymeId}`) .set('authorization', `Bearer ${token}`) .expect(204); - const voie = await toponymeModel.findOne(toponymeId); + const voie = await toponymeRepository.findOneBy({ id: toponymeId }); expect(voie).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const toponymeId = await createToponyme({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const toponymeId = await createToponyme(balId, { nom: 'rue de la paix', - _bal: balId, - commune: '97800', }); await request(app.getHttpServer()) .delete(`/toponymes/${toponymeId}`) .expect(403); - const toponyme = await toponymeModel.findOne(toponymeId); + const toponyme = await toponymeRepository.findOneBy({ id: toponymeId }); expect(toponyme).not.toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); }); diff --git a/apps/api/test/voie.e2e-spec.ts b/apps/api/test/voie.e2e-spec.ts index a8342be7..041e9303 100644 --- a/apps/api/test/voie.e2e-spec.ts +++ b/apps/api/test/voie.e2e-spec.ts @@ -1,114 +1,148 @@ +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; - -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; +import { ObjectId } from 'mongodb'; +import { v4 as uuid } from 'uuid'; + +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie, TypeNumerotationEnum } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; import { VoieModule } from '@/modules/voie/voie.module'; import { CreateNumeroDTO } from '@/modules/numeros/dto/create_numero.dto'; import { UpdateVoieDTO } from '@/modules/voie/dto/update_voie.dto'; -import { TypeNumerotationEnum } from '@/shared/schemas/voie/type_numerotation.enum'; import { MailerModule } from '@/shared/test/mailer.module.test'; +import { Repository } from 'typeorm'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; describe('VOIE MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; - let toponymeModel: Model; + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [MongooseModule.forRoot(uri), VoieModule, MailerModule], + imports: [ + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + VoieModule, + MailerModule, + ], }).compile(); app = moduleFixture.createNestApplication(); app.useGlobalPipes(new ValidationPipe()); await app.init(); - - // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); - toponymeModel = app.get>(getModelToken(Toponyme.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - const collections = mongoConnection.collections; - for (const key in collections) { - const collection = collections[key]; - await collection.deleteMany({}); - } + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); }); - async function createBal() { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + async function createBal(props: Partial = {}) { + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, + ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, - _created, - _updated, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, - _created, - _updated, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } describe('GET /voies/numeros', () => { it('Return 200 numero without comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); + const numeroId1 = await createNumero(balId, voieId, { numero: 1, comment: 'coucou', }); @@ -117,26 +151,24 @@ describe('VOIE MODULE', () => { .get(`/voies/${voieId}/numeros`) .expect(200); expect(response.body.length).toEqual(1); - expect(response.body[0]._id).toEqual(numeroId1.toString()); + expect(response.body[0].id).toEqual(numeroId1.toString()); expect(response.body[0].comment).toEqual(null); }); it('Return 200 numeronot deleted', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { + nom: 'rue de la paix', + }); + await createNumero(balId, voieId, { numero: 1, comment: 'coucou', }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 2, comment: 'coucou', - _deleted: new Date(), + deletedAt: new Date(), }); const response = await request(app.getHttpServer()) @@ -146,11 +178,11 @@ describe('VOIE MODULE', () => { }); it('Return 200 numero with comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); - const numeroId1 = await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { + nom: 'rue de la paix', + }); + const numeroId1 = await createNumero(balId, voieId, { numero: 1, comment: 'coucou', }); @@ -160,15 +192,15 @@ describe('VOIE MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(200); expect(response.body.length).toEqual(1); - expect(response.body[0]._id).toEqual(numeroId1.toString()); + expect(response.body[0].id).toEqual(numeroId1.toString()); expect(response.body[0].comment).toEqual('coucou'); }); }); describe('POST /voies/numeros', () => { it('Create 201 numero', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); const createdNumero: CreateNumeroDTO = { numero: 1, positions: [ @@ -189,28 +221,32 @@ describe('VOIE MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(201); expect(response.body.numero).toEqual(1); - expect(response.body._bal).toEqual(balId.toString()); - expect(response.body.voie).toEqual(voieId.toString()); + expect(response.body.balId).toEqual(balId); + expect(response.body.voieId).toEqual(voieId.toString()); expect(response.body.parcelles).toEqual([]); expect(response.body.positions).not.toBeNull(); expect(response.body.tiles).not.toBeNull(); expect(response.body.suffixe).toEqual(null); - expect(response.body.toponyme).toEqual(null); + expect(response.body.toponymeId).toEqual(null); expect(response.body.comment).toEqual(null); expect(response.body.certifie).toEqual(false); - expect(response.body._updated).not.toEqual(_updated.toISOString()); - expect(response.body._created).not.toEqual(_created.toISOString()); - expect(response.body._deleted).toEqual(null); - - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated).not.toEqual(_updated.toISOString()); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + expect(response.body.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(response.body.createdAt).not.toEqual(createdAt.toISOString()); + expect(response.body.deletedAt).toEqual(null); + + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); }); it('Create 201 numero with meta', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); const createdNumero: CreateNumeroDTO = { numero: 1, suffixe: 'bis', @@ -236,31 +272,34 @@ describe('VOIE MODULE', () => { .expect(201); expect(response.body.numero).toEqual(1); - expect(response.body._bal).toEqual(balId.toString()); - expect(response.body.voie).toEqual(voieId.toString()); + expect(response.body.balId).toEqual(balId.toString()); + expect(response.body.voieId).toEqual(voieId.toString()); expect(response.body.parcelles).toEqual(['97613000AS0120']); expect(response.body.positions).not.toBeNull(); expect(response.body.tiles).not.toBeNull(); expect(response.body.suffixe).toEqual('bis'); - expect(response.body.toponyme).toEqual(null); + expect(response.body.toponymeId).toEqual(null); expect(response.body.comment).toEqual('coucou'); expect(response.body.certifie).toEqual(true); - expect(response.body._updated).not.toEqual(_updated.toISOString()); - expect(response.body._created).not.toEqual(_created.toISOString()); - expect(response.body._deleted).toEqual(null); - - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated).not.toEqual(_updated.toISOString()); - expect(balAfter._updated).not.toEqual(_updated.toISOString()); + expect(response.body.updatedAt).not.toEqual(updatedAt.toISOString()); + expect(response.body.createdAt).not.toEqual(createdAt.toISOString()); + expect(response.body.deletedAt).toEqual(null); + + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).not.toEqual( + updatedAt.toISOString(), + ); }); it('Create 404 voie is deleted', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: new Date(), + deletedAt: new Date(), }); const createdNumero: CreateNumeroDTO = { numero: 1, @@ -289,21 +328,25 @@ describe('VOIE MODULE', () => { }), ); - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated.toISOString()).toEqual(_updated.toISOString()); - expect(balAfter._updated.toISOString()).toEqual(_updated.toISOString()); + const voieAfter: Voie = await voieRepository.findOne({ + where: { id: voieId }, + withDeleted: true, + }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); it('Create 404 toponyme not exist', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, }); const createdNumero: CreateNumeroDTO = { numero: 1, - toponyme: new Types.ObjectId(), + toponymeId: new ObjectId().toHexString(), positions: [ { type: PositionTypeEnum.ENTREE, @@ -329,17 +372,18 @@ describe('VOIE MODULE', () => { }), ); - const voieAfter: Voie = await voieModel.findOne({ _id: voieId }); - const balAfter: BaseLocale = await balModel.findOne({ _id: balId }); - expect(voieAfter._updated.toISOString()).toEqual(_updated.toISOString()); - expect(balAfter._updated.toISOString()).toEqual(_updated.toISOString()); + const voieAfter: Voie = await voieRepository.findOneBy({ id: voieId }); + const balAfter: BaseLocale = await balRepository.findOneBy({ id: balId }); + expect(voieAfter.updatedAt.toISOString()).toEqual( + updatedAt.toISOString(), + ); + expect(balAfter.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); it('Create 404 bad payload', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, }); const createdNumero: CreateNumeroDTO = { numero: 1, @@ -361,10 +405,9 @@ describe('VOIE MODULE', () => { }); it('Create 404 bad payload', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, }); const createdNumero: CreateNumeroDTO = { positions: [ @@ -397,25 +440,24 @@ describe('VOIE MODULE', () => { describe('PUT /voies/:voieId/convert-to-toponyme', () => { it('Return 200 numero without comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix' }); await request(app.getHttpServer()) .put(`/voies/${voieId}/convert-to-toponyme`) .set('authorization', `Bearer ${token}`) .expect(200); - const toponyme: Toponyme = await toponymeModel.findOne({ + const toponyme: Toponyme = await toponymeRepository.findOneBy({ nom: 'rue de la paix', }); expect(toponyme).toBeDefined(); }); it('Return 200 numero without comment', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de paris', _bal: balId }); - await createNumero({ - _bal: balId, - voie: voieId, + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de paris' }); + await createNumero(balId, voieId, { + numero: 1, }); const response = await request(app.getHttpServer()) @@ -430,7 +472,7 @@ describe('VOIE MODULE', () => { }), ); - const toponyme: Toponyme = await toponymeModel.findOne({ + const toponyme: Toponyme = await toponymeRepository.findOneBy({ nom: 'rue de paris', }); expect(toponyme).toBeNull(); @@ -439,27 +481,31 @@ describe('VOIE MODULE', () => { describe('GET /voies', () => { it('Return 200', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { + nom: 'rue de la paix', + }); const response = await request(app.getHttpServer()) .get(`/voies/${voieId}`) .expect(200); - expect(response.body._id).toEqual(voieId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(voieId); + expect(response.body.balId).toEqual(balId); expect(response.body.nom).toEqual('rue de la paix'); }); it('Return 404', async () => { await request(app.getHttpServer()) - .get(`/voies/${new Types.ObjectId()}`) + .get(`/voies/${new ObjectId()}`) .expect(404); }); }); describe('PUT /voies', () => { it('Return 200', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { + nom: 'rue de la paix', + }); const changes: UpdateVoieDTO = { nom: 'coucou', nomAlt: null, @@ -478,21 +524,23 @@ describe('VOIE MODULE', () => { .send(changes) .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._id).toEqual(voieId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(voieId); + expect(response.body.balId).toEqual(balId); expect(response.body.nom).toEqual('coucou'); expect(response.body.typeNumerotation).toEqual( TypeNumerotationEnum.NUMERIQUE, ); expect(response.body.trace).toEqual(changes.trace); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 200 trace empty', async () => { - const balId = await createBal(); - const voieId = await createVoie({ nom: 'rue de la paix', _bal: balId }); + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { + nom: 'rue de la paix', + }); const changes: UpdateVoieDTO = { nom: 'coucou', nomAlt: null, @@ -505,23 +553,22 @@ describe('VOIE MODULE', () => { .send(changes) .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._id).toEqual(voieId.toString()); - expect(response.body._bal).toEqual(balId.toString()); + expect(response.body.id).toEqual(voieId.toString()); + expect(response.body.balId).toEqual(balId.toString()); expect(response.body.nom).toEqual('coucou'); expect(response.body.typeNumerotation).toEqual( TypeNumerotationEnum.NUMERIQUE, ); expect(response.body.trace).toEqual(null); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, trace: null, }); const changes: UpdateVoieDTO = { @@ -542,65 +589,59 @@ describe('VOIE MODULE', () => { .send(changes) .expect(403); - const voie = await voieModel.findOne(voieId); + const voie = await voieRepository.findOneBy({ id: voieId }); expect(voie.nom).toEqual('rue de la paix'); expect(voie.trace).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('PUT /soft-delete', () => { it('Return 200', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: null, + deletedAt: null, }); - const response = await request(app.getHttpServer()) + await request(app.getHttpServer()) .put(`/voies/${voieId}/soft-delete`) .set('authorization', `Bearer ${token}`) - .expect(200); - - expect(response.body._deleted).not.toBeNull(); + .expect(204); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: null, + deletedAt: null, }); await request(app.getHttpServer()) .put(`/voies/${voieId}/soft-delete`) .expect(403); - const voie = await voieModel.findOne(voieId); - expect(voie._deleted).toBeNull(); + const voie = await voieRepository.findOneBy({ id: voieId }); + expect(voie.deletedAt).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('PUT /restore', () => { it('Return 200', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: _updated, + deletedAt: updatedAt, }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, - _deleted: _updated, + const numeroId = await createNumero(balId, voieId, { + numero: 1, + deletedAt: updatedAt, }); const response = await request(app.getHttpServer()) .put(`/voies/${voieId}/restore`) @@ -608,84 +649,84 @@ describe('VOIE MODULE', () => { .set('authorization', `Bearer ${token}`) .expect(200); - expect(response.body._deleted).toBeNull(); + expect(response.body.deletedAt).toBeNull(); - const numero = await numeroModel.findOne(numeroId); + const numero = await numeroRepository.findOneBy({ id: numeroId }); - expect(numero._deleted).toEqual(null); + expect(numero.deletedAt).toEqual(null); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: _updated, + deletedAt: updatedAt, }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, - _deleted: _updated, + const numeroId = await createNumero(balId, voieId, { + numero: 1, + deletedAt: updatedAt, }); await request(app.getHttpServer()) .put(`/voies/${voieId}/restore`) .send({ numeroIds: [numeroId] }) .expect(403); - const voie = await voieModel.findOne(voieId); - expect(voie._deleted).not.toBeNull(); + const voie = await voieRepository.findOne({ + where: { id: voieId }, + withDeleted: true, + }); + expect(voie.deletedAt).not.toBeNull(); - const numero = await numeroModel.findOne(numeroId); + const numero = await numeroRepository.findOne({ + where: { id: numeroId }, + withDeleted: true, + }); - expect(numero._deleted).not.toBeNull(); + expect(numero.deletedAt).not.toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); describe('DELETE /voies', () => { it('Return 200', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: _updated, + deletedAt: updatedAt, }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, - _deleted: _updated, + const numeroId = await createNumero(balId, voieId, { + numero: 1, + deletedAt: updatedAt, }); await request(app.getHttpServer()) .delete(`/voies/${voieId}`) .set('authorization', `Bearer ${token}`) .expect(204); - const voie = await voieModel.findOne(voieId); + const voie = await voieRepository.findOneBy({ id: voieId }); expect(voie).toBeNull(); - const numero = await numeroModel.findOne(numeroId); + const numero = await numeroRepository.findOneBy({ id: numeroId }); expect(numero).toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).not.toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).not.toEqual(updatedAt.toISOString()); }); it('Return 403', async () => { - const balId = await createBal(); - const voieId = await createVoie({ + const balId = await createBal({ nom: 'bal', commune: '91400' }); + const voieId = await createVoie(balId, { nom: 'rue de la paix', - _bal: balId, - _deleted: _updated, + deletedAt: updatedAt, }); - const numeroId = await createNumero({ - _bal: balId, - voie: voieId, - _deleted: _updated, + const numeroId = await createNumero(balId, voieId, { + numero: 1, + deletedAt: updatedAt, }); await request(app.getHttpServer()) @@ -693,14 +734,20 @@ describe('VOIE MODULE', () => { .send({ numeroIds: [numeroId] }) .expect(403); - const voie = await voieModel.findOne(voieId); + const voie = await voieRepository.findOne({ + where: { id: voieId }, + withDeleted: true, + }); expect(voie).not.toBeNull(); - const numero = await numeroModel.findOne(numeroId); + const numero = await numeroRepository.findOne({ + where: { id: numeroId }, + withDeleted: true, + }); expect(numero).not.toBeNull(); - const bal = await balModel.findOne(balId); - expect(bal._updated.toISOString()).toEqual(_updated.toISOString()); + const bal = await balRepository.findOneBy({ id: balId }); + expect(bal.updatedAt.toISOString()).toEqual(updatedAt.toISOString()); }); }); }); diff --git a/apps/cron/src/cron.module.ts b/apps/cron/src/cron.module.ts index 5b774898..4002fc4d 100644 --- a/apps/cron/src/cron.module.ts +++ b/apps/cron/src/cron.module.ts @@ -1,57 +1,69 @@ import { Module } from '@nestjs/common'; import { ScheduleModule } from '@nestjs/schedule'; -import { MongooseModule } from '@nestjs/mongoose'; import { ConfigModule, ConfigService } from '@nestjs/config'; import { MailerModule } from '@nestjs-modules/mailer'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CacheModule, CacheStore } from '@nestjs/cache-manager'; +import { redisStore } from 'cache-manager-redis-store'; -import { - BaseLocale, - BaseLocaleSchema, -} from '@/shared/schemas/base_locale/base_locale.schema'; +import { Initialization1725371358514 } from 'migrations/1725371358514-initialization'; +import { PublicationModule } from '@/shared/modules/publication/publication.module'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { ApiDepotModule } from '@/shared/modules/api_depot/api_depot.module'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Position } from '@/shared/entities/position.entity'; -import { CronService } from './cron.service'; import { DetectOutdatedTask } from './tasks/detect_outdated.task'; import { DetectConflictTask } from './tasks/detect_conflict.task'; -import { CacheModule } from '@/shared/modules/cache/cache.module'; -import { PublicationModule } from '@/shared/modules/publication/publication.module'; import { SyncOutdatedTask } from './tasks/sync_outdated.task'; -import { Numero, NumeroSchema } from '@/shared/schemas/numero/numero.schema'; -import { - Toponyme, - ToponymeSchema, -} from '@/shared/schemas/toponyme/toponyme.schema'; -import { Voie, VoieSchema } from '@/shared/schemas/voie/voie.schema'; import { MailerParams } from '@/shared/params/mailer.params'; +import { RemoveSoftDeleteBalTask } from './tasks/remove_soft_delete_bal.task'; +import { RemoveDemoBalTask } from './tasks/remove_demo_bal.task'; +import { CronService } from './cron.service'; @Module({ imports: [ ConfigModule.forRoot(), - MongooseModule.forRootAsync({ + TypeOrmModule.forRootAsync({ imports: [ConfigModule], useFactory: async (config: ConfigService) => ({ - uri: config.get('MONGODB_URL'), - dbName: config.get('MONGODB_DBNAME'), + type: 'postgres', + url: config.get('POSTGRES_URL'), + keepConnectionAlive: true, + schema: 'public', + migrationsRun: true, + migrations: [Initialization1725371358514], + entities: [BaseLocale, Voie, Numero, Toponyme, Position], }), inject: [ConfigService], }), - MongooseModule.forFeature([ - { name: BaseLocale.name, schema: BaseLocaleSchema }, - { name: Numero.name, schema: NumeroSchema }, - { name: Toponyme.name, schema: ToponymeSchema }, - { name: Voie.name, schema: VoieSchema }, - ]), + CacheModule.registerAsync({ + imports: [ConfigModule], + useFactory: async (configService: ConfigService) => + configService.get('REDIS_URL') + ? { + store: (await redisStore({ + url: configService.get('REDIS_URL'), + })) as unknown as CacheStore, + } + : {}, + inject: [ConfigService], + }), + TypeOrmModule.forFeature([BaseLocale]), MailerModule.forRootAsync(MailerParams), ScheduleModule.forRoot(), ApiDepotModule, - CacheModule, PublicationModule, ], providers: [ CronService, DetectOutdatedTask, - DetectConflictTask, SyncOutdatedTask, + DetectConflictTask, + RemoveSoftDeleteBalTask, + RemoveDemoBalTask, ], }) export class CronModule {} diff --git a/apps/cron/src/cron.service.ts b/apps/cron/src/cron.service.ts index 7e63432b..08bc15f3 100644 --- a/apps/cron/src/cron.service.ts +++ b/apps/cron/src/cron.service.ts @@ -1,32 +1,23 @@ import { Injectable } from '@nestjs/common'; import { Cron, CronExpression, Interval } from '@nestjs/schedule'; -import { FilterQuery, Model, Types } from 'mongoose'; -import { InjectModel } from '@nestjs/mongoose'; -import { subMonths } from 'date-fns'; - -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { StatusBaseLocalEnum } from '@/shared/schemas/base_locale/status.enum'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; import { DetectOutdatedTask } from './tasks/detect_outdated.task'; import { DetectConflictTask } from './tasks/detect_conflict.task'; import { SyncOutdatedTask } from './tasks/sync_outdated.task'; import { TaskQueue } from './task_queue.class'; +import { RemoveSoftDeleteBalTask } from './tasks/remove_soft_delete_bal.task'; +import { RemoveDemoBalTask } from './tasks/remove_demo_bal.task'; @Injectable() export class CronService { private queue: TaskQueue = new TaskQueue(); constructor( - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, - @InjectModel(Numero.name) private numeroModel: Model, - @InjectModel(Voie.name) private voieModel: Model, - @InjectModel(Toponyme.name) private toponymeModel: Model, private readonly detectOutdatedTask: DetectOutdatedTask, private readonly detectConflictTask: DetectConflictTask, private readonly syncOutdatedTask: SyncOutdatedTask, + private readonly removeSoftDeleteBalTask: RemoveSoftDeleteBalTask, + private readonly removeDemoBalTask: RemoveDemoBalTask, ) {} // Every 30 seconds @@ -53,36 +44,11 @@ export class CronService { @Cron(CronExpression.EVERY_DAY_AT_2AM) async removeSoftDeletedBALsOlderThanOneYear() { - console.debug('Task start : purge old deleted BALs'); - const deleteTime = subMonths(new Date(), 12); - const filter: FilterQuery = { _deleted: { $lt: deleteTime } }; - await this.removeBals(filter); - console.debug('Task end : purge old deleted BALs'); + this.queue.pushTask(this.removeSoftDeleteBalTask); } @Cron(CronExpression.EVERY_DAY_AT_3AM) async removeDemoBALsOlderThanAMonth() { - console.debug('Task start : purge demo BALs'); - const creationTime = subMonths(new Date(), 1); - const filter: FilterQuery = { - status: StatusBaseLocalEnum.DEMO, - _created: { $lt: creationTime }, - }; - await this.removeBals(filter); - console.debug('Task end : purge demo BALs'); - } - - private async removeBals(filter: FilterQuery) { - const balIds: Types.ObjectId[] = await this.baseLocaleModel.distinct( - '_id', - filter, - ); - - for (const balId of balIds) { - await this.toponymeModel.deleteMany({ _bal: balId }); - await this.numeroModel.deleteMany({ _bal: balId }); - await this.voieModel.deleteMany({ _bal: balId }); - await this.baseLocaleModel.deleteOne({ _id: balId }); - } + this.queue.pushTask(this.removeDemoBalTask); } } diff --git a/apps/cron/src/tasks/detect_conflict.task.ts b/apps/cron/src/tasks/detect_conflict.task.ts index a9acc3c8..656d7714 100644 --- a/apps/cron/src/tasks/detect_conflict.task.ts +++ b/apps/cron/src/tasks/detect_conflict.task.ts @@ -1,15 +1,15 @@ -import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; +import { Inject, Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CACHE_MANAGER, Cache } from '@nestjs/cache-manager'; +import { In, Repository } from 'typeorm'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; import { + BaseLocale, StatusSyncEnum, StatusBaseLocalEnum, -} from '@/shared/schemas/base_locale/status.enum'; +} from '@/shared/entities/base_locale.entity'; import { ApiDepotService } from '@/shared/modules/api_depot/api_depot.service'; import { Revision } from '@/shared/modules/api_depot/types/revision.type'; -import { CacheService } from '@/shared/modules/cache/cache.service'; import { Task } from '../task_queue.class'; @@ -22,14 +22,17 @@ export class DetectConflictTask implements Task { constructor( private readonly apiDepotService: ApiDepotService, - private readonly cacheService: CacheService, - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, + @Inject(CACHE_MANAGER) private cacheManager: Cache, + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, ) {} public async run() { const futurePublishedSince = new Date(); - const detectConflictPublishedSince = await this.cacheService.get( - KEY_DETECT_CONFLICT_PUBLISHED_SINCE, + const detectConflictPublishedSince = new Date( + (await this.cacheManager.get( + KEY_DETECT_CONFLICT_PUBLISHED_SINCE, + )) || '1970-01-01', ); const currentRevisions: Revision[] = await this.apiDepotService.getCurrentRevisions( @@ -37,7 +40,7 @@ export class DetectConflictTask implements Task { ); const revisedCommunes = currentRevisions.map((r) => r.codeCommune); - await this.cacheService.set( + await this.cacheManager.set( KEY_DETECT_CONFLICT_PUBLISHED_SINCE, futurePublishedSince, ); @@ -52,11 +55,9 @@ export class DetectConflictTask implements Task { } private async updateConflictStatus(codeCommune: string) { - const basesLocales = await this.baseLocaleModel.find({ + const basesLocales = await this.basesLocalesRepository.findBy({ commune: codeCommune, - status: { - $in: [StatusBaseLocalEnum.REPLACED, StatusBaseLocalEnum.PUBLISHED], - }, + status: In([StatusBaseLocalEnum.REPLACED, StatusBaseLocalEnum.PUBLISHED]), }); if (basesLocales.length === 0) { @@ -74,27 +75,26 @@ export class DetectConflictTask implements Task { } for (const baseLocale of basesLocales) { - if ( - currentRevision._id === - baseLocale.sync.lastUploadedRevisionId.toString() - ) { - await this.baseLocaleModel.updateOne( - { _id: baseLocale._id, status: StatusBaseLocalEnum.REPLACED }, + if (currentRevision._id === baseLocale.sync.lastUploadedRevisionId) { + await this.basesLocalesRepository.update( + { + id: baseLocale.id, + status: StatusBaseLocalEnum.REPLACED, + }, { - $set: { - status: StatusBaseLocalEnum.PUBLISHED, - 'sync.status': StatusSyncEnum.SYNCED, - }, + status: StatusBaseLocalEnum.PUBLISHED, + sync: { status: StatusSyncEnum.SYNCED }, }, ); } else { - await this.baseLocaleModel.updateOne( - { _id: baseLocale._id, status: StatusBaseLocalEnum.PUBLISHED }, + await this.basesLocalesRepository.update( + { + id: baseLocale.id, + status: StatusBaseLocalEnum.PUBLISHED, + }, { - $set: { - status: StatusBaseLocalEnum.REPLACED, - 'sync.status': StatusSyncEnum.CONFLICT, - }, + status: StatusBaseLocalEnum.REPLACED, + sync: { status: StatusSyncEnum.CONFLICT }, }, ); } diff --git a/apps/cron/src/tasks/detect_outdated.task.ts b/apps/cron/src/tasks/detect_outdated.task.ts index d6b04308..07754ff2 100644 --- a/apps/cron/src/tasks/detect_outdated.task.ts +++ b/apps/cron/src/tasks/detect_outdated.task.ts @@ -1,30 +1,42 @@ import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; - -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { StatusSyncEnum } from '@/shared/schemas/base_locale/status.enum'; +import { + BaseLocale, + StatusSyncEnum, +} from '@/shared/entities/base_locale.entity'; import { Task } from '../task_queue.class'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class DetectOutdatedTask implements Task { title: string = 'Detect outdated'; constructor( - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, ) {} public async run() { - await this.baseLocaleModel.updateMany( - { - 'sync.status': StatusSyncEnum.SYNCED, - $expr: { $gt: ['$_updated', '$sync.currentUpdated'] }, - }, - { - $set: { 'sync.status': StatusSyncEnum.OUTDATED }, - $unset: { 'sync.currentUpdated': 1 }, - }, - ); + await this.basesLocalesRepository + .createQueryBuilder('bases_locales') + .update(BaseLocale) + // On set sync.status a 'outdated' et sync.currentUpdated a null + .set({ + sync: () => + `sync || jsonb_build_object( + 'status', '${StatusSyncEnum.OUTDATED}', + 'currentUpdated', null + )`, + }) + // Si sync.status egale SYNCED + .where(`bases_locales.sync->>'status' = :status`, { + status: StatusSyncEnum.SYNCED, + }) + // Et si sync.currentUpdated est inférieur a updatedAt + .andWhere( + `(bases_locales.sync->>'currentUpdated')::timestamp < bases_locales.updated_at`, + ) + .execute(); } } diff --git a/apps/cron/src/tasks/remove_demo_bal.task.ts b/apps/cron/src/tasks/remove_demo_bal.task.ts new file mode 100644 index 00000000..19bee49f --- /dev/null +++ b/apps/cron/src/tasks/remove_demo_bal.task.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { subMonths } from 'date-fns'; + +import { + BaseLocale, + StatusBaseLocalEnum, +} from '@/shared/entities/base_locale.entity'; + +import { Task } from '../task_queue.class'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FindOptionsWhere, LessThan, Repository } from 'typeorm'; + +@Injectable() +export class RemoveDemoBalTask implements Task { + title: string = 'Remove Demo Bal'; + + constructor( + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, + ) {} + + public async run() { + // On créer le where pour selectioné toutes les BALs DEMO qui on plus d'un mois + const deleteTime = subMonths(new Date(), 1); + const where: FindOptionsWhere = { + deletedAt: LessThan(deleteTime), + status: StatusBaseLocalEnum.DEMO, + }; + // On supprime les BAL, et par CASCADE les voie, toponymes, numeros et positions sont supprimé également + await this.basesLocalesRepository.delete(where); + } +} diff --git a/apps/cron/src/tasks/remove_soft_delete_bal.task.ts b/apps/cron/src/tasks/remove_soft_delete_bal.task.ts new file mode 100644 index 00000000..54b604db --- /dev/null +++ b/apps/cron/src/tasks/remove_soft_delete_bal.task.ts @@ -0,0 +1,28 @@ +import { Injectable } from '@nestjs/common'; +import { subMonths } from 'date-fns'; + +import { BaseLocale } from '@/shared/entities/base_locale.entity'; + +import { Task } from '../task_queue.class'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FindOptionsWhere, LessThan, Repository } from 'typeorm'; + +@Injectable() +export class RemoveSoftDeleteBalTask implements Task { + title: string = 'Remove Soft Delete Bal'; + + constructor( + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, + ) {} + + public async run() { + // On créer le where pour selectioné toutes les BALs qui sont supprimer depuis plus d'un an + const deleteTime = subMonths(new Date(), 12); + const where: FindOptionsWhere = { + deletedAt: LessThan(deleteTime), + }; + // On supprime les BAL, et par CASCADE les voie, toponymes, numeros et positions sont supprimé également + await this.basesLocalesRepository.delete(where); + } +} diff --git a/apps/cron/src/tasks/sync_outdated.task.ts b/apps/cron/src/tasks/sync_outdated.task.ts index 1f68c2f7..c0bb2e8b 100644 --- a/apps/cron/src/tasks/sync_outdated.task.ts +++ b/apps/cron/src/tasks/sync_outdated.task.ts @@ -1,10 +1,12 @@ +import { InjectRepository } from '@nestjs/typeorm'; +import { FindOptionsWhere, JsonContains, LessThan, Repository } from 'typeorm'; import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model, Types } from 'mongoose'; import { sub } from 'date-fns'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; -import { StatusSyncEnum } from '@/shared/schemas/base_locale/status.enum'; +import { + BaseLocale, + StatusSyncEnum, +} from '@/shared/entities/base_locale.entity'; import { PublicationService } from '@/shared/modules/publication/publication.service'; import { Task } from '../task_queue.class'; @@ -14,27 +16,28 @@ export class SyncOutdatedTask implements Task { title: string = 'Sync outdated'; constructor( + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, private readonly publicationService: PublicationService, - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, ) {} public async run() { const timeLimit: Date = sub(new Date(), { hours: 2 }); + const where: FindOptionsWhere = { + updatedAt: LessThan(timeLimit), + sync: JsonContains({ + status: StatusSyncEnum.OUTDATED, + isPaused: false, + }), + }; - const idsToSync: Types.ObjectId[] = await this.baseLocaleModel.distinct( - '_id', - { - 'sync.status': StatusSyncEnum.OUTDATED, - 'sync.isPaused': { $ne: true }, - _updated: { $lt: timeLimit }, - }, - ); + const bals: BaseLocale[] = await this.basesLocalesRepository.findBy(where); - for (const balId of idsToSync) { + for (const bal of bals) { try { - await this.publicationService.exec(balId); + await this.publicationService.exec(bal.id); } catch (error) { - console.error(`Unable to sync ${balId}`, error); + console.error(`Unable to sync ${bal.id}`, error); } } } diff --git a/apps/cron/test/jest-e2e.json b/apps/cron/test/jest-e2e.json index 998162ab..c5c3a871 100644 --- a/apps/cron/test/jest-e2e.json +++ b/apps/cron/test/jest-e2e.json @@ -1,7 +1,12 @@ { - "moduleFileExtensions": ["js", "json", "ts"], + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], "rootDir": ".", "testEnvironment": "node", + "testTimeout": 6000000, "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" diff --git a/apps/cron/test/task.e2e-spec.ts b/apps/cron/test/task.e2e-spec.ts index e676c48d..e4a700a7 100644 --- a/apps/cron/test/task.e2e-spec.ts +++ b/apps/cron/test/task.e2e-spec.ts @@ -1,39 +1,35 @@ import { Test, TestingModule } from '@nestjs/testing'; +import { + PostgreSqlContainer, + StartedPostgreSqlContainer, +} from '@testcontainers/postgresql'; +import { Client } from 'pg'; import { Global, INestApplication, Module, ValidationPipe, } from '@nestjs/common'; -import { MongoMemoryServer } from 'mongodb-memory-server'; -import { MongooseModule, getModelToken } from '@nestjs/mongoose'; -import { Connection, connect, Model, Types } from 'mongoose'; +import { ObjectId } from 'mongodb'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { v4 as uuid } from 'uuid'; -import { Numero, NumeroSchema } from '@/shared/schemas/numero/numero.schema'; -import { Voie, VoieSchema } from '@/shared/schemas/voie/voie.schema'; -import { - Toponyme, - ToponymeSchema, -} from '@/shared/schemas/toponyme/toponyme.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; import { BaseLocale, - BaseLocaleSchema, -} from '@/shared/schemas/base_locale/base_locale.schema'; -import { PositionTypeEnum } from '@/shared/schemas/position_type.enum'; -import { Position } from '@/shared/schemas/position.schema'; - -import { DetectOutdatedTask } from '../src/tasks/detect_outdated.task'; -import { StatusBaseLocalEnum, StatusSyncEnum, -} from '@/shared/schemas/base_locale/status.enum'; +} from '@/shared/entities/base_locale.entity'; +import { Position, PositionTypeEnum } from '@/shared/entities/position.entity'; + +import { DetectOutdatedTask } from '../src/tasks/detect_outdated.task'; import { DetectConflictTask, KEY_DETECT_CONFLICT_PUBLISHED_SINCE, } from '../src/tasks/detect_conflict.task'; -import { CacheService } from '@/shared/modules/cache/cache.service'; import { Revision, StatusRevision, @@ -44,12 +40,12 @@ import { StatusHabiliation, } from '@/shared/modules/api_depot/types/habilitation.type'; import { SyncOutdatedTask } from '../src/tasks/sync_outdated.task'; -import { ScheduleModule } from '@nestjs/schedule'; import { ApiDepotModule } from '@/shared/modules/api_depot/api_depot.module'; -import { CacheModule } from '@/shared/modules/cache/cache.module'; import { PublicationModule } from '@/shared/modules/publication/publication.module'; -import { CronService } from '../src/cron.service'; import { MailerService } from '@nestjs-modules/mailer'; +import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm'; +import { Point, Repository } from 'typeorm'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; @Global() @Module({ @@ -67,50 +63,71 @@ class MailerModule {} describe('TASK MODULE', () => { let app: INestApplication; - let mongod: MongoMemoryServer; - let mongoConnection: Connection; - let numeroModel: Model; - let voieModel: Model; - let balModel: Model; - let toponymeModel: Model; - // TASK + // DB + let postgresContainer: StartedPostgreSqlContainer; + let postgresClient: Client; + let numeroRepository: Repository; + let voieRepository: Repository; + let balRepository: Repository; + let toponymeRepository: Repository; + // SERVICE let detectOutdated: DetectOutdatedTask; let detectConflict: DetectConflictTask; - let cacheService: CacheService; let syncOutdatedTask: SyncOutdatedTask; // VAR const token = 'xxxx'; - const _created = new Date('2000-01-01'); - const _updated = new Date('2000-01-02'); + const createdAt = new Date('2000-01-01'); + const updatedAt = new Date('2000-01-02'); + // CACHE + const cache = {}; + const setCache = (key: string, value: any) => (cache[key] = value); + const getCache = (key: string) => cache[key]; + // AXIOS const axiosMock = new MockAdapter(axios); beforeAll(async () => { // INIT DB - mongod = await MongoMemoryServer.create(); - const uri = mongod.getUri(); - mongoConnection = (await connect(uri)).connection; - + postgresContainer = await new PostgreSqlContainer( + 'postgis/postgis:12-3.0', + ).start(); + postgresClient = new Client({ + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + database: postgresContainer.getDatabase(), + user: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + }); + await postgresClient.connect(); + // INIT MODULE const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [ - MongooseModule.forRoot(uri), - MongooseModule.forFeature([ - { name: BaseLocale.name, schema: BaseLocaleSchema }, - { name: Numero.name, schema: NumeroSchema }, - { name: Toponyme.name, schema: ToponymeSchema }, - { name: Voie.name, schema: VoieSchema }, - ]), - ScheduleModule.forRoot(), + TypeOrmModule.forRoot({ + type: 'postgres', + host: postgresContainer.getHost(), + port: postgresContainer.getPort(), + username: postgresContainer.getUsername(), + password: postgresContainer.getPassword(), + database: postgresContainer.getDatabase(), + synchronize: true, + entities: [BaseLocale, Voie, Numero, Toponyme, Position], + }), + TypeOrmModule.forFeature([BaseLocale]), ApiDepotModule, - CacheModule, PublicationModule, MailerModule, ], providers: [ - CronService, DetectOutdatedTask, DetectConflictTask, SyncOutdatedTask, + { + provide: CACHE_MANAGER, + useValue: { + get: getCache, + set: setCache, + }, + }, ], }).compile(); @@ -118,88 +135,97 @@ describe('TASK MODULE', () => { app.useGlobalPipes(new ValidationPipe()); await app.init(); - // INIT MODEL - numeroModel = app.get>(getModelToken(Numero.name)); - voieModel = app.get>(getModelToken(Voie.name)); - balModel = app.get>(getModelToken(BaseLocale.name)); - toponymeModel = app.get>(getModelToken(Toponyme.name)); + // INIT REPOSITORY + numeroRepository = app.get(getRepositoryToken(Numero)); + voieRepository = app.get(getRepositoryToken(Voie)); + balRepository = app.get(getRepositoryToken(BaseLocale)); + toponymeRepository = app.get(getRepositoryToken(Toponyme)); // INIT TASK detectOutdated = app.get(DetectOutdatedTask); detectConflict = app.get(DetectConflictTask); - cacheService = app.get(CacheService); syncOutdatedTask = app.get(SyncOutdatedTask); }); afterAll(async () => { - await mongoConnection.dropDatabase(); - await mongoConnection.close(); - await mongod.stop(); + await postgresClient.end(); + await postgresContainer.stop(); await app.close(); }); afterEach(async () => { - await toponymeModel.deleteMany({}); - await voieModel.deleteMany({}); - await balModel.deleteMany({}); - await numeroModel.deleteMany({}); + await numeroRepository.delete({}); + await voieRepository.delete({}); + await balRepository.delete({}); + await toponymeRepository.delete({}); axiosMock.reset(); }); async function createBal(props: Partial = {}) { - const balId = new Types.ObjectId(); - const bal: Partial = { - _id: balId, - _created, - _updated, + const payload: Partial = { + banId: uuid(), + createdAt, + updatedAt, + status: props.status ?? StatusBaseLocalEnum.DRAFT, token, ...props, }; - await balModel.create(bal); - return balId; + const entityToInsert = balRepository.create(payload); + const result = await balRepository.save(entityToInsert); + return result.id; } - async function createVoie(props: Partial = {}) { - const voieId = new Types.ObjectId(); - const voie: Partial = { - _id: voieId, - _created, - _updated, + async function createVoie(balId: string, props: Partial = {}) { + const payload: Partial = { + balId, + banId: uuid(), + createdAt, + updatedAt, ...props, }; - await voieModel.create(voie); - return voieId; + const entityToInsert = voieRepository.create(payload); + const result = await voieRepository.save(entityToInsert); + return result.id; } - async function createNumero(props: Partial = {}) { - const numeroId = new Types.ObjectId(); - const numero: Partial = { - _id: numeroId, - _created, - _updated, + async function createNumero( + balId: string, + voieId: string, + props: Partial = {}, + ) { + const payload: Partial = { + balId, + banId: uuid(), + voieId, + createdAt, + updatedAt, ...props, }; - await numeroModel.create(numero); - return numeroId; + const entityToInsert = numeroRepository.create(payload); + const result = await numeroRepository.save(entityToInsert); + return result.id; } - function createPositions(coordinates: number[] = [8, 42]): Position[] { - return [ - { - type: PositionTypeEnum.INCONNUE, - source: 'ban', - point: { - type: 'Point', - coordinates, - }, - }, - ]; + function createPositions(coordinates: number[] = [8, 42]): Position { + const id = new ObjectId().toHexString(); + const point: Point = { + type: 'Point', + coordinates, + }; + return { + id, + type: PositionTypeEnum.INCONNUE, + source: 'ban', + point, + } as Position; } it('detectOutdated', async () => { const balId = await createBal({ + nom: 'bal', + commune: '91534', sync: { status: StatusSyncEnum.SYNCED, - lastUploadedRevisionId: new Types.ObjectId(), + lastUploadedRevisionId: new ObjectId().toHexString(), currentUpdated: new Date('2000-01-01'), }, status: StatusBaseLocalEnum.PUBLISHED, @@ -207,20 +233,20 @@ describe('TASK MODULE', () => { await detectOutdated.run(); - const resultBal = await balModel.findOne(balId); + const resultBal = await balRepository.findOneBy({ id: balId }); expect(resultBal.sync.status).toEqual(StatusSyncEnum.OUTDATED); - expect(resultBal.sync.currentUpdated).not.toBeDefined(); + expect(resultBal.sync.currentUpdated).toBe(null); }); it('detectConflict', async () => { const commune = '97354'; const date = new Date('2000-01-01'); - await cacheService.set(KEY_DETECT_CONFLICT_PUBLISHED_SINCE, date); + await setCache(KEY_DETECT_CONFLICT_PUBLISHED_SINCE, date); - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { - _id: revisionId.toHexString(), + _id: revisionId, codeCommune: commune, ready: true, current: true, @@ -237,33 +263,33 @@ describe('TASK MODULE', () => { .reply(200, revision); const balId1 = await createBal({ + nom: 'bal', sync: { status: StatusSyncEnum.SYNCED, lastUploadedRevisionId: revisionId, }, - commune: commune, + commune, status: StatusBaseLocalEnum.PUBLISHED, }); const balId2 = await createBal({ + nom: 'bal', + commune, sync: { status: StatusSyncEnum.SYNCED, - lastUploadedRevisionId: new Types.ObjectId(), + lastUploadedRevisionId: new ObjectId().toHexString(), }, - commune: commune, status: StatusBaseLocalEnum.PUBLISHED, }); await detectConflict.run(); - const resultDate = await cacheService.get( - KEY_DETECT_CONFLICT_PUBLISHED_SINCE, - ); + const resultDate = await getCache(KEY_DETECT_CONFLICT_PUBLISHED_SINCE); expect(date.toISOString()).not.toEqual(resultDate.toISOString()); - const bal1After = await balModel.findOne(balId1); - const bal2After = await balModel.findOne(balId2); + const bal1After = await balRepository.findOneBy({ id: balId1 }); + const bal2After = await balRepository.findOneBy({ id: balId2 }); expect(bal1After.status).toEqual(StatusBaseLocalEnum.PUBLISHED); expect(bal1After.sync.status).toEqual(StatusSyncEnum.SYNCED); @@ -274,11 +300,11 @@ describe('TASK MODULE', () => { it('syncOutdated', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PENDING, ready: false, @@ -295,32 +321,34 @@ describe('TASK MODULE', () => { }, ], }; - + const date = sub(new Date(), { hours: 3 }); // BAL const balId = await createBal({ + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', + nom: 'bal', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { status: StatusSyncEnum.OUTDATED, lastUploadedRevisionId: revisionId, + isPaused: false, + currentUpdated: null, }, + updatedAt: date, }); - const voieId = await createVoie({ + const voieId = await createVoie(balId, { nom: 'rue de la paix', - commune, - _bal: balId, + banId: '26734c2d-2a14-4eeb-ac5b-1be055c0a5ae', }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 1, + banId: '2da3bb47-1a10-495a-8c29-6b8d0e79f9af', suffixe: 'bis', - positions: createPositions(), + positions: [createPositions()], certifie: true, - commune, - _updated: new Date('2000-01-01'), + updatedAt: new Date('2000-01-01'), }); // MOCK AXIOS @@ -342,14 +370,14 @@ describe('TASK MODULE', () => { axiosMock.onPost(`/revisions/${revisionId}/compute`).reply(200, revision); const csvFile = `cle_interop;id_ban_commune;id_ban_toponyme;id_ban_adresse;voie_nom;lieudit_complement_nom;numero;suffixe;certification_commune;commune_insee;commune_nom;position;long;lat;x;y;cad_parcelles;source;date_der_maj - 91534_xxxx_00001_bis;;;;rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; + 91534_xxxx_00001_bis;52c4de09-6b82-45eb-8ed7-b212607282f7;26734c2d-2a14-4eeb-ac5b-1be055c0a5ae;2da3bb47-1a10-495a-8c29-6b8d0e79f9af;rue de la paix;;1;bis;1;91534;Saclay;inconnue;8;42;1114835.92;6113076.85;;ban;2000-01-01`; axiosMock.onPut(`/revisions/${revisionId}/files/bal`).reply(({ data }) => { expect(data.replace(/\s/g, '')).toEqual(csvFile.replace(/\s/g, '')); return [200, null]; }); const publishedRevision: Revision = { - _id: revisionId.toString(), + _id: revisionId, codeCommune: commune, status: StatusRevision.PUBLISHED, ready: true, @@ -369,24 +397,20 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const balResult = await balModel.findOne(balId); + const balResult = await balRepository.findOneBy({ id: balId }); expect(balResult.status).toEqual(StatusBaseLocalEnum.PUBLISHED); - expect(balResult.sync.currentUpdated.toISOString()).toEqual( - _updated.toISOString(), - ); + expect(balResult.sync.currentUpdated).toBeDefined(); expect(balResult.sync.status).toEqual(StatusSyncEnum.SYNCED); expect(balResult.sync.isPaused).toEqual(false); - expect(balResult.sync.lastUploadedRevisionId.toString()).toEqual( - revisionId.toString(), - ); + expect(balResult.sync.lastUploadedRevisionId).toEqual(revisionId); }); it('syncOutdated same hash', async () => { const commune = '91534'; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { _id: revisionId.toString(), codeCommune: commune, @@ -401,36 +425,36 @@ describe('TASK MODULE', () => { files: [ { type: 'bal', - hash: '8d0cda05e7b8b58a92a18cd40d0549c1d3f8ac1ac9586243aa0e3f885bb870c4', + hash: 'a62492c9dbd6c74e7cfb2b67b3a9e49be89da7b8fa4dff3c061b0f82805b65c9', }, ], }; // BAL const balId = await createBal({ + nom: 'bal', + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { status: StatusSyncEnum.OUTDATED, lastUploadedRevisionId: revisionId, + isPaused: false, + currentUpdated: null, }, }); - const voieId = await createVoie({ + const voieId = await createVoie(balId, { nom: 'rue de la paix', - commune, - _bal: balId, + banId: '26734c2d-2a14-4eeb-ac5b-1be055c0a5ae', }); - await createNumero({ - _bal: balId, - voie: voieId, + await createNumero(balId, voieId, { numero: 1, + banId: '2da3bb47-1a10-495a-8c29-6b8d0e79f9af', suffixe: 'bis', - positions: createPositions(), + positions: [createPositions()], certifie: true, - commune, - _updated: new Date('2000-01-01'), }); // MOCK AXIOS @@ -449,23 +473,18 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const balResult = await balModel.findOne(balId); - + const balResult = await balRepository.findOneBy({ id: balId }); expect(balResult.status).toEqual(StatusBaseLocalEnum.PUBLISHED); - expect(balResult.sync.currentUpdated.toISOString()).toEqual( - _updated.toISOString(), - ); + expect(balResult.sync.currentUpdated).toBeDefined(); expect(balResult.sync.status).toEqual(StatusSyncEnum.SYNCED); expect(balResult.sync.isPaused).toEqual(false); - expect(balResult.sync.lastUploadedRevisionId.toString()).toEqual( - revisionId.toString(), - ); + expect(balResult.sync.lastUploadedRevisionId).toEqual(revisionId); }); it('syncOutdated 412 no habilitation', async () => { const commune = '91534'; // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { _id: revisionId.toString(), codeCommune: commune, @@ -487,6 +506,8 @@ describe('TASK MODULE', () => { // BAL const balId = await createBal({ + nom: 'bal', + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', commune, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], @@ -503,7 +524,7 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const resultBal = await balModel.findOne(balId); + const resultBal = await balRepository.findOneBy({ id: balId }); expect(resultBal.sync.status).toEqual(StatusSyncEnum.OUTDATED); expect(resultBal.sync.lastUploadedRevisionId).toEqual(revisionId); }); @@ -511,7 +532,7 @@ describe('TASK MODULE', () => { it('syncOutdated 412 habilitation PENDING', async () => { const commune = '91534'; // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { _id: revisionId.toString(), codeCommune: commune, @@ -531,11 +552,13 @@ describe('TASK MODULE', () => { ], }; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // BAL const balId = await createBal({ + nom: 'bal', + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { @@ -560,7 +583,7 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const resultBal = await balModel.findOne(balId); + const resultBal = await balRepository.findOneBy({ id: balId }); expect(resultBal.sync.status).toEqual(StatusSyncEnum.OUTDATED); expect(resultBal.sync.lastUploadedRevisionId).toEqual(revisionId); }); @@ -568,7 +591,7 @@ describe('TASK MODULE', () => { it('syncOutdated 412 habilitation expired', async () => { const commune = '91534'; // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { _id: revisionId.toString(), codeCommune: commune, @@ -588,11 +611,13 @@ describe('TASK MODULE', () => { ], }; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // BAL const balId = await createBal({ + nom: 'bal', + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { @@ -617,7 +642,7 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const resultBal = await balModel.findOne(balId); + const resultBal = await balRepository.findOneBy({ id: balId }); expect(resultBal.sync.status).toEqual(StatusSyncEnum.OUTDATED); expect(resultBal.sync.lastUploadedRevisionId).toEqual(revisionId); }); @@ -625,7 +650,7 @@ describe('TASK MODULE', () => { it('syncOutdated 412 no numero', async () => { const commune = '91534'; // REVSION - const revisionId = new Types.ObjectId(); + const revisionId = new ObjectId().toHexString(); const revision: Revision = { _id: revisionId.toString(), codeCommune: commune, @@ -645,11 +670,13 @@ describe('TASK MODULE', () => { ], }; - const habilitationId = new Types.ObjectId(); + const habilitationId = new ObjectId().toHexString(); // BAL const balId = await createBal({ + nom: 'bal', + banId: '52c4de09-6b82-45eb-8ed7-b212607282f7', commune, - _habilitation: habilitationId.toString(), + habilitationId, status: StatusBaseLocalEnum.PUBLISHED, emails: ['test@test.fr'], sync: { @@ -674,7 +701,7 @@ describe('TASK MODULE', () => { await syncOutdatedTask.run(); - const resultBal = await balModel.findOne(balId); + const resultBal = await balRepository.findOneBy({ id: balId }); expect(resultBal.sync.status).toEqual(StatusSyncEnum.OUTDATED); expect(resultBal.sync.lastUploadedRevisionId).toEqual(revisionId); }); diff --git a/libs/shared/src/entities/base_locale.entity.ts b/libs/shared/src/entities/base_locale.entity.ts new file mode 100644 index 00000000..dbfeed2f --- /dev/null +++ b/libs/shared/src/entities/base_locale.entity.ts @@ -0,0 +1,76 @@ +import { GlobalEntity } from './global.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Column, Entity, OneToMany } from 'typeorm'; +import { Voie } from './voie.entity'; +import { Numero } from './numero.entity'; +import { Toponyme } from './toponyme.entity'; + +export enum StatusBaseLocalEnum { + DRAFT = 'draft', + PUBLISHED = 'published', + DEMO = 'demo', + REPLACED = 'replaced', +} + +export enum StatusSyncEnum { + OUTDATED = 'outdated', + SYNCED = 'synced', + CONFLICT = 'conflict', +} + +export class BaseLocaleSync { + @ApiProperty({ enum: StatusSyncEnum }) + status: StatusSyncEnum; + + @ApiProperty() + isPaused?: boolean; + + @ApiProperty() + lastUploadedRevisionId: string; + + @ApiProperty() + currentUpdated?: Date; +} + +@Entity({ name: 'bases_locales' }) +export class BaseLocale extends GlobalEntity { + @ApiProperty() + @Column('text', { nullable: false }) + nom: string; + + @ApiProperty() + @Column('varchar', { nullable: false, length: 5 }) + commune: string; + + @ApiProperty() + @Column('text', { nullable: true, array: true }) + emails: string[]; + + @ApiProperty() + @Column('varchar', { nullable: false, length: 20 }) + token: string; + + @ApiProperty({ enum: StatusBaseLocalEnum }) + @Column('enum', { enum: StatusBaseLocalEnum, nullable: false }) + status: StatusBaseLocalEnum; + + @ApiProperty() + @Column('varchar', { name: 'habilitation_id', nullable: true, length: 24 }) + habilitationId: string | null; + + @ApiProperty({ type: () => BaseLocaleSync }) + @Column('jsonb', { nullable: true }) + sync: BaseLocaleSync | null; + + @ApiProperty({ type: () => Voie, isArray: true }) + @OneToMany(() => Voie, (voie) => voie.baseLocale) + voies?: Voie[]; + + @ApiProperty({ type: () => Toponyme, isArray: true }) + @OneToMany(() => Toponyme, (toponyme) => toponyme.baseLocale) + toponymes?: Toponyme[]; + + @ApiProperty({ type: () => Numero, isArray: true }) + @OneToMany(() => Numero, (numero) => numero.baseLocale) + numeros?: Numero[]; +} diff --git a/libs/shared/src/entities/global.entity.ts b/libs/shared/src/entities/global.entity.ts new file mode 100644 index 00000000..a4223ba7 --- /dev/null +++ b/libs/shared/src/entities/global.entity.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { ObjectId } from 'mongodb'; +import { + BeforeInsert, + Column, + CreateDateColumn, + DeleteDateColumn, + Index, + PrimaryColumn, + UpdateDateColumn, +} from 'typeorm'; + +export class GlobalEntity { + @ApiProperty() + @PrimaryColumn('varchar', { length: 24 }) + id: string; + + @ApiProperty() + @Column('uuid', { name: 'ban_id', nullable: false }) + banId: string; + + @ApiProperty() + @CreateDateColumn({ name: 'created_at' }) + createdAt: Date; + + @ApiProperty() + @UpdateDateColumn({ name: 'updated_at' }) + updatedAt: Date; + + @Index({ where: 'deleted_at IS NULL' }) + @ApiProperty() + @DeleteDateColumn({ name: 'deleted_at' }) + deletedAt: Date | null; + + @BeforeInsert() + generatedObjectId?() { + this.id = new ObjectId().toHexString(); + } +} diff --git a/libs/shared/src/entities/numero.entity.ts b/libs/shared/src/entities/numero.entity.ts new file mode 100644 index 00000000..51d9c542 --- /dev/null +++ b/libs/shared/src/entities/numero.entity.ts @@ -0,0 +1,112 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + AfterLoad, + BeforeInsert, + BeforeUpdate, + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + OneToMany, +} from 'typeorm'; +import { GlobalEntity } from './global.entity'; +import { BaseLocale } from './base_locale.entity'; +import { Voie } from './voie.entity'; +import { Toponyme } from './toponyme.entity'; +import { Position } from './position.entity'; + +export function displaySuffix(numero: Numero): string { + if (numero.suffixe) { + if (numero.suffixe.trim().match(/^\d/)) { + return '-' + numero.suffixe.trim(); + } + return numero.suffixe.trim(); + } + return ''; +} + +@Entity({ name: 'numeros' }) +export class Numero extends GlobalEntity { + @Index('IDX_numeros_bal_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'bal_id', nullable: false }) + balId: string; + + @Index('IDX_numeros_voie_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'voie_id', nullable: false }) + voieId: string; + + @Index('IDX_numeros_toponyme_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'toponyme_id', nullable: true }) + toponymeId: string; + + @ApiProperty() + @Column('int', { nullable: false }) + numero: number; + + @ApiProperty() + @Column('text', { nullable: true }) + suffixe?: string | null; + + @ApiProperty() + numeroComplet: string; + + @ApiProperty() + @Column('text', { nullable: true }) + comment?: string | null; + + @ApiProperty() + @Column('text', { nullable: true, array: true }) + parcelles?: string[] | null; + + @ApiProperty() + @Column('boolean', { nullable: false, default: false }) + certifie?: boolean; + + @ApiProperty({ type: () => Position, isArray: true }) + @OneToMany(() => Position, (position) => position.numero, { + eager: true, + cascade: true, + }) + positions?: Position[]; + + @BeforeInsert() + @BeforeUpdate() + setRankPosition?() { + for (let i = 0; i < this?.positions?.length; i++) { + this.positions[i].rank = i; + } + } + + @AfterLoad() + sortPositions?() { + if (this?.positions?.length) { + this.positions.sort((a, b) => a.rank - b.rank); + } + } + + @ApiProperty({ type: () => BaseLocale }) + @ManyToOne(() => BaseLocale, (baseLocale) => baseLocale.numeros) + @JoinColumn({ name: 'bal_id' }) + baseLocale?: BaseLocale; + + @ApiProperty({ type: () => Voie }) + @ManyToOne(() => Voie, (voie) => voie.numeros, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'voie_id' }) + voie?: Voie; + + @ApiProperty({ type: () => Toponyme }) + @ManyToOne(() => Toponyme, (toponyme) => toponyme.numeros) + @JoinColumn({ name: 'toponyme_id' }) + toponyme?: Toponyme; + + @AfterLoad() + getNumeroComplet?() { + this.numeroComplet = this.numero + ' ' + displaySuffix(this); + } +} diff --git a/libs/shared/src/entities/position.entity.ts b/libs/shared/src/entities/position.entity.ts new file mode 100644 index 00000000..e85693f6 --- /dev/null +++ b/libs/shared/src/entities/position.entity.ts @@ -0,0 +1,87 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + BeforeInsert, + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + Point, + PrimaryColumn, +} from 'typeorm'; +import { Numero } from './numero.entity'; +import { Toponyme } from './toponyme.entity'; +import { ObjectId } from 'mongodb'; + +export enum PositionTypeEnum { + ENTREE = 'entrée', + BATIMENT = 'bâtiment', + CAGE_ESCALIER = 'cage d’escalier', + LOGEMENT = 'logement', + SERVICE_TECHNIQUE = 'service technique', + DELIVRANCE_POSTALE = 'délivrance postale', + PARCELLE = 'parcelle', + SEGMENT = 'segment', + INCONNUE = 'inconnue', +} + +@Entity({ name: 'positions' }) +export class Position { + @ApiProperty() + @PrimaryColumn('varchar', { length: 24 }) + id?: string; + + @BeforeInsert() + generatedObjectId? = function () { + this.id = new ObjectId().toHexString(); + }; + + @Index('IDX_positions_toponyme_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'toponyme_id', nullable: true }) + toponymeId?: string; + + @Index('IDX_positions_numeros_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'numero_id', nullable: true }) + numeroId?: string; + + @ApiProperty({ enum: PositionTypeEnum }) + @Column('enum', { + enum: PositionTypeEnum, + default: PositionTypeEnum.ENTREE, + nullable: false, + }) + type: PositionTypeEnum; + + @ApiProperty() + @Column('text', { nullable: true }) + source?: string; + + @ApiProperty() + @Column('int', { nullable: false }) + rank?: number; + + @Index('IDX_positions_point', { spatial: true }) + @ApiProperty() + @Column('geometry', { + nullable: false, + spatialFeatureType: 'Point', + srid: 4326, + }) + point: Point; + + @ApiProperty({ type: () => Toponyme }) + @ManyToOne(() => Toponyme, (toponyme) => toponyme.positions, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'toponyme_id' }) + toponyme?: Toponyme; + + @ApiProperty({ type: () => Numero }) + @ManyToOne(() => Numero, (numero) => numero.positions, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'numero_id' }) + numero?: Numero; +} diff --git a/libs/shared/src/entities/toponyme.entity.ts b/libs/shared/src/entities/toponyme.entity.ts new file mode 100644 index 00000000..156d1f32 --- /dev/null +++ b/libs/shared/src/entities/toponyme.entity.ts @@ -0,0 +1,69 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { + AfterLoad, + BeforeInsert, + BeforeUpdate, + Column, + Entity, + Index, + JoinColumn, + ManyToOne, + OneToMany, +} from 'typeorm'; +import { GlobalEntity } from './global.entity'; +import { BaseLocale } from './base_locale.entity'; +import { Numero } from './numero.entity'; +import { Position } from './position.entity'; + +@Entity({ name: 'toponymes' }) +export class Toponyme extends GlobalEntity { + @Index('IDX_toponymes_bal_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'bal_id', nullable: false }) + balId: string; + + @ApiProperty() + @Column('text', { nullable: false }) + nom: string; + + @ApiProperty() + @Column('json', { name: 'nom_alt', nullable: true }) + nomAlt: Record | null; + + @ApiProperty() + @Column('text', { nullable: true, array: true }) + parcelles?: string[] | null; + + @ApiProperty({ type: () => Position, isArray: true }) + @OneToMany(() => Position, (position) => position.toponyme, { + eager: true, + cascade: true, + }) + positions?: Position[]; + + @BeforeInsert() + @BeforeUpdate() + setRankPosition?() { + for (let i = 0; i < this?.positions?.length; i++) { + this.positions[i].rank = i; + } + } + + @AfterLoad() + sortPositions?() { + if (this?.positions?.length) { + this.positions.sort((a, b) => a.rank - b.rank); + } + } + + @ApiProperty({ type: () => BaseLocale }) + @ManyToOne(() => BaseLocale, (baseLocale) => baseLocale.toponymes, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'bal_id' }) + baseLocale?: BaseLocale; + + @ApiProperty({ type: () => Numero, isArray: true }) + @OneToMany(() => Numero, (numero) => numero.toponyme) + numeros?: Numero[]; +} diff --git a/libs/shared/src/entities/voie.entity.ts b/libs/shared/src/entities/voie.entity.ts new file mode 100644 index 00000000..db9e130c --- /dev/null +++ b/libs/shared/src/entities/voie.entity.ts @@ -0,0 +1,70 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { GlobalEntity } from './global.entity'; +import { + Column, + Entity, + Index, + JoinColumn, + LineString, + ManyToOne, + OneToMany, + Point, +} from 'typeorm'; +import { BaseLocale } from './base_locale.entity'; +import { Numero } from './numero.entity'; + +export enum TypeNumerotationEnum { + NUMERIQUE = 'numerique', + METRIQUE = 'metrique', +} + +@Entity({ name: 'voies' }) +export class Voie extends GlobalEntity { + @Index('IDX_voies_bal_id') + @ApiProperty() + @Column('varchar', { length: 24, name: 'bal_id', nullable: false }) + balId: string; + + @ApiProperty() + @Column('text', { nullable: false }) + nom: string; + + @ApiProperty() + @Column('json', { name: 'nom_alt', nullable: true }) + nomAlt: Record | null; + + @ApiProperty({ enum: TypeNumerotationEnum }) + @Column('enum', { + name: 'type_numerotation', + enum: TypeNumerotationEnum, + default: TypeNumerotationEnum.NUMERIQUE, + }) + typeNumerotation: TypeNumerotationEnum; + + @ApiProperty() + @Column('geometry', { + nullable: true, + spatialFeatureType: 'Point', + srid: 4326, + }) + centroid: Point | null; + + @ApiProperty() + @Column('geometry', { + nullable: true, + spatialFeatureType: 'LineString', + srid: 4326, + }) + trace: LineString | null; + + @ApiProperty({ type: () => BaseLocale }) + @ManyToOne(() => BaseLocale, (baseLocale) => baseLocale.voies, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'bal_id' }) + baseLocale?: BaseLocale; + + @ApiProperty({ type: () => Numero, isArray: true }) + @OneToMany(() => Numero, (numero) => numero.voie) + numeros?: Numero[]; +} diff --git a/libs/shared/src/modules/api_depot/api_depot.service.ts b/libs/shared/src/modules/api_depot/api_depot.service.ts index a10740ad..fade5e4a 100644 --- a/libs/shared/src/modules/api_depot/api_depot.service.ts +++ b/libs/shared/src/modules/api_depot/api_depot.service.ts @@ -5,7 +5,7 @@ import { of, catchError, firstValueFrom } from 'rxjs'; import * as hasha from 'hasha'; import { Habilitation } from './types/habilitation.type'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { Revision } from '@/shared/modules/api_depot/types/revision.type'; import { ConfigService } from '@nestjs/config'; import { ObjectId } from 'mongodb'; diff --git a/libs/shared/src/modules/cache/cache.module.ts b/libs/shared/src/modules/cache/cache.module.ts deleted file mode 100644 index d5b8d913..00000000 --- a/libs/shared/src/modules/cache/cache.module.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Module } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; - -import { Cache, CacheSchema } from '@/shared/modules/cache/cache.schema'; -import { CacheService } from './cache.service'; - -@Module({ - imports: [ - MongooseModule.forFeature([{ name: Cache.name, schema: CacheSchema }]), - ], - providers: [CacheService], - exports: [CacheService], -}) -export class CacheModule {} diff --git a/libs/shared/src/modules/cache/cache.schema.ts b/libs/shared/src/modules/cache/cache.schema.ts deleted file mode 100644 index 5b691b4d..00000000 --- a/libs/shared/src/modules/cache/cache.schema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, SchemaTypes } from 'mongoose'; - -export type BasesLocaleDocument = HydratedDocument; - -@Schema({ collection: '_mongo-cache' }) -export class Cache { - @Prop({ type: SchemaTypes.String }) - key: string; - - @Prop({ type: SchemaTypes.Mixed }) - value: any; -} - -export const CacheSchema = SchemaFactory.createForClass(Cache); diff --git a/libs/shared/src/modules/cache/cache.service.ts b/libs/shared/src/modules/cache/cache.service.ts deleted file mode 100644 index ca44d073..00000000 --- a/libs/shared/src/modules/cache/cache.service.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model } from 'mongoose'; - -import { Cache } from '@/shared/modules/cache/cache.schema'; - -@Injectable() -export class CacheService { - constructor(@InjectModel(Cache.name) private cacheModel: Model) {} - - public async get(key: string): Promise { - const cache: any = await this.cacheModel.findOne({ key }); - - return cache?.value; - } - - public async set(key: string, value: any) { - return this.cacheModel.updateOne( - { key }, - { $set: { value } }, - { upsert: true }, - ); - } -} diff --git a/libs/shared/src/modules/export_csv/export_csv.module.ts b/libs/shared/src/modules/export_csv/export_csv.module.ts index fb7930bd..e8df15cb 100644 --- a/libs/shared/src/modules/export_csv/export_csv.module.ts +++ b/libs/shared/src/modules/export_csv/export_csv.module.ts @@ -1,23 +1,13 @@ import { Module } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; - -import { Numero, NumeroSchema } from '@/shared/schemas/numero/numero.schema'; -import { Voie, VoieSchema } from '@/shared/schemas/voie/voie.schema'; -import { - Toponyme, - ToponymeSchema, -} from '@/shared/schemas/toponyme/toponyme.schema'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { ExportCsvService } from '@/shared/modules/export_csv/export_csv.service'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Numero } from '@/shared/entities/numero.entity'; @Module({ - imports: [ - MongooseModule.forFeature([ - { name: Numero.name, schema: NumeroSchema }, - { name: Voie.name, schema: VoieSchema }, - { name: Toponyme.name, schema: ToponymeSchema }, - ]), - ], + imports: [TypeOrmModule.forFeature([Toponyme, Voie, Numero])], providers: [ExportCsvService], exports: [ExportCsvService], }) diff --git a/libs/shared/src/modules/export_csv/export_csv.service.ts b/libs/shared/src/modules/export_csv/export_csv.service.ts index 3a2044b1..a61a4d42 100644 --- a/libs/shared/src/modules/export_csv/export_csv.service.ts +++ b/libs/shared/src/modules/export_csv/export_csv.service.ts @@ -1,34 +1,56 @@ import { Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model, Types } from 'mongoose'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; import { exportBalToCsv } from '@/shared/modules/export_csv/utils/export_csv_bal.utils'; import { exportVoiesToCsv } from '@/shared/modules/export_csv/utils/export_csv_voies.utils'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; @Injectable() export class ExportCsvService { constructor( - @InjectModel(Numero.name) private numeroModel: Model, - @InjectModel(Voie.name) private voieModel: Model, - @InjectModel(Toponyme.name) private toponymeModel: Model, + @InjectRepository(Numero) + private numerosRepository: Repository, + @InjectRepository(Voie) + private voiesRepository: Repository, + @InjectRepository(Toponyme) + private toponymesRepository: Repository, ) {} - async getAllFromBal(balId: Types.ObjectId) { - const voies: Voie[] = await this.voieModel.find({ - _bal: balId, - _deleted: null, + async getAllFromBal(balId: string) { + const voies: Voie[] = await this.voiesRepository.find({ + where: { + balId, + deletedAt: null, + }, + order: { + nom: 'ASC', + }, }); - const toponymes: Toponyme[] = await this.toponymeModel.find({ - _bal: balId, - _deleted: null, + const toponymes: Toponyme[] = await this.toponymesRepository.find({ + where: { + balId, + deletedAt: null, + }, + order: { + nom: 'ASC', + }, }); - const numeros: Numero[] = await this.numeroModel.find({ - _bal: balId, - _deleted: null, + const numeros: Numero[] = await this.numerosRepository.find({ + where: { + balId, + deletedAt: null, + }, + order: { + numero: 'ASC', + suffixe: { + direction: 'ASC', + nulls: 'FIRST', + }, + }, }); return { voies, toponymes, numeros }; } @@ -38,7 +60,7 @@ export class ExportCsvService { withComment: boolean = false, ): Promise { const { voies, toponymes, numeros } = await this.getAllFromBal( - baseLocale._id, + baseLocale.id, ); return exportBalToCsv(baseLocale, voies, toponymes, numeros, withComment); @@ -46,7 +68,7 @@ export class ExportCsvService { async exportVoiesToCsv(baseLocale: BaseLocale): Promise { const { voies, toponymes, numeros } = await this.getAllFromBal( - baseLocale._id, + baseLocale.id, ); return exportVoiesToCsv(voies, toponymes, numeros); diff --git a/libs/shared/src/modules/export_csv/utils/export_csv_bal.utils.ts b/libs/shared/src/modules/export_csv/utils/export_csv_bal.utils.ts index 28184200..6f8e7894 100644 --- a/libs/shared/src/modules/export_csv/utils/export_csv_bal.utils.ts +++ b/libs/shared/src/modules/export_csv/utils/export_csv_bal.utils.ts @@ -5,12 +5,12 @@ import { keyBy } from 'lodash'; import * as pumpify from 'pumpify'; import * as proj from '@etalab/project-legal'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; import { getCommune } from '@/shared/utils/cog.utils'; import { roundCoordinate } from '@/shared/utils/coor.utils'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; const DEFAULT_CODE_VOIE = 'xxxx'; const DEFAULT_NUMERO_TOPONYME = 99999; @@ -35,7 +35,7 @@ type RowType = { nomToponymeAlt?: Record; parcelles: string[]; position?: any; - _updated: Date; + updatedAt: Date; comment?: string; }; @@ -114,7 +114,7 @@ function createRow(obj: RowType, withComment: boolean): CsvRowType { id_ban_adresse: obj.banIds.adresse || '', voie_nom: obj.nomVoie, lieudit_complement_nom: obj.nomToponyme || '', - numero: Number.isInteger(obj.numero) ? obj.numero.toString() : '', + numero: obj.numero.toString() || '', suffixe: obj.suffixe || '', certification_commune: toCsvBoolean(obj.certifie), commune_insee: obj.codeCommune, @@ -126,7 +126,7 @@ function createRow(obj: RowType, withComment: boolean): CsvRowType { y: '', cad_parcelles: obj.parcelles ? obj.parcelles.join('|') : '', source: DEFAULT_SOURCE, - date_der_maj: obj._updated ? obj._updated.toISOString().slice(0, 10) : '', + date_der_maj: obj.updatedAt ? obj.updatedAt.toISOString().slice(0, 10) : '', }; if (withComment) { @@ -168,18 +168,15 @@ export async function exportBalToCsv( numeros: Numero[], withComment: boolean, ): Promise { - const voiesIndex: Record = keyBy(voies, (v) => - v._id.toHexString(), - ); + const voiesIndex: Record = keyBy(voies, 'id'); const rows: RowType[] = []; numeros.forEach((n) => { - const voieId: string = n.voie.toHexString(); - const v: Voie = voiesIndex[voieId]; + const v: Voie = voiesIndex[n.voieId]; let toponyme: Toponyme = null; - if (n.toponyme) { - toponyme = toponymes.find(({ _id }) => _id.equals(n.toponyme)); + if (n.toponymeId) { + toponyme = toponymes.find(({ id }) => id == n.toponymeId); if (!toponyme) { throw new Error( @@ -191,17 +188,17 @@ export async function exportBalToCsv( if (n.positions && n.positions.length > 0) { n.positions.forEach((p) => { rows.push({ + codeCommune: baseLocale.commune, banIds: { commune: baseLocale.banId, toponyme: v.banId, adresse: n.banId, }, - codeCommune: n.commune, codeVoie: DEFAULT_CODE_VOIE, numero: n.numero, suffixe: n.suffixe, certifie: n.certifie || false, - _updated: n._updated, + updatedAt: n.updatedAt, nomVoie: v.nom, nomVoieAlt: v.nomAlt || null, nomToponyme: toponyme?.nom || null, @@ -218,14 +215,14 @@ export async function exportBalToCsv( if (t.positions.length > 0) { t.positions.forEach((p) => { rows.push({ + codeCommune: baseLocale.commune, banIds: { commune: baseLocale.banId, toponyme: t.banId, }, - codeCommune: t.commune, codeVoie: DEFAULT_CODE_VOIE, numero: DEFAULT_NUMERO_TOPONYME, - _updated: t._updated, + updatedAt: t.updatedAt, nomVoie: t.nom, nomVoieAlt: t.nomAlt || null, parcelles: t.parcelles, @@ -238,10 +235,10 @@ export async function exportBalToCsv( commune: baseLocale.banId, toponyme: t.banId, }, - codeCommune: t.commune, + codeCommune: baseLocale.commune, codeVoie: DEFAULT_CODE_VOIE, numero: DEFAULT_NUMERO_TOPONYME, - _updated: t._updated, + updatedAt: t.updatedAt, nomVoie: t.nom, nomVoieAlt: t.nomAlt || null, parcelles: t.parcelles, diff --git a/libs/shared/src/modules/export_csv/utils/export_csv_voies.utils.ts b/libs/shared/src/modules/export_csv/utils/export_csv_voies.utils.ts index 79dead93..82f41f88 100644 --- a/libs/shared/src/modules/export_csv/utils/export_csv_voies.utils.ts +++ b/libs/shared/src/modules/export_csv/utils/export_csv_voies.utils.ts @@ -3,9 +3,9 @@ import * as getStream from 'get-stream'; import * as intoStream from 'into-stream'; import * as pumpify from 'pumpify'; -import { Toponyme } from '@/shared/schemas/toponyme/toponyme.schema'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; +import { Toponyme } from '@/shared/entities/toponyme.entity'; +import { Numero } from '@/shared/entities/numero.entity'; +import { Voie } from '@/shared/entities/voie.entity'; type CsvRow = { type: string; @@ -43,17 +43,15 @@ function createKeysNomAlts( } function modelToRow( - type: 'voie' | 'toponyme', + type: 'voieId' | 'toponymeId', model: Voie | Toponyme, numeros: Numero[], keysNomAlts: Record, ) { - const numerosVoie: Numero[] = numeros.filter( - (n) => model._id.toHexString() === n[type]?.toHexString(), - ); + const numerosVoie: Numero[] = numeros.filter((n) => model.id === n[type]); const row: CsvRow = { - type, + type: type === 'voieId' ? 'voie' : 'toponyme', nom: model.nom || '', }; @@ -82,8 +80,8 @@ export function exportVoiesToCsv( ...toponymes, ]); const rows: CsvRow[] = [ - ...voies.map((v) => modelToRow('voie', v, numeros, keysNomAlts)), - ...toponymes.map((t) => modelToRow('toponyme', t, numeros, keysNomAlts)), + ...voies.map((v) => modelToRow('voieId', v, numeros, keysNomAlts)), + ...toponymes.map((t) => modelToRow('toponymeId', t, numeros, keysNomAlts)), ]; const header: string[] = createHeader(rows); diff --git a/libs/shared/src/modules/publication/publication.module.ts b/libs/shared/src/modules/publication/publication.module.ts index 9ee1afbb..02a400c7 100644 --- a/libs/shared/src/modules/publication/publication.module.ts +++ b/libs/shared/src/modules/publication/publication.module.ts @@ -1,23 +1,17 @@ import { Module } from '@nestjs/common'; -import { MongooseModule } from '@nestjs/mongoose'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule } from '@nestjs/config'; -import { - BaseLocale, - BaseLocaleSchema, -} from '@/shared/schemas/base_locale/base_locale.schema'; -import { Numero, NumeroSchema } from '@/shared/schemas/numero/numero.schema'; import { ApiDepotModule } from '@/shared/modules/api_depot/api_depot.module'; import { ExportCsvModule } from '@/shared/modules/export_csv/export_csv.module'; import { PublicationService } from '@/shared/modules/publication/publication.service'; -import { ConfigModule } from '@nestjs/config'; +import { BaseLocale } from '@/shared/entities/base_locale.entity'; +import { Numero } from '@/shared/entities/numero.entity'; @Module({ imports: [ ConfigModule, - MongooseModule.forFeature([ - { name: BaseLocale.name, schema: BaseLocaleSchema }, - { name: Numero.name, schema: NumeroSchema }, - ]), + TypeOrmModule.forFeature([BaseLocale, Numero]), ApiDepotModule, ExportCsvModule, ], diff --git a/libs/shared/src/modules/publication/publication.service.ts b/libs/shared/src/modules/publication/publication.service.ts index e2f1437d..4a0396c9 100644 --- a/libs/shared/src/modules/publication/publication.service.ts +++ b/libs/shared/src/modules/publication/publication.service.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { InjectModel } from '@nestjs/mongoose'; -import { Model, Types } from 'mongoose'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; import { MailerService } from '@nestjs-modules/mailer'; import { ConfigService } from '@nestjs/config'; import * as hasha from 'hasha'; @@ -10,15 +10,15 @@ import { StatusHabiliation, } from '@/shared/modules/api_depot/types/habilitation.type'; import { Revision } from '@/shared/modules/api_depot/types/revision.type'; -import { BaseLocale } from '@/shared/schemas/base_locale/base_locale.schema'; import { + BaseLocale, StatusBaseLocalEnum, StatusSyncEnum, -} from '@/shared/schemas/base_locale/status.enum'; -import { Numero } from '@/shared/schemas/numero/numero.schema'; + BaseLocaleSync, +} from '@/shared/entities/base_locale.entity'; +import { Numero } from '@/shared/entities/numero.entity'; import { ApiDepotService } from '@/shared/modules/api_depot/api_depot.service'; import { ExportCsvService } from '@/shared/modules/export_csv/export_csv.service'; -import { Sync } from '@/shared/schemas/base_locale/sync.schema'; import { getApiUrl, getEditorUrl } from '@/shared/utils/mailer.utils'; @Injectable() @@ -27,16 +27,20 @@ export class PublicationService { private readonly apiDepotService: ApiDepotService, private readonly exportCsvService: ExportCsvService, private readonly mailerService: MailerService, - @InjectModel(BaseLocale.name) private baseLocaleModel: Model, - @InjectModel(Numero.name) private numeroModel: Model, + @InjectRepository(BaseLocale) + private basesLocalesRepository: Repository, + @InjectRepository(Numero) + private numerosRepository: Repository, private configService: ConfigService, ) {} async exec( - balId: Types.ObjectId, + balId: string, options: { force?: boolean } = {}, ): Promise { - const baseLocale = await this.baseLocaleModel.findOne(balId).lean(); + const baseLocale = await this.basesLocalesRepository.findOneBy({ + id: balId, + }); // On vérifie que la BAL n'est pas en DEMO ou DRAFT if (baseLocale.status === StatusBaseLocalEnum.DEMO) { @@ -49,7 +53,7 @@ export class PublicationService { const codeCommune = baseLocale.commune; // On vérifie que la BAL a une habilitation rattachée - if (!baseLocale._habilitation) { + if (!baseLocale.habilitationId) { await this.pause(balId); throw new HttpException( 'Aucune habilitation rattachée à cette Base Adresse Locale', @@ -61,7 +65,7 @@ export class PublicationService { let habilitation: Habilitation; try { habilitation = await this.apiDepotService.findOneHabiliation( - baseLocale._habilitation, + baseLocale.habilitationId, ); } catch (err) { // Si l'habilitation est introuvable sur l'API dépot on met la BAL en pause @@ -92,10 +96,10 @@ export class PublicationService { ); } - // On récupère les numeros de la BAL - const numeroCount = await this.numeroModel.countDocuments({ - _bal: balId, - _deleted: null, + // On récupère le nombre de numeros de la BAL + const numeroCount = await this.numerosRepository.countBy({ + balId, + deletedAt: null, }); // On vérifie qu'il y ai au moins un numero dans la BAL if (numeroCount === 0) { @@ -104,7 +108,6 @@ export class PublicationService { HttpStatus.PRECONDITION_FAILED, ); } - // On traite ensuite le cas de la première publication if (baseLocale.status === StatusBaseLocalEnum.DRAFT) { // On créer le fichier BAL CSV @@ -113,9 +116,9 @@ export class PublicationService { const publishedRevision: Revision = await this.apiDepotService.publishNewRevision( codeCommune, - baseLocale._id.toString(), + baseLocale.id, file, - baseLocale._habilitation, + baseLocale.habilitationId, ); // SEND MAIL @@ -161,46 +164,42 @@ export class PublicationService { // On créer la publication sur l'api-depot const publishedRevision = await this.apiDepotService.publishNewRevision( codeCommune, - baseLocale._id.toString(), + baseLocale.id, file, - baseLocale._habilitation, + baseLocale.habilitationId, ); // On marque le sync de la BAL en published return this.markAsSynced(baseLocale, publishedRevision._id); } // On marque le sync de la BAL en published - return this.markAsSynced( - baseLocale, - sync.lastUploadedRevisionId.toString(), - ); + return this.markAsSynced(baseLocale, sync.lastUploadedRevisionId); } - return this.baseLocaleModel.findOne(balId).lean(); + return this.basesLocalesRepository.findOneBy({ id: balId }); } - public pause(balId: Types.ObjectId) { + public async pause(balId: string) { return this.setIsPaused(balId, true); } - public resume(balId: Types.ObjectId) { + public async resume(balId: string) { return this.setIsPaused(balId, false); } - private async setIsPaused( - balId: Types.ObjectId, - isPaused: boolean, - ): Promise { - await this.baseLocaleModel.updateOne( - { _id: balId }, - { $set: { 'sync.isPaused': isPaused } }, + private async setIsPaused(balId: string, isPaused: boolean): Promise { + await this.basesLocalesRepository.update( + { + id: balId, + }, + { sync: { isPaused } }, ); } private async updateSync( baseLocale: BaseLocale, - syncChanges: Partial, - ): Promise { + syncChanges: Partial, + ): Promise { const changes: Partial = { sync: { ...baseLocale.sync, @@ -211,40 +210,43 @@ export class PublicationService { }), }; - const baseLocaleChanged: BaseLocale = await this.baseLocaleModel - .findOneAndUpdate( - { _id: baseLocale._id }, - { $set: changes }, - { new: true }, - ) - .lean(); + await this.basesLocalesRepository.update( + { + id: baseLocale.id, + }, + changes, + ); - return baseLocaleChanged.sync; + return changes.sync; } private async markAsSynced( baseLocale: BaseLocale, lastUploadedRevisionId: string, ) { - const sync: Sync = { + const now = new Date(); + const sync: BaseLocaleSync = { status: StatusSyncEnum.SYNCED, isPaused: false, - currentUpdated: baseLocale._updated, - lastUploadedRevisionId: new Types.ObjectId(lastUploadedRevisionId), + currentUpdated: now, + lastUploadedRevisionId, }; - const baseLocaleSynced: BaseLocale = await this.baseLocaleModel - .findOneAndUpdate( - { _id: baseLocale._id }, - { $set: { status: StatusBaseLocalEnum.PUBLISHED, sync } }, - { new: true }, - ) - .lean(); + await this.basesLocalesRepository.update( + { id: baseLocale.id }, + { + status: StatusBaseLocalEnum.PUBLISHED, + sync, + updatedAt: () => `'${now.toISOString()}'`, + }, + ); - return baseLocaleSynced; + return this.basesLocalesRepository.findOneBy({ id: baseLocale.id }); } - private async updateSyncInfo(baseLocale: BaseLocale): Promise { + private async updateSyncInfo( + baseLocale: BaseLocale, + ): Promise { // Si le status de la BAL est différent de PUBLISHED on retourne sync if (baseLocale.status !== StatusBaseLocalEnum.PUBLISHED) { return baseLocale.sync; @@ -280,7 +282,7 @@ export class PublicationService { // Si la date du changement de BAL est la même que la date du currentUpdated du sync de la BAL // On met le status du sync de la BAL a sync et on le retourne - if (baseLocale._updated === baseLocale.sync.currentUpdated) { + if (baseLocale.updatedAt === baseLocale.sync.currentUpdated) { if (baseLocale.sync.status === StatusSyncEnum.SYNCED) { return baseLocale.sync; } diff --git a/libs/shared/src/schemas/base-entity.schema.ts b/libs/shared/src/schemas/base-entity.schema.ts deleted file mode 100644 index d628bff7..00000000 --- a/libs/shared/src/schemas/base-entity.schema.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Prop } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { SchemaTypes, Types } from 'mongoose'; - -export class BaseEntity { - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId, auto: true }) - _id: Types.ObjectId; - - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.UUID }) - banId: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.Date, default: Date.now }) - _created: Date; - - @ApiProperty() - @Prop({ type: SchemaTypes.Date, default: Date.now }) - _updated: Date; - - @ApiProperty() - @Prop({ type: SchemaTypes.Date, default: null }) - _deleted?: Date; -} diff --git a/libs/shared/src/schemas/base_locale/base_locale.schema.ts b/libs/shared/src/schemas/base_locale/base_locale.schema.ts deleted file mode 100644 index 18b13712..00000000 --- a/libs/shared/src/schemas/base_locale/base_locale.schema.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, SchemaTypes } from 'mongoose'; -import { Sync, SyncSchema } from './sync.schema'; -import { StatusBaseLocalEnum } from './status.enum'; -import { BaseEntity } from '../base-entity.schema'; -import { ApiProperty } from '@nestjs/swagger'; - -export type BasesLocaleDocument = HydratedDocument; - -@Schema({ collection: 'bases_locales' }) -export class BaseLocale extends BaseEntity { - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - nom: string; - - @ApiProperty() - @Prop({ type: [SchemaTypes.String] }) - emails: string[]; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - token: string; - - @ApiProperty({ enum: StatusBaseLocalEnum }) - @Prop({ type: SchemaTypes.String, enum: StatusBaseLocalEnum }) - status: StatusBaseLocalEnum; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - _habilitation: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - commune: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.Boolean }) - enableComplement: boolean; - - @ApiProperty() - @Prop({ type: SyncSchema }) - sync: Sync; -} - -export const BaseLocaleSchema = SchemaFactory.createForClass(BaseLocale); diff --git a/libs/shared/src/schemas/base_locale/status.enum.ts b/libs/shared/src/schemas/base_locale/status.enum.ts deleted file mode 100644 index 6fcd205d..00000000 --- a/libs/shared/src/schemas/base_locale/status.enum.ts +++ /dev/null @@ -1,12 +0,0 @@ -export enum StatusBaseLocalEnum { - DRAFT = 'draft', - PUBLISHED = 'published', - DEMO = 'demo', - REPLACED = 'replaced', -} - -export enum StatusSyncEnum { - OUTDATED = 'outdated', - SYNCED = 'synced', - CONFLICT = 'conflict', -} diff --git a/libs/shared/src/schemas/base_locale/sync.schema.ts b/libs/shared/src/schemas/base_locale/sync.schema.ts deleted file mode 100644 index 6f00e427..00000000 --- a/libs/shared/src/schemas/base_locale/sync.schema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, SchemaTypes, Types } from 'mongoose'; -import { StatusSyncEnum } from './status.enum'; -import { ApiProperty } from '@nestjs/swagger'; - -export type SyncDocument = HydratedDocument; - -@Schema({ - _id: false, -}) -export class Sync { - @ApiProperty({ enum: StatusSyncEnum }) - @Prop({ type: SchemaTypes.String, enum: StatusSyncEnum }) - status: StatusSyncEnum; - - @ApiProperty() - @Prop({ type: SchemaTypes.Boolean }) - isPaused?: boolean; - - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId }) - lastUploadedRevisionId: Types.ObjectId; - - @ApiProperty() - @Prop({ type: SchemaTypes.Date }) - currentUpdated?: Date; -} - -export const SyncSchema = SchemaFactory.createForClass(Sync); diff --git a/libs/shared/src/schemas/feature_point.schema.ts b/libs/shared/src/schemas/feature_point.schema.ts deleted file mode 100644 index d6dec6d1..00000000 --- a/libs/shared/src/schemas/feature_point.schema.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { SchemaTypes } from 'mongoose'; -import { Point, PointSchema } from './geometry/point.schema'; -import { ValidateNested, Equals, IsOptional } from 'class-validator'; -import { Type } from 'class-transformer'; -import { Point as PointTurf, Feature as FeatureTurf } from '@turf/helpers'; - -@Schema({ - _id: false, -}) -export class FeaturePoint implements FeatureTurf { - @Equals('Feature') - @ApiProperty() - @Prop({ type: SchemaTypes.String, required: true }) - type: 'Feature'; - - @IsOptional() - @ApiProperty() - @Prop({ type: Object }) - properties: Record; - - @ValidateNested() - @Type(() => Point) - @ApiProperty({ type: () => Point }) - @Prop({ type: PointSchema }) - geometry: Point; -} - -export const FeaturePointSchema = SchemaFactory.createForClass(FeaturePoint); diff --git a/libs/shared/src/schemas/geometry/point.schema.ts b/libs/shared/src/schemas/geometry/point.schema.ts deleted file mode 100644 index 09857371..00000000 --- a/libs/shared/src/schemas/geometry/point.schema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { HydratedDocument, SchemaTypes } from 'mongoose'; -import { Validate, Equals } from 'class-validator'; -import { PointValidator } from '../../validators/coord.validator'; -import { Point as PointTurf, Position as PositionTurf } from '@turf/helpers'; - -export type PointDocument = HydratedDocument; - -@Schema({ - _id: false, -}) -export class Point implements PointTurf { - @Equals('Point') - @ApiProperty({ required: true, nullable: false }) - @Prop({ - type: SchemaTypes.String, - required: true, - nullable: false, - }) - type: 'Point'; - - @Validate(PointValidator) - @ApiProperty({ required: true, nullable: false, type: Number, isArray: true }) - @Prop({ type: [SchemaTypes.Number], required: true, nullable: false }) - coordinates: PositionTurf; -} - -export const PointSchema = SchemaFactory.createForClass(Point); diff --git a/libs/shared/src/schemas/numero/numero.populate.ts b/libs/shared/src/schemas/numero/numero.populate.ts deleted file mode 100644 index 05b03676..00000000 --- a/libs/shared/src/schemas/numero/numero.populate.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Types } from 'mongoose'; -import { Voie } from '@/shared/schemas/voie/voie.schema'; -import { Position } from '../position.schema'; -import { BaseEntity } from '../base-entity.schema'; - -export class NumeroPopulate extends BaseEntity { - @ApiProperty({ type: String }) - _bal: Types.ObjectId; - - @ApiProperty() - numero: number; - - @ApiProperty() - numeroComplet: string; - - @ApiProperty() - commune: string; - - @ApiProperty() - suffixe?: string; - - @ApiProperty() - comment?: string; - - @ApiProperty({ type: String }) - toponyme?: Types.ObjectId; - - @ApiProperty() - voie: Voie; - - @ApiProperty() - parcelles?: string[]; - - @ApiProperty() - certifie?: boolean; - - @ApiProperty({ type: () => Position, isArray: true }) - positions: Position[]; - - @ApiProperty() - tiles: string[]; -} diff --git a/libs/shared/src/schemas/numero/numero.schema.ts b/libs/shared/src/schemas/numero/numero.schema.ts deleted file mode 100644 index 34ca4b93..00000000 --- a/libs/shared/src/schemas/numero/numero.schema.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { HydratedDocument, SchemaTypes, Types } from 'mongoose'; -import { mongooseLeanVirtuals } from 'mongoose-lean-virtuals'; -import { Position, PositionSchema } from '../position.schema'; -import { BaseEntity } from '../base-entity.schema'; -import { displaySuffix } from '../../utils/numero.utils'; -import { Voie } from '../voie/voie.schema'; - -export type NumeroDocument = HydratedDocument; - -@Schema({ - collection: 'numeros', - toJSON: { - virtuals: true, - }, -}) -export class Numero extends BaseEntity { - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId }) - _bal: Types.ObjectId; - - @ApiProperty() - @Prop({ type: SchemaTypes.Number }) - numero: number; - - @ApiProperty() - numeroComplet: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - commune: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - suffixe?: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - comment?: string; - - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId }) - toponyme?: Types.ObjectId; - - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId, ref: Voie.name }) - voie: Types.ObjectId; - - @ApiProperty() - @Prop([SchemaTypes.String]) - parcelles?: string[]; - - @ApiProperty() - @Prop({ type: SchemaTypes.Boolean }) - certifie?: boolean; - - @ApiProperty({ type: () => Position, isArray: true }) - @Prop({ type: [PositionSchema] }) - positions: Position[]; - - @ApiProperty() - @Prop({ type: [SchemaTypes.String] }) - tiles: string[]; -} - -export const NumeroSchema = SchemaFactory.createForClass(Numero); - -// VIRTUALS - -NumeroSchema.virtual('numeroComplet').get(function () { - return this.numero + displaySuffix(this); -}); - -// Return virtuals when using lean() -NumeroSchema.plugin(mongooseLeanVirtuals); - -// INDEXES - -NumeroSchema.index({ _bal: 1 }); -NumeroSchema.index({ _bal: 1, commune: 1 }); -NumeroSchema.index({ voie: 1 }); -NumeroSchema.index({ toponyme: 1 }); -NumeroSchema.index({ _deleted: 1 }); -NumeroSchema.index({ tiles: 1 }); diff --git a/libs/shared/src/schemas/position.schema.ts b/libs/shared/src/schemas/position.schema.ts deleted file mode 100644 index 36811bfd..00000000 --- a/libs/shared/src/schemas/position.schema.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { HydratedDocument, SchemaTypes } from 'mongoose'; -import { Point, PointSchema } from './geometry/point.schema'; -import { ValidatorBal } from '../validators/validator_bal.validator'; -import { ValidateNested, Validate, IsEnum, IsOptional } from 'class-validator'; -import { Type } from 'class-transformer'; -import { PositionTypeEnum } from './position_type.enum'; - -export type PositionDocument = HydratedDocument; - -@Schema({ - _id: false, -}) -export class Position { - @IsEnum(PositionTypeEnum) - @ApiProperty({ enum: PositionTypeEnum }) - @Prop({ type: SchemaTypes.String, enum: PositionTypeEnum }) - type: PositionTypeEnum; - - @IsOptional() - @Validate(ValidatorBal, ['source']) - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - source?: string; - - @ValidateNested() - @Type(() => Point) - @ApiProperty({ type: () => Point }) - @Prop({ type: PointSchema }) - point: Point; -} - -export const PositionSchema = SchemaFactory.createForClass(Position); diff --git a/libs/shared/src/schemas/position_type.enum.ts b/libs/shared/src/schemas/position_type.enum.ts deleted file mode 100644 index a18e0029..00000000 --- a/libs/shared/src/schemas/position_type.enum.ts +++ /dev/null @@ -1,11 +0,0 @@ -export enum PositionTypeEnum { - ENTREE = 'entrée', - BATIMENT = 'bâtiment', - CAGE_ESCALIER = 'cage d’escalier', - LOGEMENT = 'logement', - SERVICE_TECHNIQUE = 'service technique', - DELIVRANCE_POSTALE = 'délivrance postale', - PARCELLE = 'parcelle', - SEGMENT = 'segment', - INCONNUE = 'inconnue', -} diff --git a/libs/shared/src/schemas/toponyme/toponyme.schema.ts b/libs/shared/src/schemas/toponyme/toponyme.schema.ts deleted file mode 100644 index e4199b63..00000000 --- a/libs/shared/src/schemas/toponyme/toponyme.schema.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, SchemaTypes, Types } from 'mongoose'; -import { Position, PositionSchema } from '../position.schema'; -import { BaseEntity } from '../base-entity.schema'; -import { ApiProperty } from '@nestjs/swagger'; - -export type ToponymeDocument = HydratedDocument; - -@Schema({ collection: 'toponymes' }) -export class Toponyme extends BaseEntity { - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId }) - _bal: Types.ObjectId; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - nom: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - commune: string; - - @ApiProperty() - @Prop({ type: Object }) - nomAlt: Record; - - @ApiProperty() - @Prop([SchemaTypes.String]) - parcelles?: string[]; - - @ApiProperty({ type: () => Position, isArray: true }) - @Prop({ type: [PositionSchema] }) - positions: Position[]; -} - -export const ToponymeSchema = SchemaFactory.createForClass(Toponyme); diff --git a/libs/shared/src/schemas/voie/type_numerotation.enum.ts b/libs/shared/src/schemas/voie/type_numerotation.enum.ts deleted file mode 100644 index 2b917de6..00000000 --- a/libs/shared/src/schemas/voie/type_numerotation.enum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum TypeNumerotationEnum { - NUMERIQUE = 'numerique', - METRIQUE = 'metrique', -} diff --git a/libs/shared/src/schemas/voie/voie.populate.ts b/libs/shared/src/schemas/voie/voie.populate.ts deleted file mode 100644 index d42b8fe2..00000000 --- a/libs/shared/src/schemas/voie/voie.populate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { Numero } from '../numero/numero.schema'; -import { Voie } from './voie.schema'; - -export class PopulateVoie extends Voie { - @ApiProperty({ type: () => Numero, isArray: true }) - numeros?: Numero[]; -} diff --git a/libs/shared/src/schemas/voie/voie.schema.ts b/libs/shared/src/schemas/voie/voie.schema.ts deleted file mode 100644 index fe2f065a..00000000 --- a/libs/shared/src/schemas/voie/voie.schema.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose'; -import { HydratedDocument, SchemaTypes, Types } from 'mongoose'; -import { ApiProperty } from '@nestjs/swagger'; -import { FeaturePoint, FeaturePointSchema } from '../feature_point.schema'; -import { LineString, LineStringSchema } from '../geometry/line_string.schema'; -import { TypeNumerotationEnum } from './type_numerotation.enum'; -import { BaseEntity } from '../base-entity.schema'; - -export type VoieDocument = HydratedDocument; - -@Schema({ collection: 'voies' }) -export class Voie extends BaseEntity { - @ApiProperty({ type: String }) - @Prop({ type: SchemaTypes.ObjectId }) - _bal: Types.ObjectId; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - nom: string; - - @ApiProperty() - @Prop({ type: SchemaTypes.String }) - commune: string; - - @ApiProperty() - @Prop({ type: Object }) - nomAlt: Record; - - @ApiProperty() - @Prop({ type: FeaturePointSchema }) - centroid: FeaturePoint; - - @ApiProperty() - @Prop({ type: [SchemaTypes.String] }) - centroidTiles: string[]; - - @ApiProperty({ enum: TypeNumerotationEnum }) - @Prop({ type: SchemaTypes.String, enum: TypeNumerotationEnum }) - typeNumerotation: TypeNumerotationEnum; - - @ApiProperty() - @Prop({ type: LineStringSchema }) - trace: LineString; - - @ApiProperty() - @Prop({ type: [SchemaTypes.String] }) - traceTiles: string[]; -} - -export const VoieSchema = SchemaFactory.createForClass(Voie); diff --git a/libs/shared/src/types/with-numero.type.ts b/libs/shared/src/types/with-numero.type.ts index 6654a501..64ca3507 100644 --- a/libs/shared/src/types/with-numero.type.ts +++ b/libs/shared/src/types/with-numero.type.ts @@ -1,4 +1,4 @@ -import { Numero } from '../schemas/numero/numero.schema'; +import { Numero } from '../entities/numero.entity'; export type WithNumero = T & { nbNumeros: number; diff --git a/libs/shared/src/utils/mailer.utils.ts b/libs/shared/src/utils/mailer.utils.ts index caba6273..2349c476 100644 --- a/libs/shared/src/utils/mailer.utils.ts +++ b/libs/shared/src/utils/mailer.utils.ts @@ -1,3 +1,5 @@ +import { BaseLocale } from '../entities/base_locale.entity'; + function getEditorUrlPattern() { if (process.env.EDITOR_URL_PATTERN) { return process.env.EDITOR_URL_PATTERN; @@ -22,14 +24,14 @@ export function getApiUrl() { return 'http://api.domain.tld'; } -export function getEditorUrl(baseLocale) { +export function getEditorUrl(baseLocale: BaseLocale) { return getEditorUrlPattern() - .replace('', baseLocale._id) + .replace('', baseLocale.id) .replace('', baseLocale.token); } export function getApiRecoveryUrl(baseLocale) { - return `${getApiUrl()}/v2/bases-locales/${baseLocale._id}/${ + return `${getApiUrl()}/v2/bases-locales/${baseLocale.id}/${ baseLocale.token }/recovery`; } diff --git a/libs/shared/src/utils/numero.utils.ts b/libs/shared/src/utils/numero.utils.ts index c3b367e7..19a226be 100644 --- a/libs/shared/src/utils/numero.utils.ts +++ b/libs/shared/src/utils/numero.utils.ts @@ -1,5 +1,4 @@ -import { Numero } from '../schemas/numero/numero.schema'; -import { NumeroPopulate } from '../schemas/numero/numero.populate'; +import { Numero } from '../entities/numero.entity'; import { WithNumero } from '../types/with-numero.type'; export function displaySuffix(numero: Numero): string { @@ -14,9 +13,9 @@ export function displaySuffix(numero: Numero): string { } export function filterSensitiveFields( - numero: Numero | NumeroPopulate, + numero: Numero, filter: boolean = true, -): Numero | NumeroPopulate { +): Numero { if (filter && numero.comment) { numero.comment = null; } diff --git a/migrations/1725371358514-initialization.ts b/migrations/1725371358514-initialization.ts new file mode 100644 index 00000000..4d6ac31c --- /dev/null +++ b/migrations/1725371358514-initialization.ts @@ -0,0 +1,143 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Initialization1725371358514 implements MigrationInterface { + name = 'Initialization1725371358514'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."positions_type_enum" AS ENUM('entrée', 'bâtiment', 'cage d’escalier', 'logement', 'service technique', 'délivrance postale', 'parcelle', 'segment', 'inconnue')`, + ); + await queryRunner.query( + `CREATE TABLE "positions" ("id" character varying(24) NOT NULL, "toponyme_id" character varying(24), "numero_id" character varying(24), "type" "public"."positions_type_enum" NOT NULL DEFAULT 'entrée', "source" text, "rank" integer NOT NULL, "point" geometry(Point,4326) NOT NULL, CONSTRAINT "PK_17e4e62ccd5749b289ae3fae6f3" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_positions_toponyme_id" ON "positions" USING HASH ("toponyme_id") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_positions_numeros_id" ON "positions" USING HASH ("numero_id") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_positions_point" ON "positions" USING GiST ("point") `, + ); + await queryRunner.query( + `CREATE TABLE "toponymes" ("id" character varying(24) NOT NULL, "ban_id" uuid NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "bal_id" character varying(24) NOT NULL, "nom" text NOT NULL, "nom_alt" json, "parcelles" text array, CONSTRAINT "PK_f8e0f31344c7c2543562a61c1f1" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_934ff6f32b687f42142dbedf44" ON "toponymes" ("deleted_at") WHERE deleted_at IS NULL`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_toponymes_bal_id" ON "toponymes" USING HASH ("bal_id") `, + ); + await queryRunner.query( + `CREATE TABLE "numeros" ("id" character varying(24) NOT NULL, "ban_id" uuid NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "bal_id" character varying(24) NOT NULL, "voie_id" character varying(24) NOT NULL, "toponyme_id" character varying(24), "numero" integer NOT NULL, "suffixe" text, "comment" text, "parcelles" text array, "certifie" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_afc7d4916c4ae1472683de49489" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_7d845d3615a8694f9d53a9df21" ON "numeros" ("deleted_at") WHERE deleted_at IS NULL`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_numeros_bal_id" ON "numeros" USING HASH ("bal_id") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_numeros_voie_id" ON "numeros" USING HASH ("voie_id") `, + ); + await queryRunner.query( + `CREATE INDEX "IDX_numeros_toponyme_id" ON "numeros" USING HASH ("toponyme_id") `, + ); + await queryRunner.query( + `CREATE TYPE "public"."voies_type_numerotation_enum" AS ENUM('numerique', 'metrique')`, + ); + await queryRunner.query( + `CREATE TABLE "voies" ("id" character varying(24) NOT NULL, "ban_id" uuid NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "bal_id" character varying(24) NOT NULL, "nom" text NOT NULL, "nom_alt" json, "type_numerotation" "public"."voies_type_numerotation_enum" NOT NULL DEFAULT 'numerique', "centroid" geometry(Point,4326), "trace" geometry(LineString,4326), CONSTRAINT "PK_80c425cf773e2b81a56ba55a08c" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_a31fd9857f1bfdbd294b3a346e" ON "voies" ("deleted_at") WHERE deleted_at IS NULL`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_voies_bal_id" ON "voies" USING HASH ("bal_id") `, + ); + await queryRunner.query( + `CREATE TYPE "public"."bases_locales_status_enum" AS ENUM('draft', 'published', 'demo', 'replaced')`, + ); + await queryRunner.query( + `CREATE TABLE "bases_locales" ("id" character varying(24) NOT NULL, "ban_id" uuid NOT NULL, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, "nom" text NOT NULL, "commune" character varying(5) NOT NULL, "emails" text array, "token" character varying(20) NOT NULL, "status" "public"."bases_locales_status_enum" NOT NULL, "habilitation_id" character varying(24), "sync" jsonb, CONSTRAINT "PK_9ebcac4bb5518b2d3e0894cb5c4" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE INDEX "IDX_72ac99dc6e0935a571c3a772e6" ON "bases_locales" ("deleted_at") WHERE deleted_at IS NULL`, + ); + await queryRunner.query( + `ALTER TABLE "positions" ADD CONSTRAINT "FK_a085cf9b9ac326f5ac8b95a24a3" FOREIGN KEY ("toponyme_id") REFERENCES "toponymes"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "positions" ADD CONSTRAINT "FK_9c67f5a501ad6b85c76322763a2" FOREIGN KEY ("numero_id") REFERENCES "numeros"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "toponymes" ADD CONSTRAINT "FK_9c20685d64e23cd748e2aa13c28" FOREIGN KEY ("bal_id") REFERENCES "bases_locales"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" ADD CONSTRAINT "FK_dd823e556d4b255deae92952f03" FOREIGN KEY ("bal_id") REFERENCES "bases_locales"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" ADD CONSTRAINT "FK_94139df4b633b19174d80f86806" FOREIGN KEY ("voie_id") REFERENCES "voies"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" ADD CONSTRAINT "FK_b467148fe65ba857534bdc83c56" FOREIGN KEY ("toponyme_id") REFERENCES "toponymes"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "voies" ADD CONSTRAINT "FK_c8ebcc5e894d640fbabbd081c80" FOREIGN KEY ("bal_id") REFERENCES "bases_locales"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "voies" DROP CONSTRAINT "FK_c8ebcc5e894d640fbabbd081c80"`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" DROP CONSTRAINT "FK_b467148fe65ba857534bdc83c56"`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" DROP CONSTRAINT "FK_94139df4b633b19174d80f86806"`, + ); + await queryRunner.query( + `ALTER TABLE "numeros" DROP CONSTRAINT "FK_dd823e556d4b255deae92952f03"`, + ); + await queryRunner.query( + `ALTER TABLE "toponymes" DROP CONSTRAINT "FK_9c20685d64e23cd748e2aa13c28"`, + ); + await queryRunner.query( + `ALTER TABLE "positions" DROP CONSTRAINT "FK_9c67f5a501ad6b85c76322763a2"`, + ); + await queryRunner.query( + `ALTER TABLE "positions" DROP CONSTRAINT "FK_a085cf9b9ac326f5ac8b95a24a3"`, + ); + await queryRunner.query( + `DROP INDEX "public"."IDX_72ac99dc6e0935a571c3a772e6"`, + ); + await queryRunner.query(`DROP TABLE "bases_locales"`); + await queryRunner.query(`DROP TYPE "public"."bases_locales_status_enum"`); + await queryRunner.query(`DROP INDEX "public"."IDX_voies_bal_id"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_a31fd9857f1bfdbd294b3a346e"`, + ); + await queryRunner.query(`DROP TABLE "voies"`); + await queryRunner.query( + `DROP TYPE "public"."voies_type_numerotation_enum"`, + ); + await queryRunner.query(`DROP INDEX "public"."IDX_numeros_toponyme_id"`); + await queryRunner.query(`DROP INDEX "public"."IDX_numeros_voie_id"`); + await queryRunner.query(`DROP INDEX "public"."IDX_numeros_bal_id"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_7d845d3615a8694f9d53a9df21"`, + ); + await queryRunner.query(`DROP TABLE "numeros"`); + await queryRunner.query(`DROP INDEX "public"."IDX_toponymes_bal_id"`); + await queryRunner.query( + `DROP INDEX "public"."IDX_934ff6f32b687f42142dbedf44"`, + ); + await queryRunner.query(`DROP TABLE "toponymes"`); + await queryRunner.query(`DROP INDEX "public"."IDX_positions_point"`); + await queryRunner.query(`DROP INDEX "public"."IDX_positions_numeros_id"`); + await queryRunner.query(`DROP INDEX "public"."IDX_positions_toponyme_id"`); + await queryRunner.query(`DROP TABLE "positions"`); + await queryRunner.query(`DROP TYPE "public"."positions_type_enum"`); + } +} diff --git a/migrations/2023_11_29_replace_ready_to_publish.ts b/migrations/2023_11_29_replace_ready_to_publish.ts deleted file mode 100644 index 536dcb92..00000000 --- a/migrations/2023_11_29_replace_ready_to_publish.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Schema, model, connect } from 'mongoose'; -import * as dotenv from 'dotenv'; -dotenv.config(); - -export enum StatusBaseLocalEnum { - DRAFT = 'draft', - PUBLISHED = 'published', - DEMO = 'demo', - REPLACED = 'replaced', - READY_TO_PUBLISH = 'ready-to-publish', -} - -// 1. Create an interface representing a document in MongoDB. -interface IBaseLocale { - status: StatusBaseLocalEnum; -} - -// 2. Create a Schema corresponding to the document interface. -const basesLocalesSchema = new Schema({ - status: { type: String, enum: StatusBaseLocalEnum }, -}); - -// 3. Create a Model. -const BaseLocale = model('bases_locales', basesLocalesSchema); - -async function run() { - // 4. Connect to MongoDB - await connect(`${process.env.MONGODB_URL}/${process.env.MONGODB_DBNAME}`); - - await BaseLocale.updateMany( - { - status: StatusBaseLocalEnum.READY_TO_PUBLISH, - }, - { - $set: { status: StatusBaseLocalEnum.DRAFT }, - $unset: { sync: 1 }, - }, - ); - process.exit(1); -} - -run().catch((err) => console.log(err)); diff --git a/migrations/2024_04_26_fix_update_tiles_voies.ts b/migrations/2024_04_26_fix_update_tiles_voies.ts deleted file mode 100644 index 0c74f31e..00000000 --- a/migrations/2024_04_26_fix_update_tiles_voies.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { Schema, model, connect, disconnect } from 'mongoose'; -import * as turf from '@turf/turf'; -import { range, maxBy } from 'lodash'; -import { getPositionPriorityByType } from '@ban-team/adresses-util'; -import { - pointToTile, -} from '@mapbox/tilebelt'; -import * as dotenv from 'dotenv'; -dotenv.config(); - -// VOIE -interface IVoie { - _id: any; - centroid: any; - centroidTiles: string[] | null; - typeNumerotation: string; - traceTiles: string[] | null; -} -const VoieSchema = new Schema({ - _id: { type: Object }, - centroid: { type: Object }, - centroidTiles: { type: [String] }, - typeNumerotation: { type: String }, - traceTiles: { type: [String] }, -}); -const VoiesModel = model('voies', VoieSchema); - -// VOIE -interface INumero { - positions: any[]; -} -const NumeroSchema = new Schema({ - positions: { type: [Object] }, -}); -const NumeroModel = model('numeros', NumeroSchema); - -const ZOOM = { - NUMEROS_ZOOM: { - minZoom: 13, - maxZoom: 19, - }, - VOIE_ZOOM: { - minZoom: 13, - maxZoom: 19, - }, - TRACE_DISPLAY_ZOOM: { - minZoom: 13, - maxZoom: 19, - }, - TRACE_MONGO_ZOOM: { - zoom: 13, - }, -}; - -export function getPriorityPosition(positions) { - return positions.length === 0 - ? {} - : maxBy(positions, (p) => getPositionPriorityByType(p.type)); -} - -function roundCoordinate( - coordinate: number, - precision: number = 6, -): number { - return Number.parseFloat(coordinate.toFixed(precision)); -} - -function getTilesByPosition( - point: any, - { - minZoom, - maxZoom, - }: { minZoom: number; maxZoom: number } = ZOOM.NUMEROS_ZOOM, -): string[] | null { - if (!point || !minZoom || !maxZoom) { - return null; - } - - const lon: number = roundCoordinate(point.coordinates[0], 6); - const lat: number = roundCoordinate(point.coordinates[1], 6); - - const tiles: string[] = range(minZoom, maxZoom + 1).map((zoom) => { - const [x, y, z]: number[] = pointToTile(lon, lat, zoom); - return `${z}/${x}/${y}`; - }); - - return tiles; -} - -async function calcMetaTilesVoie(voie: IVoie) { - voie.centroid = null; - voie.centroidTiles = null; - voie.traceTiles = null; - - try { - const numeros = await NumeroModel.find( - { voie: voie._id, _deleted: null }, - { positions: 1, voie: 1 }, - ); - if (numeros.length > 0) { - const coordinatesNumeros = numeros - .filter((n) => n.positions && n.positions.length > 0) - .map((n) => getPriorityPosition(n.positions)?.point?.coordinates); - // CALC CENTROID - if (coordinatesNumeros.length > 0) { - const featureCollection = turf.featureCollection( - coordinatesNumeros.map((n) => turf.point(n)), - ); - voie.centroid = turf.centroid(featureCollection); - voie.centroidTiles = getTilesByPosition( - voie.centroid.geometry, - ZOOM.VOIE_ZOOM, - ); - } - } - } catch (error) { - console.error(error, voie._id); - } - - return voie; -} - -async function run() { - await connect(`${process.env.MONGODB_URL}/${process.env.MONGODB_DBNAME}`); - - const total = await VoiesModel.count({centroid: {$exists: false}, typeNumerotation: {$ne: 'metrique'}}) - console.log(`START ${total} VOIES`) - - const voiesCursor = VoiesModel.find({centroid: {$exists: false}, typeNumerotation: {$ne: 'metrique'}}) - let count = 0 - - for await (const v of voiesCursor) { - count++ - - await calcMetaTilesVoie(v) - await VoiesModel.updateOne( - {_id: v._id}, - { $set: { centroid: v.centroid, centroidTiles: v.centroidTiles, traceTiles: v.traceTiles }} - ) - if (count % 100 === 0) { - console.log(`${count}/${total}`) - } - } - - await disconnect() - process.exit(1); -} - -run().catch((err) => console.log(err)); diff --git a/migrations/2024_04_30_add_ban_id.ts b/migrations/2024_04_30_add_ban_id.ts deleted file mode 100644 index 79a89e32..00000000 --- a/migrations/2024_04_30_add_ban_id.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Schema, model, connect, Types, disconnect } from 'mongoose'; -import { v4 as uuid } from 'uuid'; -import * as dotenv from 'dotenv'; -dotenv.config(); - -interface IBaseLocale { - _id: Types.ObjectId; - banId: string; - commune: string; -} -const basesLocalesSchema = new Schema({ - _id: { type: Schema.Types.ObjectId }, - banId: { type: String }, - commune: { type: String }, -}); - - -interface IOther { - id: Types.ObjectId; - banId: string; -} -const otherSchema = new Schema({ - id: { type: Schema.Types.ObjectId }, - banId: { type: String }, -}); - -async function updateManyUuid(collection: string) { - let count = 0 - const Model = model(collection, otherSchema); - const cursor = Model.find({banId: {$exists: false}}).lean() - const total = await Model.count({banId: {$exists: false}}) - - console.log(`START ${total} ${collection}`) - for await (const entity of cursor) { - count++ - - await Model.updateOne({_id: entity._id}, {$set: {banId: uuid()}}) - - if (count % 1000 === 0) { - console.log(`Upload OK, ${count} / ${total} ${collection}`) - } - } - console.log(`END, ${count} / ${total} ${collection}`) -} - -async function getUuidBan(codeCommune: string): Promise { - const res = await fetch(`${process.env.BAN_API_URL}/api/district/cog/${codeCommune}`) - const {response} = await res.json() - return response[0].id -} - -async function updateBalManyUuid() { - let count = 0 - const BaseLocaleModel = model('bases_locales', basesLocalesSchema); - const cursor = BaseLocaleModel.find({banId: {$exists: false}}) - const total = await BaseLocaleModel.count({banId: {$exists: false}}) - const cacheUuid: Record = {} - - console.log(`START ${total} bases_locales`) - for await (const bal of cursor) { - try { - count++ - const banId = cacheUuid[bal.commune] ? cacheUuid[bal.commune] : (await getUuidBan(bal.commune)) - cacheUuid[bal.commune] = banId; - await BaseLocaleModel.updateOne({_id: bal._id}, {$set: {banId}}) - } catch (e) { - console.log(`ERROR COMMUNE ${bal.commune}`, e) - } - - if (count % 100 === 0) { - console.log(`Upload OK, ${count} / ${total} bases_locales`) - } - } - console.log(`END, ${count} / ${total} bases_locales`) -} - -async function run() { - // 4. Connect to MongoDB - await connect(`${process.env.MONGODB_URL}/${process.env.MONGODB_DBNAME}`); - await updateBalManyUuid(); - await updateManyUuid('voies') - await updateManyUuid('toponymes') - await updateManyUuid('numeros') - await disconnect() - process.exit(1); -} - -run().catch((err) => console.log(err)); diff --git a/nest-cli.json b/nest-cli.json index 30828132..ae5ee177 100644 --- a/nest-cli.json +++ b/nest-cli.json @@ -38,4 +38,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 2b513901..8a70a85d 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,8 @@ "build": "nest build api && nest build cron", "format": "prettier --write \"apps/**/*.ts\" \"libs/**/*.ts\"", "start": "node dist/apps/api/main.js", - "start:api": "node dist/apps/api/main", - "start:cron": "node dist/apps/cron/main", + "start:api": "node dist/apps/api/main.js", + "start:cron": "node dist/apps/cron/main.js", "start:debug": "nest start --debug --watch", "dev": "nest start --watch", "dev:api": "nest start api --watch", @@ -29,7 +29,8 @@ "test:api": "jest --config ./apps/api/test/jest-e2e.json", "test:cron": "jest --config ./apps/cron/test/jest-e2e.json", "update-cadastre": "node ./scripts/update-communes-cadastre.js", - "push-staging": "./scripts/push-staging.sh" + "push-staging": "./scripts/push-staging.sh", + "typeorm": "typeorm-ts-node-commonjs" }, "dependencies": { "@ban-team/adresses-util": "^0.9.0", @@ -40,15 +41,17 @@ "@mapbox/tilebelt": "^1.0.2", "@nestjs-modules/mailer": "^2.0.2", "@nestjs/axios": "^3.0.0", + "@nestjs/cache-manager": "^2.2.2", "@nestjs/cli": "^10.0.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", - "@nestjs/mongoose": "^10.0.1", "@nestjs/platform-express": "^10.3.7", "@nestjs/schedule": "^3.0.4", "@nestjs/serve-static": "^4.0.2", "@nestjs/swagger": "^7.1.13", + "@nestjs/typeorm": "^10.0.2", + "@testcontainers/postgresql": "^10.10.4", "@turf/turf": "^6.5.0", "@types/express": "^4.17.17", "@types/geojson-vt": "^3.2.3", @@ -57,6 +60,8 @@ "axios-mock-adapter": "^1.22.0", "bluebird": "^3.7.2", "body-parser": "^1.20.2", + "cache-manager": "^5.7.4", + "cache-manager-redis-store": "^3.0.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "compression": "^1.7.4", @@ -72,14 +77,15 @@ "into-stream": "^6.0.0", "lodash": "^4.17.21", "mongodb": "^6.1.0", - "mongoose": "^7.6.0", - "mongoose-lean-virtuals": "^0.9.1", + "mysql2": "^3.9.7", "nodemailer": "^6.9.9", + "pg": "^8.11.5", "pumpify": "^2.0.1", "randomcolor": "^0.6.2", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "ts-loader": "^9.4.3", + "typeorm": "^0.3.20", "uuid": "^9.0.1", "vt-pbf": "^3.1.3" }, @@ -98,7 +104,6 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", - "mongodb-memory-server": "^8.15.1", "nock": "^13.3.3", "nyc": "^15.1.0", "prettier": "^3.0.0", @@ -136,6 +141,6 @@ ] }, "engines": { - "node": ">= 16" + "node": ">= 18" } -} +} \ No newline at end of file diff --git a/typeorm.config.ts b/typeorm.config.ts new file mode 100644 index 00000000..eb6cca0b --- /dev/null +++ b/typeorm.config.ts @@ -0,0 +1,19 @@ +import * as dotenv from 'dotenv'; +import { DataSource } from 'typeorm'; +import { BaseLocale } from './libs/shared/src/entities/base_locale.entity'; +import { Voie } from './libs/shared/src/entities/voie.entity'; +import { Numero } from './libs/shared/src/entities/numero.entity'; +import { Toponyme } from './libs/shared/src/entities/toponyme.entity'; +import { Position } from './libs/shared/src/entities/position.entity'; + +dotenv.config(); + +export const AppDataSource = new DataSource({ + type: 'postgres', + url: process.env.POSTGRES_URL, + synchronize: false, + logging: true, + entities: [BaseLocale, Voie, Toponyme, Numero, Position], + migrationsRun: false, + migrations: ['**/migrations/*.ts'], +}); diff --git a/yarn.lock b/yarn.lock index dc09e9fd..eae152ee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -72,476 +72,6 @@ ora "5.4.1" rxjs "7.8.1" -"@aws-crypto/crc32@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa" - integrity sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/ie11-detection@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz#640ae66b4ec3395cee6a8e94ebcd9f80c24cd688" - integrity sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/sha256-browser@3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz#05f160138ab893f1c6ba5be57cfd108f05827766" - integrity sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ== - dependencies: - "@aws-crypto/ie11-detection" "^3.0.0" - "@aws-crypto/sha256-js" "^3.0.0" - "@aws-crypto/supports-web-crypto" "^3.0.0" - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-locate-window" "^3.0.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-crypto/sha256-js@3.0.0", "@aws-crypto/sha256-js@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz#f06b84d550d25521e60d2a0e2a90139341e007c2" - integrity sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ== - dependencies: - "@aws-crypto/util" "^3.0.0" - "@aws-sdk/types" "^3.222.0" - tslib "^1.11.1" - -"@aws-crypto/supports-web-crypto@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz#5d1bf825afa8072af2717c3e455f35cda0103ec2" - integrity sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg== - dependencies: - tslib "^1.11.1" - -"@aws-crypto/util@^3.0.0": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@aws-crypto/util/-/util-3.0.0.tgz#1c7ca90c29293f0883468ad48117937f0fe5bfb0" - integrity sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w== - dependencies: - "@aws-sdk/types" "^3.222.0" - "@aws-sdk/util-utf8-browser" "^3.0.0" - tslib "^1.11.1" - -"@aws-sdk/client-cognito-identity@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.423.0.tgz#7b9195d1bfed5032adb8f72c1bb86c95e9e0161f" - integrity sha512-9nyilMrihznN7Y6T/dVhbg4YGsdk7szzShoyoSGwofOg61ugobnHbBvh0tPPOQcHhlzXvD8LZdOQ6Kd4KvNp/A== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.423.0" - "@aws-sdk/credential-provider-node" "3.423.0" - "@aws-sdk/middleware-host-header" "3.418.0" - "@aws-sdk/middleware-logger" "3.418.0" - "@aws-sdk/middleware-recursion-detection" "3.418.0" - "@aws-sdk/middleware-signing" "3.418.0" - "@aws-sdk/middleware-user-agent" "3.418.0" - "@aws-sdk/region-config-resolver" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@aws-sdk/util-endpoints" "3.418.0" - "@aws-sdk/util-user-agent-browser" "3.418.0" - "@aws-sdk/util-user-agent-node" "3.418.0" - "@smithy/config-resolver" "^2.0.10" - "@smithy/fetch-http-handler" "^2.1.5" - "@smithy/hash-node" "^2.0.9" - "@smithy/invalid-dependency" "^2.0.9" - "@smithy/middleware-content-length" "^2.0.11" - "@smithy/middleware-endpoint" "^2.0.9" - "@smithy/middleware-retry" "^2.0.12" - "@smithy/middleware-serde" "^2.0.9" - "@smithy/middleware-stack" "^2.0.2" - "@smithy/node-config-provider" "^2.0.12" - "@smithy/node-http-handler" "^2.1.5" - "@smithy/protocol-http" "^3.0.5" - "@smithy/smithy-client" "^2.1.6" - "@smithy/types" "^2.3.3" - "@smithy/url-parser" "^2.0.9" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.10" - "@smithy/util-defaults-mode-node" "^2.0.12" - "@smithy/util-retry" "^2.0.2" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-sso@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.423.0.tgz#99db1f73419443cef544892337a1344aba10abc2" - integrity sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.418.0" - "@aws-sdk/middleware-logger" "3.418.0" - "@aws-sdk/middleware-recursion-detection" "3.418.0" - "@aws-sdk/middleware-user-agent" "3.418.0" - "@aws-sdk/region-config-resolver" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@aws-sdk/util-endpoints" "3.418.0" - "@aws-sdk/util-user-agent-browser" "3.418.0" - "@aws-sdk/util-user-agent-node" "3.418.0" - "@smithy/config-resolver" "^2.0.10" - "@smithy/fetch-http-handler" "^2.1.5" - "@smithy/hash-node" "^2.0.9" - "@smithy/invalid-dependency" "^2.0.9" - "@smithy/middleware-content-length" "^2.0.11" - "@smithy/middleware-endpoint" "^2.0.9" - "@smithy/middleware-retry" "^2.0.12" - "@smithy/middleware-serde" "^2.0.9" - "@smithy/middleware-stack" "^2.0.2" - "@smithy/node-config-provider" "^2.0.12" - "@smithy/node-http-handler" "^2.1.5" - "@smithy/protocol-http" "^3.0.5" - "@smithy/smithy-client" "^2.1.6" - "@smithy/types" "^2.3.3" - "@smithy/url-parser" "^2.0.9" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.10" - "@smithy/util-defaults-mode-node" "^2.0.12" - "@smithy/util-retry" "^2.0.2" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/client-sts@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.423.0.tgz#530a9cd58baef40cc6bbc6321c6ed93175e0e5b2" - integrity sha512-EcpkKu02QZbRX6dQE0u7a8RgWrn/5riz1qAlKd7rM8FZJpr/D6GGX8ZzWxjgp7pRUgfNvinTmIudDnyQY3v9Mg== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/credential-provider-node" "3.423.0" - "@aws-sdk/middleware-host-header" "3.418.0" - "@aws-sdk/middleware-logger" "3.418.0" - "@aws-sdk/middleware-recursion-detection" "3.418.0" - "@aws-sdk/middleware-sdk-sts" "3.418.0" - "@aws-sdk/middleware-signing" "3.418.0" - "@aws-sdk/middleware-user-agent" "3.418.0" - "@aws-sdk/region-config-resolver" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@aws-sdk/util-endpoints" "3.418.0" - "@aws-sdk/util-user-agent-browser" "3.418.0" - "@aws-sdk/util-user-agent-node" "3.418.0" - "@smithy/config-resolver" "^2.0.10" - "@smithy/fetch-http-handler" "^2.1.5" - "@smithy/hash-node" "^2.0.9" - "@smithy/invalid-dependency" "^2.0.9" - "@smithy/middleware-content-length" "^2.0.11" - "@smithy/middleware-endpoint" "^2.0.9" - "@smithy/middleware-retry" "^2.0.12" - "@smithy/middleware-serde" "^2.0.9" - "@smithy/middleware-stack" "^2.0.2" - "@smithy/node-config-provider" "^2.0.12" - "@smithy/node-http-handler" "^2.1.5" - "@smithy/protocol-http" "^3.0.5" - "@smithy/smithy-client" "^2.1.6" - "@smithy/types" "^2.3.3" - "@smithy/url-parser" "^2.0.9" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.10" - "@smithy/util-defaults-mode-node" "^2.0.12" - "@smithy/util-retry" "^2.0.2" - "@smithy/util-utf8" "^2.0.0" - fast-xml-parser "4.2.5" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-cognito-identity@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.423.0.tgz#4fdf0d8d72e811d857cb53d77c2352434a4151bb" - integrity sha512-FuuCOeUkAn3tZU2GUN3eUjs4AC88t5je4N5/NVbTaSN0e2FGf9PnN5nrwTKwaOGVLSe6/FvfudW01LZ/+PRQOQ== - dependencies: - "@aws-sdk/client-cognito-identity" "3.423.0" - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-env@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-env/-/credential-provider-env-3.418.0.tgz#7b14169350d9c14c9f656da06edf46f40a224ed2" - integrity sha512-e74sS+x63EZUBO+HaI8zor886YdtmULzwKdctsZp5/37Xho1CVUNtEC+fYa69nigBD9afoiH33I4JggaHgrekQ== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-http@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.423.0.tgz#b05a63c4bcdb18418f2894332714ddded72ab3ba" - integrity sha512-y/mutbiCU/4HGN/ChcNBhPaXo4pgg6lAcWyuMTSSfAR03hjoXe1cMwbPcUiEwzQrZ/+1yufLpZhmoiAWsgAkNw== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/fetch-http-handler" "^2.1.5" - "@smithy/node-http-handler" "^2.1.5" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-ini@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.423.0.tgz#62690a3c49b0223c3d239c8a3d2f2708e967a767" - integrity sha512-7CsFWz8g7dQmblp57XzzxMirO4ClowGZIOwAheBkmk6q1XHbllcHFnbh2kdPyQQ0+JmjDg6waztIc7dY7Ycfvw== - dependencies: - "@aws-sdk/credential-provider-env" "3.418.0" - "@aws-sdk/credential-provider-process" "3.418.0" - "@aws-sdk/credential-provider-sso" "3.423.0" - "@aws-sdk/credential-provider-web-identity" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-node@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.423.0.tgz#80d05ea89b1a4f245786171ae516c331aa315908" - integrity sha512-lygbGJJUnDpgo8OEqdoYd51BKkyBVQ1Catiua/m0aHvL+SCmVrHiYPQPawWYGxpH8X3DXdXa0nd0LkEaevrHRg== - dependencies: - "@aws-sdk/credential-provider-env" "3.418.0" - "@aws-sdk/credential-provider-ini" "3.423.0" - "@aws-sdk/credential-provider-process" "3.418.0" - "@aws-sdk/credential-provider-sso" "3.423.0" - "@aws-sdk/credential-provider-web-identity" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-process@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.418.0.tgz#1cb6d816bd471db3f9724715b007035ef18b5b2b" - integrity sha512-xPbdm2WKz1oH6pTkrJoUmr3OLuqvvcPYTQX0IIlc31tmDwDWPQjXGGFD/vwZGIZIkKaFpFxVMgAzfFScxox7dw== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-sso@3.423.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.423.0.tgz#a04f1715e5d9c75370d17ceac645379ca57cbb0b" - integrity sha512-zAH68IjRMmW22USbsCVQ5Q6AHqhmWABwLbZAMocSGMasddTGv/nkA/nUiVCJ/B4LI3P81FoPQVrG5JxNmkNH0w== - dependencies: - "@aws-sdk/client-sso" "3.423.0" - "@aws-sdk/token-providers" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-provider-web-identity@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.418.0.tgz#c2aed2a79bf193c1fef2b98391aaa9de7336aaaf" - integrity sha512-do7ang565n9p3dS1JdsQY01rUfRx8vkxQqz5M8OlcEHBNiCdi2PvSjNwcBdrv/FKkyIxZb0TImOfBSt40hVdxQ== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/credential-providers@^3.186.0": - version "3.423.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.423.0.tgz#a2a822c704ad4d4e4cf1e70163dab75affc4b23c" - integrity sha512-jsjIrnu+bVUz2lekcg9wxpPlO8jWd9q26MP/rRwdkm9LHqroICjZY7tIYqSJliVkeSyJHJ9pq/jNDceWhy6a0A== - dependencies: - "@aws-sdk/client-cognito-identity" "3.423.0" - "@aws-sdk/client-sso" "3.423.0" - "@aws-sdk/client-sts" "3.423.0" - "@aws-sdk/credential-provider-cognito-identity" "3.423.0" - "@aws-sdk/credential-provider-env" "3.418.0" - "@aws-sdk/credential-provider-http" "3.423.0" - "@aws-sdk/credential-provider-ini" "3.423.0" - "@aws-sdk/credential-provider-node" "3.423.0" - "@aws-sdk/credential-provider-process" "3.418.0" - "@aws-sdk/credential-provider-sso" "3.423.0" - "@aws-sdk/credential-provider-web-identity" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@smithy/credential-provider-imds" "^2.0.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/middleware-host-header@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-host-header/-/middleware-host-header-3.418.0.tgz#35d682e14f36c9d9d7464c7c1dd582bf6611436d" - integrity sha512-LrMTdzalkPw/1ujLCKPLwCGvPMCmT4P+vOZQRbSEVZPnlZk+Aj++aL/RaHou0jL4kJH3zl8iQepriBt4a7UvXQ== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/middleware-logger@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-logger/-/middleware-logger-3.418.0.tgz#08d7419f4220c36032a070a7dbb8bbf7e744a9ce" - integrity sha512-StKGmyPVfoO/wdNTtKemYwoJsqIl4l7oqarQY7VSf2Mp3mqaa+njLViHsQbirYpyqpgUEusOnuTlH5utxJ1NsQ== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/middleware-recursion-detection@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.418.0.tgz#2bb80d084f946846ad4907f3d6e0b451787d62b1" - integrity sha512-kKFrIQglBLUFPbHSDy1+bbe3Na2Kd70JSUC3QLMbUHmqipXN8KeXRfAj7vTv97zXl0WzG0buV++WcNwOm1rFjg== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/middleware-sdk-sts@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.418.0.tgz#f167f16050e055282ddd60226a2216c84873d464" - integrity sha512-cW8ijrCTP+mgihvcq4+TbhAcE/we5lFl4ydRqvTdtcSnYQAVQADg47rnTScQiFsPFEB3NKq7BGeyTJF9MKolPA== - dependencies: - "@aws-sdk/middleware-signing" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/middleware-signing@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.418.0.tgz#c7242b84069067bb671cb4191d412b59713a375e" - integrity sha512-onvs5KoYQE8OlOE740RxWBGtsUyVIgAo0CzRKOQO63ZEYqpL1Os+MS1CGzdNhvQnJgJruE1WW+Ix8fjN30zKPA== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/signature-v4" "^2.0.0" - "@smithy/types" "^2.3.3" - "@smithy/util-middleware" "^2.0.2" - tslib "^2.5.0" - -"@aws-sdk/middleware-user-agent@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.418.0.tgz#37426cf801332165fb170b1fd62dea8bb967a1ef" - integrity sha512-Jdcztg9Tal9SEAL0dKRrnpKrm6LFlWmAhvuwv0dQ7bNTJxIxyEFbpqdgy7mpQHsLVZgq1Aad/7gT/72c9igyZw== - dependencies: - "@aws-sdk/types" "3.418.0" - "@aws-sdk/util-endpoints" "3.418.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/region-config-resolver@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/region-config-resolver/-/region-config-resolver-3.418.0.tgz#53b99e4bd92f3369f51e9a76534b7d884db67526" - integrity sha512-lJRZ/9TjZU6yLz+mAwxJkcJZ6BmyYoIJVo1p5+BN//EFdEmC8/c0c9gXMRzfISV/mqWSttdtccpAyN4/goHTYA== - dependencies: - "@smithy/node-config-provider" "^2.0.12" - "@smithy/types" "^2.3.3" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.2" - tslib "^2.5.0" - -"@aws-sdk/token-providers@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.418.0.tgz#cbfac922df397e72daf6dbdd8c1e9a140df0aa0e" - integrity sha512-9P7Q0VN0hEzTngy3Sz5eya2qEOEf0Q8qf1vB3um0gE6ID6EVAdz/nc/DztfN32MFxk8FeVBrCP5vWdoOzmd72g== - dependencies: - "@aws-crypto/sha256-browser" "3.0.0" - "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/middleware-host-header" "3.418.0" - "@aws-sdk/middleware-logger" "3.418.0" - "@aws-sdk/middleware-recursion-detection" "3.418.0" - "@aws-sdk/middleware-user-agent" "3.418.0" - "@aws-sdk/types" "3.418.0" - "@aws-sdk/util-endpoints" "3.418.0" - "@aws-sdk/util-user-agent-browser" "3.418.0" - "@aws-sdk/util-user-agent-node" "3.418.0" - "@smithy/config-resolver" "^2.0.10" - "@smithy/fetch-http-handler" "^2.1.5" - "@smithy/hash-node" "^2.0.9" - "@smithy/invalid-dependency" "^2.0.9" - "@smithy/middleware-content-length" "^2.0.11" - "@smithy/middleware-endpoint" "^2.0.9" - "@smithy/middleware-retry" "^2.0.12" - "@smithy/middleware-serde" "^2.0.9" - "@smithy/middleware-stack" "^2.0.2" - "@smithy/node-config-provider" "^2.0.12" - "@smithy/node-http-handler" "^2.1.5" - "@smithy/property-provider" "^2.0.0" - "@smithy/protocol-http" "^3.0.5" - "@smithy/shared-ini-file-loader" "^2.0.6" - "@smithy/smithy-client" "^2.1.6" - "@smithy/types" "^2.3.3" - "@smithy/url-parser" "^2.0.9" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-body-length-browser" "^2.0.0" - "@smithy/util-body-length-node" "^2.1.0" - "@smithy/util-defaults-mode-browser" "^2.0.10" - "@smithy/util-defaults-mode-node" "^2.0.12" - "@smithy/util-retry" "^2.0.2" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@aws-sdk/types@3.418.0", "@aws-sdk/types@^3.222.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.418.0.tgz#c23213110b0c313d5546c810da032a441682f49a" - integrity sha512-y4PQSH+ulfFLY0+FYkaK4qbIaQI9IJNMO2xsxukW6/aNoApNymN1D2FSi2la8Qbp/iPjNDKsG8suNPm9NtsWXQ== - dependencies: - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/util-endpoints@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.418.0.tgz#462c976f054fe260562d4d2844152a04dd883fd7" - integrity sha512-sYSDwRTl7yE7LhHkPzemGzmIXFVHSsi3AQ1KeNEk84eBqxMHHcCc2kqklaBk2roXWe50QDgRMy1ikZUxvtzNHQ== - dependencies: - "@aws-sdk/types" "3.418.0" - tslib "^2.5.0" - -"@aws-sdk/util-locate-window@^3.0.0": - version "3.310.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-locate-window/-/util-locate-window-3.310.0.tgz#b071baf050301adee89051032bd4139bba32cc40" - integrity sha512-qo2t/vBTnoXpjKxlsC2e1gBrRm80M3bId27r0BRB2VniSSe7bL1mmzM+/HFtujm0iAxtPM+aLEflLJlJeDPg0w== - dependencies: - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-browser@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.418.0.tgz#dc76b8e7e5cae3f827d68cd4a3ee30c0d475a39c" - integrity sha512-c4p4mc0VV/jIeNH0lsXzhJ1MpWRLuboGtNEpqE4s1Vl9ck2amv9VdUUZUmHbg+bVxlMgRQ4nmiovA4qIrqGuyg== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/types" "^2.3.3" - bowser "^2.11.0" - tslib "^2.5.0" - -"@aws-sdk/util-user-agent-node@3.418.0": - version "3.418.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.418.0.tgz#7d5a1c82ce3265ff0f70b13d58d08593113ab99a" - integrity sha512-BXMskXFtg+dmzSCgmnWOffokxIbPr1lFqa1D9kvM3l3IFRiFGx2IyDg+8MAhq11aPDLvoa/BDuQ0Yqma5izOhg== - dependencies: - "@aws-sdk/types" "3.418.0" - "@smithy/node-config-provider" "^2.0.12" - "@smithy/types" "^2.3.3" - tslib "^2.5.0" - -"@aws-sdk/util-utf8-browser@^3.0.0": - version "3.259.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz#3275a6f5eb334f96ca76635b961d3c50259fd9ff" - integrity sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw== - dependencies: - tslib "^2.3.1" - "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.22.13": version "7.22.13" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e" @@ -908,6 +438,11 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@balena/dockerignore@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" + integrity sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q== + "@ban-team/adresses-util@^0.9.0": version "0.9.0" resolved "https://registry.yarnpkg.com/@ban-team/adresses-util/-/adresses-util-0.9.0.tgz#7b137c3507a1c543d0f91d60ceff478363a9d5ba" @@ -1409,6 +944,11 @@ resolved "https://registry.yarnpkg.com/@nestjs/axios/-/axios-3.0.0.tgz#a2e70b118e3058f3d4b9c3deacd496ec4e3ee69e" integrity sha512-ULdH03jDWkS5dy9X69XbUVbhC+0pVnrRcj7bIK/ytTZ76w7CgvTZDJqsIyisg3kNOiljRW/4NIjSf3j6YGvl+g== +"@nestjs/cache-manager@^2.2.2": + version "2.2.2" + resolved "https://registry.yarnpkg.com/@nestjs/cache-manager/-/cache-manager-2.2.2.tgz#4b0e7c4112c7b8c2a869d64f998aaf8a1bf0040d" + integrity sha512-+n7rpU1QABeW2WV17Dl1vZCG3vWjJU1MaamWgZvbGxYE9EeCM0lVLfw3z7acgDTNwOy+K68xuQPoIMxD0bhjlA== + "@nestjs/cli@^10.0.0": version "10.1.18" resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.1.18.tgz#7aa0099eea5fe60787eb822f9a8a6d47e22d0123" @@ -1473,11 +1013,6 @@ resolved "https://registry.yarnpkg.com/@nestjs/mapped-types/-/mapped-types-2.0.2.tgz#c8a090a8d22145b85ed977414c158534210f2e4f" integrity sha512-V0izw6tWs6fTp9+KiiPUbGHWALy563Frn8X6Bm87ANLRuE46iuBMD5acKBDP5lKL/75QFvrzSJT7HkCbB0jTpg== -"@nestjs/mongoose@^10.0.1": - version "10.0.1" - resolved "https://registry.yarnpkg.com/@nestjs/mongoose/-/mongoose-10.0.1.tgz#f55f4bf79d08f4a93834fe7fe27d42b5f19fccf3" - integrity sha512-woUViG28WKf/kRiv6NFXu4Oc0DvAPeX4+fT4coDVt2OqndnfJZTXwkkys23uVsBpKSIflRyjPvmcVBHQvcunZw== - "@nestjs/platform-express@^10.3.7": version "10.3.7" resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.7.tgz#ae3fc59609bdc0ffc5029a6e74d59a5d1e257eef" @@ -1533,6 +1068,13 @@ dependencies: tslib "2.6.2" +"@nestjs/typeorm@^10.0.2": + version "10.0.2" + resolved "https://registry.yarnpkg.com/@nestjs/typeorm/-/typeorm-10.0.2.tgz#25e3ec3c9a127b085c06fd7ea25f8690dba145c2" + integrity sha512-H738bJyydK4SQkRCTeh1aFBxoO1E9xdL/HaLGThwrqN95os5mEyAtK7BLADOS+vldP4jDZ2VQPLj4epWwRqCeQ== + dependencies: + uuid "9.0.1" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -1585,6 +1127,40 @@ picocolors "^1.0.0" tslib "^2.6.0" +"@redis/bloom@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@redis/bloom/-/bloom-1.2.0.tgz#d3fd6d3c0af3ef92f26767b56414a370c7b63b71" + integrity sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg== + +"@redis/client@1.5.17": + version "1.5.17" + resolved "https://registry.yarnpkg.com/@redis/client/-/client-1.5.17.tgz#44d179f2b5b542754d6f218bb352bac3ccf150eb" + integrity sha512-IPvU9A31qRCZ7lds/x+ksuK/UMndd0EASveAvCvEtFFKIZjZ+m/a4a0L7S28KEWoR5ka8526hlSghDo4Hrc2Hg== + dependencies: + cluster-key-slot "1.1.2" + generic-pool "3.9.0" + yallist "4.0.0" + +"@redis/graph@1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@redis/graph/-/graph-1.1.1.tgz#8c10df2df7f7d02741866751764031a957a170ea" + integrity sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw== + +"@redis/json@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@redis/json/-/json-1.0.6.tgz#b7a7725bbb907765d84c99d55eac3fcf772e180e" + integrity sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw== + +"@redis/search@1.1.6": + version "1.1.6" + resolved "https://registry.yarnpkg.com/@redis/search/-/search-1.1.6.tgz#33bcdd791d9ed88ab6910243a355d85a7fedf756" + integrity sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw== + +"@redis/time-series@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@redis/time-series/-/time-series-1.0.5.tgz#a6d70ef7a0e71e083ea09b967df0a0ed742bc6ad" + integrity sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg== + "@selderee/plugin-htmlparser2@^0.11.0": version "0.11.0" resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz#d5b5e29a7ba6d3958a1972c7be16f4b2c188c517" @@ -1617,354 +1193,10 @@ dependencies: "@sinonjs/commons" "^3.0.0" -"@smithy/abort-controller@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.0.10.tgz#a6d0d24973ac35b59cc450c34decd68485fbe2c0" - integrity sha512-xn7PnFD3m4rQIG00h1lPuDVnC2QMtTFhzRLX3y56KkgFaCysS7vpNevNBgmNUtmJ4eVFc+66Zucwo2KDLdicOg== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/config-resolver@^2.0.10", "@smithy/config-resolver@^2.0.11": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@smithy/config-resolver/-/config-resolver-2.0.11.tgz#20c4711b4e80f94527ee9e4e092cf024471bb09d" - integrity sha512-q97FnlUmbai1c4JlQJgLVBsvSxgV/7Nvg/JK76E1nRq/U5UM56Eqo3dn2fY7JibqgJLg4LPsGdwtIyqyOk35CQ== - dependencies: - "@smithy/node-config-provider" "^2.0.13" - "@smithy/types" "^2.3.4" - "@smithy/util-config-provider" "^2.0.0" - "@smithy/util-middleware" "^2.0.3" - tslib "^2.5.0" - -"@smithy/credential-provider-imds@^2.0.0", "@smithy/credential-provider-imds@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/credential-provider-imds/-/credential-provider-imds-2.0.13.tgz#9904912bc236d25d870add10b6eb138570bf5732" - integrity sha512-/xe3wNoC4j+BeTemH9t2gSKLBfyZmk8LXB2pQm/TOEYi+QhBgT+PSolNDfNAhrR68eggNE17uOimsrnwSkCt4w== - dependencies: - "@smithy/node-config-provider" "^2.0.13" - "@smithy/property-provider" "^2.0.11" - "@smithy/types" "^2.3.4" - "@smithy/url-parser" "^2.0.10" - tslib "^2.5.0" - -"@smithy/eventstream-codec@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/eventstream-codec/-/eventstream-codec-2.0.10.tgz#dbd46d0ed13abc61b1f08ab249f3097602752933" - integrity sha512-3SSDgX2nIsFwif6m+I4+ar4KDcZX463Noes8ekBgQHitULiWvaDZX8XqPaRQSQ4bl1vbeVXHklJfv66MnVO+lw== - dependencies: - "@aws-crypto/crc32" "3.0.0" - "@smithy/types" "^2.3.4" - "@smithy/util-hex-encoding" "^2.0.0" - tslib "^2.5.0" - -"@smithy/fetch-http-handler@^2.1.5", "@smithy/fetch-http-handler@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.1.tgz#a8abbd339c2c3d76456f4d16e65cf934727fc7ad" - integrity sha512-bXyM8PBAIKxVV++2ZSNBEposTDjFQ31XWOdHED+2hWMNvJHUoQqFbECg/uhcVOa6vHie2/UnzIZfXBSTpDBnEw== - dependencies: - "@smithy/protocol-http" "^3.0.6" - "@smithy/querystring-builder" "^2.0.10" - "@smithy/types" "^2.3.4" - "@smithy/util-base64" "^2.0.0" - tslib "^2.5.0" - -"@smithy/hash-node@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/hash-node/-/hash-node-2.0.10.tgz#af13889a008880bdc30278b148e0e0b2a6e2d243" - integrity sha512-jSTf6uzPk/Vf+8aQ7tVXeHfjxe9wRXSCqIZcBymSDTf7/YrVxniBdpyN74iI8ZUOx/Pyagc81OK5FROLaEjbXQ== - dependencies: - "@smithy/types" "^2.3.4" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/invalid-dependency@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/invalid-dependency/-/invalid-dependency-2.0.10.tgz#b708e7cfc35214ce664db6aa67465567b97ffd36" - integrity sha512-zw9p/zsmJ2cFcW4KMz3CJoznlbRvEA6HG2mvEaX5eAca5dq4VGI2MwPDTfmteC/GsnURS4ogoMQ0p6aHM2SDVQ== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/is-array-buffer@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz#8fa9b8040651e7ba0b2f6106e636a91354ff7d34" - integrity sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug== - dependencies: - tslib "^2.5.0" - -"@smithy/middleware-content-length@^2.0.11": - version "2.0.12" - resolved "https://registry.yarnpkg.com/@smithy/middleware-content-length/-/middleware-content-length-2.0.12.tgz#e6f874f5eef880561f774a4376b73f04b97efc53" - integrity sha512-QRhJTo5TjG7oF7np6yY4ZO9GDKFVzU/GtcqUqyEa96bLHE3yZHgNmsolOQ97pfxPHmFhH4vDP//PdpAIN3uI1Q== - dependencies: - "@smithy/protocol-http" "^3.0.6" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/middleware-endpoint@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.0.10.tgz#c11d9f75549116453eea0e812e17ec7917ce5bb1" - integrity sha512-O6m4puZc16xfenotZUHL4bRlMrwf4gTp+0I5l954M5KNd3dOK18P+FA/IIUgnXF/dX6hlCUcJkBp7nAzwrePKA== - dependencies: - "@smithy/middleware-serde" "^2.0.10" - "@smithy/types" "^2.3.4" - "@smithy/url-parser" "^2.0.10" - "@smithy/util-middleware" "^2.0.3" - tslib "^2.5.0" - -"@smithy/middleware-retry@^2.0.12": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.0.13.tgz#ef33b1511a4b01a77e54567165b78e6d0c266e88" - integrity sha512-zuOva8xgWC7KYG8rEXyWIcZv2GWszO83DCTU6IKcf/FKu6OBmSE+EYv3EUcCGY+GfiwCX0EyJExC9Lpq9b0w5Q== - dependencies: - "@smithy/node-config-provider" "^2.0.13" - "@smithy/protocol-http" "^3.0.6" - "@smithy/service-error-classification" "^2.0.3" - "@smithy/types" "^2.3.4" - "@smithy/util-middleware" "^2.0.3" - "@smithy/util-retry" "^2.0.3" - tslib "^2.5.0" - uuid "^8.3.2" - -"@smithy/middleware-serde@^2.0.10", "@smithy/middleware-serde@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.0.10.tgz#4b0e5f838c7d7621cabf7cfdd6cec4c7f4d52a3f" - integrity sha512-+A0AFqs768256H/BhVEsBF6HijFbVyAwYRVXY/izJFkTalVWJOp4JA0YdY0dpXQd+AlW0tzs+nMQCE1Ew+DcgQ== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/middleware-stack@^2.0.2", "@smithy/middleware-stack@^2.0.4": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.0.4.tgz#cf199dd4d6eb3a3562e6757804faa91165693395" - integrity sha512-MW0KNKfh8ZGLagMZnxcLJWPNXoKqW6XV/st5NnCBmmA2e2JhrUjU0AJ5Ca/yjTyNEKs3xH7AQDwp1YmmpEpmQQ== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/node-config-provider@^2.0.12", "@smithy/node-config-provider@^2.0.13": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.0.13.tgz#26c95cebbb8bf9ef5dd703ab4e00ff80de34e15f" - integrity sha512-pPpLqYuJcOq1sj1EGu+DoZK47DUS4gepqSTNgRezmrjnzNlSU2/Dcc9Ebzs+WZ0Z5vXKazuE+k+NksFLo07/AA== - dependencies: - "@smithy/property-provider" "^2.0.11" - "@smithy/shared-ini-file-loader" "^2.0.12" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/node-http-handler@^2.1.5", "@smithy/node-http-handler@^2.1.6": - version "2.1.6" - resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.1.6.tgz#c2913363bbf28f315461bd54ef9a5394f1686776" - integrity sha512-NspvD3aCwiUNtoSTcVHz0RZz1tQ/SaRIe1KPF+r0mAdCZ9eWuhIeJT8ZNPYa1ITn7/Lgg64IyFjqPynZ8KnYQw== - dependencies: - "@smithy/abort-controller" "^2.0.10" - "@smithy/protocol-http" "^3.0.6" - "@smithy/querystring-builder" "^2.0.10" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/property-provider@^2.0.0", "@smithy/property-provider@^2.0.11": - version "2.0.11" - resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.0.11.tgz#c6e03e4f6f886851339c3dfaf8cd8ae3b2878fa3" - integrity sha512-kzuOadu6XvrnlF1iXofpKXYmo4oe19st9/DE8f5gHNaFepb4eTkR8gD8BSdTnNnv7lxfv6uOwZPg4VS6hemX1w== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/protocol-http@^3.0.5", "@smithy/protocol-http@^3.0.6": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.0.6.tgz#c33c128cc0f7096bf4fcdcc6d14d156ba5cd5b7c" - integrity sha512-F0jAZzwznMmHaggiZgc7YoS08eGpmLvhVktY/Taz6+OAOHfyIqWSDNgFqYR+WHW9z5fp2XvY4mEUrQgYMQ71jw== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/querystring-builder@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.0.10.tgz#b06aa958b6ec1c56254d8cc41a19882625fd1c05" - integrity sha512-uujJGp8jzrrU1UHme8sUKEbawQTcTmUWsh8rbGXYD/lMwNLQ+9jQ9dMDWbbH9Hpoa9RER1BeL/38WzGrbpob2w== - dependencies: - "@smithy/types" "^2.3.4" - "@smithy/util-uri-escape" "^2.0.0" - tslib "^2.5.0" - -"@smithy/querystring-parser@^2.0.10": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.0.10.tgz#074d770a37feafb0d550094dd8463bdff58515f5" - integrity sha512-WSD4EU60Q8scacT5PIpx4Bahn6nWpt+MiYLcBkFt6fOj7AssrNeaNIU2Z0g40ftVmrwLcEOIKGX92ynbVDb3ZA== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/service-error-classification@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@smithy/service-error-classification/-/service-error-classification-2.0.3.tgz#4c7de61d06db5f72437557d429bd74c74988b19e" - integrity sha512-b+m4QCHXb7oKAkM/jHwHrl5gpqhFoMTHF643L0/vAEkegrcUWyh1UjyoHttuHcP5FnHVVy4EtpPtLkEYD+xMFw== - dependencies: - "@smithy/types" "^2.3.4" - -"@smithy/shared-ini-file-loader@^2.0.12", "@smithy/shared-ini-file-loader@^2.0.6": - version "2.0.12" - resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.0.12.tgz#30c8a7a36f49734fde2f052bfaeaaf40c1980b55" - integrity sha512-umi0wc4UBGYullAgYNUVfGLgVpxQyES47cnomTqzCKeKO5oudO4hyDNj+wzrOjqDFwK2nWYGVgS8Y0JgGietrw== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/signature-v4@^2.0.0": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.0.10.tgz#89161b3f59071b77713cdf06f98b2e6780580742" - integrity sha512-S6gcP4IXfO/VMswovrhxPpqvQvMal7ZRjM4NvblHSPpE5aNBYx67UkHFF3kg0hR3tJKqNpBGbxwq0gzpdHKLRA== - dependencies: - "@smithy/eventstream-codec" "^2.0.10" - "@smithy/is-array-buffer" "^2.0.0" - "@smithy/types" "^2.3.4" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-middleware" "^2.0.3" - "@smithy/util-uri-escape" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/smithy-client@^2.1.6", "@smithy/smithy-client@^2.1.9": - version "2.1.9" - resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.1.9.tgz#5a0a185947ae4e66d12d2a6135628dd2fc36924c" - integrity sha512-HTicQSn/lOcXKJT+DKJ4YMu51S6PzbWsO8Z6Pwueo30mSoFKXg5P0BDkg2VCDqCVR0mtddM/F6hKhjW6YAV/yg== - dependencies: - "@smithy/middleware-stack" "^2.0.4" - "@smithy/types" "^2.3.4" - "@smithy/util-stream" "^2.0.14" - tslib "^2.5.0" - -"@smithy/types@^2.3.3", "@smithy/types@^2.3.4": - version "2.3.4" - resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.3.4.tgz#3b9bc15000af0a0b1f4fda741f78c1580ba15e92" - integrity sha512-D7xlM9FOMFyFw7YnMXn9dK2KuN6+JhnrZwVt1fWaIu8hCk5CigysweeIT/H/nCo4YV+s8/oqUdLfexbkPZtvqw== - dependencies: - tslib "^2.5.0" - -"@smithy/url-parser@^2.0.10", "@smithy/url-parser@^2.0.9": - version "2.0.10" - resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.0.10.tgz#3261a463b87901d7686f66a9f26efb9f57d8d555" - integrity sha512-4TXQFGjHcqru8aH5VRB4dSnOFKCYNX6SR1Do6fwxZ+ExT2onLsh2W77cHpks7ma26W5jv6rI1u7d0+KX9F0aOw== - dependencies: - "@smithy/querystring-parser" "^2.0.10" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/util-base64@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.0.0.tgz#1beeabfb155471d1d41c8d0603be1351f883c444" - integrity sha512-Zb1E4xx+m5Lud8bbeYi5FkcMJMnn+1WUnJF3qD7rAdXpaL7UjkFQLdmW5fHadoKbdHpwH9vSR8EyTJFHJs++tA== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-body-length-browser@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz#5447853003b4c73da3bc5f3c5e82c21d592d1650" - integrity sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg== - dependencies: - tslib "^2.5.0" - -"@smithy/util-body-length-node@^2.1.0": - version "2.1.0" - resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz#313a5f7c5017947baf5fa018bfc22628904bbcfa" - integrity sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw== - dependencies: - tslib "^2.5.0" - -"@smithy/util-buffer-from@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz#7eb75d72288b6b3001bc5f75b48b711513091deb" - integrity sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw== - dependencies: - "@smithy/is-array-buffer" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-config-provider@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz#4dd6a793605559d94267312fd06d0f58784b4c38" - integrity sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg== - dependencies: - tslib "^2.5.0" - -"@smithy/util-defaults-mode-browser@^2.0.10": - version "2.0.13" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.13.tgz#8136955f1bef6e66cb8a8702693e7685dcd33e26" - integrity sha512-UmmOdUzaQjqdsl1EjbpEaQxM0VDFqTj6zDuI26/hXN7L/a1k1koTwkYpogHMvunDX3fjrQusg5gv1Td4UsGyog== - dependencies: - "@smithy/property-provider" "^2.0.11" - "@smithy/smithy-client" "^2.1.9" - "@smithy/types" "^2.3.4" - bowser "^2.11.0" - tslib "^2.5.0" - -"@smithy/util-defaults-mode-node@^2.0.12": - version "2.0.15" - resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.15.tgz#24f7b9de978206909ced7b522f24e7f450187372" - integrity sha512-g6J7MHAibVPMTlXyH3mL+Iet4lMJKFVhsOhJmn+IKG81uy9m42CkRSDlwdQSJAcprLQBIaOPdFxNXQvrg2w1Uw== - dependencies: - "@smithy/config-resolver" "^2.0.11" - "@smithy/credential-provider-imds" "^2.0.13" - "@smithy/node-config-provider" "^2.0.13" - "@smithy/property-provider" "^2.0.11" - "@smithy/smithy-client" "^2.1.9" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/util-hex-encoding@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz#0aa3515acd2b005c6d55675e377080a7c513b59e" - integrity sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA== - dependencies: - tslib "^2.5.0" - -"@smithy/util-middleware@^2.0.2", "@smithy/util-middleware@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.0.3.tgz#478cbf957eaffa36aed624350be342bbf15d3c42" - integrity sha512-+FOCFYOxd2HO7v/0hkFSETKf7FYQWa08wh/x/4KUeoVBnLR4juw8Qi+TTqZI6E2h5LkzD9uOaxC9lAjrpVzaaA== - dependencies: - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/util-retry@^2.0.2", "@smithy/util-retry@^2.0.3": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@smithy/util-retry/-/util-retry-2.0.3.tgz#a053855ddb51800bd679da03454cf626bc440918" - integrity sha512-gw+czMnj82i+EaH7NL7XKkfX/ZKrCS2DIWwJFPKs76bMgkhf0y1C94Lybn7f8GkBI9lfIOUdPYtzm19zQOC8sw== - dependencies: - "@smithy/service-error-classification" "^2.0.3" - "@smithy/types" "^2.3.4" - tslib "^2.5.0" - -"@smithy/util-stream@^2.0.14": - version "2.0.14" - resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.0.14.tgz#3fdd934e2bced80331dcaff18aefbcfe39ebf3cd" - integrity sha512-XjvlDYe+9DieXhLf7p+EgkXwFtl34kHZcWfHnc5KaILbhyVfDLWuqKTFx6WwCFqb01iFIig8trGwExRIqqkBYg== - dependencies: - "@smithy/fetch-http-handler" "^2.2.1" - "@smithy/node-http-handler" "^2.1.6" - "@smithy/types" "^2.3.4" - "@smithy/util-base64" "^2.0.0" - "@smithy/util-buffer-from" "^2.0.0" - "@smithy/util-hex-encoding" "^2.0.0" - "@smithy/util-utf8" "^2.0.0" - tslib "^2.5.0" - -"@smithy/util-uri-escape@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz#19955b1a0f517a87ae77ac729e0e411963dfda95" - integrity sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw== - dependencies: - tslib "^2.5.0" - -"@smithy/util-utf8@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.0.0.tgz#b4da87566ea7757435e153799df9da717262ad42" - integrity sha512-rctU1VkziY84n5OXe3bPNpKR001ZCME2JCaBBFgtiM2hfKbHFudc/BkMuPab8hRbLd0j3vbnBTTZ1igBf0wgiQ== - dependencies: - "@smithy/util-buffer-from" "^2.0.0" - tslib "^2.5.0" +"@sqltools/formatter@^1.2.5": + version "1.2.5" + resolved "https://registry.yarnpkg.com/@sqltools/formatter/-/formatter-1.2.5.tgz#3abc203c79b8c3e90fd6c156a0c62d5403520e12" + integrity sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw== "@szmarczak/http-timer@^4.0.5": version "4.0.6" @@ -1973,6 +1205,13 @@ dependencies: defer-to-connect "^2.0.0" +"@testcontainers/postgresql@^10.10.4": + version "10.10.4" + resolved "https://registry.yarnpkg.com/@testcontainers/postgresql/-/postgresql-10.10.4.tgz#00ba25df42ad196532fccfa5e3657032e7aff57d" + integrity sha512-yGRW3IYXAnv91ncOyhf6XVSMbKqfKQzFbFdaSu67agtXwIUYvGE+RFXa/SMZ6oNKHNWgMGKXB9Paj7+md79+VQ== + dependencies: + testcontainers "^10.10.4" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -3200,6 +2439,23 @@ dependencies: "@types/node" "*" +"@types/docker-modem@*": + version "3.0.6" + resolved "https://registry.yarnpkg.com/@types/docker-modem/-/docker-modem-3.0.6.tgz#1f9262fcf85425b158ca725699a03eb23cddbf87" + integrity sha512-yKpAGEuKRSS8wwx0joknWxsmLha78wNMe9R2S3UNsVOkZded8UqOrV8KoeDXoXsjndxwyF3eIhyClGbO1SEhEg== + dependencies: + "@types/node" "*" + "@types/ssh2" "*" + +"@types/dockerode@^3.3.29": + version "3.3.30" + resolved "https://registry.yarnpkg.com/@types/dockerode/-/dockerode-3.3.30.tgz#df766a75a9cf1fc6d3cdde7d5329f749fd59d457" + integrity sha512-MuxQQ7yQ+VzLbrIV2D8K2YqOYBd5Mz4yGbauEipFcn894bLnGwewNKXGKLlb7wpXTG4dn+13lk51qPXmKJ2+YA== + dependencies: + "@types/docker-modem" "*" + "@types/node" "*" + "@types/ssh2" "*" + "@types/ejs@^3.1.5": version "3.1.5" resolved "https://registry.yarnpkg.com/@types/ejs/-/ejs-3.1.5.tgz#49d738257cc73bafe45c13cb8ff240683b4d5117" @@ -3368,6 +2624,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.8.0.tgz#10ddf0119cf20028781c06d7115562934e53f745" integrity sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ== +"@types/node@^18.11.18": + version "18.19.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.41.tgz#27695cf2cac63f22c202b9217c0bcf3fb192a2f0" + integrity sha512-LX84pRJ+evD2e2nrgYCHObGWkiQJ1mL+meAgbvnwk/US6vmMY7S2ygBTGV2Jw91s9vUsLSXeDEkUHZIJGLrhsg== + dependencies: + undici-types "~5.26.4" + "@types/nodemailer@^6.4.15": version "6.4.15" resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.15.tgz#494be695e11c438f7f5df738fb4ab740312a6ed2" @@ -3424,6 +2687,28 @@ "@types/mime" "*" "@types/node" "*" +"@types/ssh2-streams@*": + version "0.1.12" + resolved "https://registry.yarnpkg.com/@types/ssh2-streams/-/ssh2-streams-0.1.12.tgz#e68795ba2bf01c76b93f9c9809e1f42f0eaaec5f" + integrity sha512-Sy8tpEmCce4Tq0oSOYdfqaBpA3hDM8SoxoFh5vzFsu2oL+znzGz8oVWW7xb4K920yYMUY+PIG31qZnFMfPWNCg== + dependencies: + "@types/node" "*" + +"@types/ssh2@*": + version "1.15.0" + resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-1.15.0.tgz#ef698a2fe05696d898e0f9398384ad370cd35317" + integrity sha512-YcT8jP5F8NzWeevWvcyrrLB3zcneVjzYY9ZDSMAMboI+2zR1qYWFhwsyOFVzT7Jorn67vqxC0FRiw8YyG9P1ww== + dependencies: + "@types/node" "^18.11.18" + +"@types/ssh2@^0.5.48": + version "0.5.52" + resolved "https://registry.yarnpkg.com/@types/ssh2/-/ssh2-0.5.52.tgz#9dbd8084e2a976e551d5e5e70b978ed8b5965741" + integrity sha512-lbLLlXxdCZOSJMCInKH2+9V/77ET2J6NPQHpFI0kda61Dd1KglJs+fPQBchizmzYSOJBgdTajhPqBO1xxLywvg== + dependencies: + "@types/node" "*" + "@types/ssh2-streams" "*" + "@types/stack-utils@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" @@ -3733,13 +3018,6 @@ acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -3842,6 +3120,11 @@ ansi-styles@^6.0.0, ansi-styles@^6.1.0, ansi-styles@^6.2.1: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +any-promise@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A== + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -3850,6 +3133,11 @@ anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-root-path@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-3.1.0.tgz#5971a2fc12ba170369a7a1ef018c71e6e47c2e86" + integrity sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA== + append-field@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" @@ -3862,6 +3150,51 @@ append-transform@^2.0.0: dependencies: default-require-extensions "^3.0.0" +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +archiver@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.4" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.1.2" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" @@ -3884,14 +3217,6 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== -array-buffer-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead" - integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A== - dependencies: - call-bind "^1.0.2" - is-array-buffer "^3.0.1" - array-find-index@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" @@ -3912,27 +3237,6 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== -array.prototype.flat@1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" - integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== - dependencies: - define-properties "^1.1.3" - es-abstract "^1.17.0-next.1" - -arraybuffer.prototype.slice@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz#98bd561953e3e74bb34938e77647179dfe6e9f12" - integrity sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw== - dependencies: - array-buffer-byte-length "^1.0.0" - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - get-intrinsic "^1.2.1" - is-array-buffer "^3.0.2" - is-shared-array-buffer "^1.0.2" - arrgv@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/arrgv/-/arrgv-1.0.2.tgz#025ed55a6a433cad9b604f8112fc4292715a6ec0" @@ -3948,19 +3252,24 @@ asap@^2.0.0, asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA== +asn1@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d" + integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== + dependencies: + safer-buffer "~2.1.0" + assert-never@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/assert-never/-/assert-never-1.2.1.tgz#11f0e363bf146205fb08193b5c7b90f4d1cf44fe" integrity sha512-TaTivMB6pYI1kXwrFlEhLeGfOqoDNdTxjCdwRfFFkEA30Eu+k48W34nlok2EYWJfFFzqaEmichdNM7th6M5HNw== -async-mutex@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/async-mutex/-/async-mutex-0.3.2.tgz#1485eda5bda1b0ec7c8df1ac2e815757ad1831df" - integrity sha512-HuTK7E7MT7jZEh1P9GtRW9+aTWiDWWi9InbZ5hjxrnRa39KS4BW04+xLBhYNS2aXhHUIKZSw3gj4Pn1pj+qGAA== - dependencies: - tslib "^2.3.1" +async-lock@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/async-lock/-/async-lock-1.4.1.tgz#56b8718915a9b68b10fce2f2a9a3dddf765ef53f" + integrity sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ== -async@^3.2.3: +async@^3.2.3, async@^3.2.4: version "3.2.5" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== @@ -4019,11 +3328,6 @@ ava@^5.3.1: write-file-atomic "^5.0.1" yargs "^17.7.2" -available-typed-arrays@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" - integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== - axios-mock-adapter@^1.22.0: version "1.22.0" resolved "https://registry.yarnpkg.com/axios-mock-adapter/-/axios-mock-adapter-1.22.0.tgz#0f3e6be0fc9b55baab06f2d49c0b71157e7c053d" @@ -4041,6 +3345,11 @@ axios@^1.6.0: form-data "^4.0.0" proxy-from-env "^1.1.0" +b4a@^1.6.4: + version "1.6.6" + resolved "https://registry.yarnpkg.com/b4a/-/b4a-1.6.6.tgz#a4cc349a3851987c3c4ac2d7785c18744f6da9ba" + integrity sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg== + babel-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.7.0.tgz#f4369919225b684c56085998ac63dbd05be020d5" @@ -4113,11 +3422,51 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +bare-events@^2.0.0, bare-events@^2.2.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/bare-events/-/bare-events-2.4.2.tgz#3140cca7a0e11d49b3edc5041ab560659fd8e1f8" + integrity sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q== + +bare-fs@^2.1.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/bare-fs/-/bare-fs-2.3.1.tgz#cdbd63dac7a552dfb2b87d18c822298d1efd213d" + integrity sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA== + dependencies: + bare-events "^2.0.0" + bare-path "^2.0.0" + bare-stream "^2.0.0" + +bare-os@^2.1.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-2.4.0.tgz#5de5e3ba7704f459c9656629edca7cc736e06608" + integrity sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg== + +bare-path@^2.0.0, bare-path@^2.1.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/bare-path/-/bare-path-2.1.3.tgz#594104c829ef660e43b5589ec8daef7df6cedb3e" + integrity sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA== + dependencies: + bare-os "^2.1.0" + +bare-stream@^2.0.0: + version "2.1.3" + resolved "https://registry.yarnpkg.com/bare-stream/-/bare-stream-2.1.3.tgz#070b69919963a437cc9e20554ede079ce0a129b2" + integrity sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ== + dependencies: + streamx "^2.18.0" + base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bcrypt-pbkdf@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== + dependencies: + tweetnacl "^0.14.3" + big-integer@^1.6.44: version "1.6.51" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686" @@ -4175,11 +3524,6 @@ boolbase@^1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== -bowser@^2.11.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== - bplist-parser@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/bplist-parser/-/bplist-parser-0.2.0.tgz#43a9d183e5bf9d545200ceac3e712f79ebbe8d0e" @@ -4233,24 +3577,12 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" -bson@^4.7.2: - version "4.7.2" - resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.2.tgz#320f4ad0eaf5312dd9b45dc369cc48945e2a5f2e" - integrity sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ== - dependencies: - buffer "^5.6.0" - -bson@^5.4.0, bson@^5.5.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/bson/-/bson-5.5.0.tgz#a419cc69f368d2def3b8b22ea03ed1c9be40e53f" - integrity sha512-B+QB4YmDx9RStKv8LLSl/aVIEV3nYJc3cJNNTK2Cd1TL+7P+cNpw9mAPeCgc5K+j01Dv6sxUzcITXDx7ZU3F0w== - bson@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/bson/-/bson-6.1.0.tgz#ea7c98b90540e1632173da6b1f70187827e6ae8c" integrity sha512-yiQ3KxvpVoRpx1oD1uPz4Jit9tAVTJgjdmjDKtUErkOoL9VNoF8Dd58qtAOL5E40exx2jvAT9sqdRSK/r+SHlA== -buffer-crc32@~0.2.3: +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== @@ -4260,7 +3592,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^5.5.0, buffer@^5.6.0: +buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -4268,6 +3600,19 @@ buffer@^5.5.0, buffer@^5.6.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + +buildcheck@~0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/buildcheck/-/buildcheck-0.0.6.tgz#89aa6e417cfd1e2196e3f8fe915eb709d2fe4238" + integrity sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A== + bundle-name@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-3.0.0.tgz#ba59bcc9ac785fb67ccdbf104a2bf60c099f0e1a" @@ -4282,6 +3627,11 @@ busboy@^1.0.0: dependencies: streamsearch "^1.1.0" +byline@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" + integrity sha512-s6webAy+R4SR8XVuJWt2V2rGvhnrhxN+9S15GNuTK3wKPOXFF6RNc+8ug2XhH+2s4f+uudG4kUVYmYOQWL2g0Q== + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -4292,6 +3642,23 @@ bytes@3.1.2: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== +cache-manager-redis-store@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/cache-manager-redis-store/-/cache-manager-redis-store-3.0.1.tgz#8eeb211212763d04cef4058666182d624f714299" + integrity sha512-o560kw+dFqusC9lQJhcm6L2F2fMKobJ5af+FoR2PdnMVdpQ3f3Bz6qzvObTGyvoazQJxjQNWgMQeChP4vRTuXQ== + dependencies: + redis "^4.3.1" + +cache-manager@^5.7.4: + version "5.7.4" + resolved "https://registry.yarnpkg.com/cache-manager/-/cache-manager-5.7.4.tgz#ed4d513ad4c6d29f2cfd14d6305e6277c830cce1" + integrity sha512-7B29xK1D8hOVdrP0SAy2DGJ/QZxy2TqxS8s2drlLGYI/xOTSJmXfatks7aKKNHvXN6SnKnPtYCi0T82lslB3Fw== + dependencies: + eventemitter3 "^5.0.1" + lodash.clonedeep "^4.5.0" + lru-cache "^10.2.2" + promise-coalesce "^1.1.2" + cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -4328,15 +3695,6 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" -call-bind@^1.0.4, call-bind@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" - integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== - dependencies: - function-bind "^1.1.2" - get-intrinsic "^1.2.1" - set-function-length "^1.1.1" - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -4360,7 +3718,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.2.0: version "6.3.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -4484,6 +3842,11 @@ chokidar@^3.0.0: optionalDependencies: fsevents "~2.3.2" +chownr@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" + integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== + chrome-trace-event@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" @@ -4554,6 +3917,18 @@ cli-cursor@^3.1.0: dependencies: restore-cursor "^3.1.0" +cli-highlight@^2.1.11: + version "2.1.11" + resolved "https://registry.yarnpkg.com/cli-highlight/-/cli-highlight-2.1.11.tgz#49736fa452f0aaf4fae580e30acb26828d2dc1bf" + integrity sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg== + dependencies: + chalk "^4.0.0" + highlight.js "^10.7.1" + mz "^2.4.0" + parse5 "^5.1.1" + parse5-htmlparser2-tree-adapter "^6.0.0" + yargs "^16.0.0" + cli-spinners@^2.5.0: version "2.9.1" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.1.tgz#9c0b9dad69a6d47cbb4333c14319b060ed395a35" @@ -4590,6 +3965,15 @@ cliui@^6.0.0: strip-ansi "^6.0.0" wrap-ansi "^6.2.0" +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -4611,6 +3995,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +cluster-key-slot@1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -4705,6 +4094,16 @@ component-emitter@^1.3.0: resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -4851,6 +4250,27 @@ cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cpu-features@~0.0.9: + version "0.0.10" + resolved "https://registry.yarnpkg.com/cpu-features/-/cpu-features-0.0.10.tgz#9aae536db2710c7254d7ed67cb3cbc7d29ad79c5" + integrity sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA== + dependencies: + buildcheck "~0.0.6" + nan "^2.19.0" + +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -4960,6 +4380,11 @@ date-time@^3.1.0: dependencies: time-zone "^1.0.0" +dayjs@^1.11.9: + version "1.11.11" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" + integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -4967,13 +4392,20 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.x, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -5064,21 +4496,12 @@ define-data-property@^1.0.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -define-data-property@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3" - integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ== - dependencies: - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - define-lazy-prop@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== -define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: +define-properties@^1.1.3, define-properties@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -5092,6 +4515,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + density-clustering@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/density-clustering/-/density-clustering-1.3.0.tgz#dc9f59c8f0ab97e1624ac64930fd3194817dcac5" @@ -5155,6 +4583,32 @@ display-notification@2.0.0: escape-string-applescript "^1.0.0" run-applescript "^3.0.0" +docker-compose@^0.24.8: + version "0.24.8" + resolved "https://registry.yarnpkg.com/docker-compose/-/docker-compose-0.24.8.tgz#6c125e6b9e04cf68ced47e2596ef2bb93ee9694e" + integrity sha512-plizRs/Vf15H+GCVxq2EUvyPK7ei9b/cVesHvjnX4xaXjM9spHe2Ytq0BitndFgvTJ3E3NljPNUEl7BAN43iZw== + dependencies: + yaml "^2.2.2" + +docker-modem@^3.0.0: + version "3.0.8" + resolved "https://registry.yarnpkg.com/docker-modem/-/docker-modem-3.0.8.tgz#ef62c8bdff6e8a7d12f0160988c295ea8705e77a" + integrity sha512-f0ReSURdM3pcKPNS30mxOHSbaFLcknGmQjwSfmbcdOw1XWKXVhukM3NJHhr7NpY9BIyyWQb0EBo3KQvvuU5egQ== + dependencies: + debug "^4.1.1" + readable-stream "^3.5.0" + split-ca "^1.0.1" + ssh2 "^1.11.0" + +dockerode@^3.3.5: + version "3.3.5" + resolved "https://registry.yarnpkg.com/dockerode/-/dockerode-3.3.5.tgz#7ae3f40f2bec53ae5e9a741ce655fff459745629" + integrity sha512-/0YNa3ZDNeLr/tSckmD69+Gq+qVNhvKfAHNeZJBnp7EOP6RGKV8ORrJHkUn20So5wU+xxT7+1n5u8PjHbfjbSA== + dependencies: + "@balena/dockerignore" "^1.0.2" + docker-modem "^3.0.0" + tar-fs "~2.0.1" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -5239,6 +4693,11 @@ dotenv@16.3.1, dotenv@^16.3.1: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e" integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ== +dotenv@^16.0.3: + version "16.4.5" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" + integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== + duplexify@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" @@ -5353,74 +4812,11 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.0-next.1, es-abstract@^1.22.1: - version "1.22.3" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" - integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== - dependencies: - array-buffer-byte-length "^1.0.0" - arraybuffer.prototype.slice "^1.0.2" - available-typed-arrays "^1.0.5" - call-bind "^1.0.5" - es-set-tostringtag "^2.0.1" - es-to-primitive "^1.2.1" - function.prototype.name "^1.1.6" - get-intrinsic "^1.2.2" - get-symbol-description "^1.0.0" - globalthis "^1.0.3" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - internal-slot "^1.0.5" - is-array-buffer "^3.0.2" - is-callable "^1.2.7" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-typed-array "^1.1.12" - is-weakref "^1.0.2" - object-inspect "^1.13.1" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.5.1" - safe-array-concat "^1.0.1" - safe-regex-test "^1.0.0" - string.prototype.trim "^1.2.8" - string.prototype.trimend "^1.0.7" - string.prototype.trimstart "^1.0.7" - typed-array-buffer "^1.0.0" - typed-array-byte-length "^1.0.0" - typed-array-byte-offset "^1.0.0" - typed-array-length "^1.0.4" - unbox-primitive "^1.0.2" - which-typed-array "^1.1.13" - es-module-lexer@^1.2.1: version "1.3.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.1.tgz#c1b0dd5ada807a3b3155315911f364dc4e909db1" integrity sha512-JUFAyicQV9mXc3YRxPnDlrfBKpqt6hUYzz9/boprUJHs4e4KVr3XwOF70doO6gwXUor6EWZJAyWAfKki84t20Q== -es-set-tostringtag@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9" - integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q== - dependencies: - get-intrinsic "^1.2.2" - has-tostringtag "^1.0.0" - hasown "^2.0.0" - -es-to-primitive@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" - integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== - dependencies: - is-callable "^1.1.4" - is-date-object "^1.0.1" - is-symbol "^1.0.2" - es6-error@^4.0.1: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" @@ -5601,6 +4997,11 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" @@ -5741,6 +5142,11 @@ fast-diff@^1.1.2, fast-diff@^1.2.0: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== +fast-fifo@^1.2.0, fast-fifo@^1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/fast-fifo/-/fast-fifo-1.3.2.tgz#286e31de96eb96d38a97899815740ba2a4f3640c" + integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== + fast-glob@^3.2.9, fast-glob@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4" @@ -5767,13 +5173,6 @@ fast-safe-stringify@2.1.1, fast-safe-stringify@^2.1.1: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== -fast-xml-parser@4.2.5: - version "4.2.5" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz#a6747a09296a6cb34f2ae634019bf1738f3b421f" - integrity sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g== - dependencies: - strnum "^1.0.5" - fastq@^1.6.0: version "1.15.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a" @@ -5788,13 +5187,6 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g== - dependencies: - pend "~1.2.0" - figures@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" @@ -5849,7 +5241,7 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-cache-dir@^3.2.0, find-cache-dir@^3.3.2: +find-cache-dir@^3.2.0: version "3.3.2" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== @@ -5908,18 +5300,11 @@ flatted@^3.2.7: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf" integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ== -follow-redirects@^1.15.0, follow-redirects@^1.15.2: +follow-redirects@^1.15.0: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== -for-each@^0.3.3: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw== - dependencies: - is-callable "^1.1.3" - foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -6030,26 +5415,18 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - -function.prototype.name@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" - integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - functions-have-names "^1.2.3" - functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== +generate-function@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.3.1.tgz#f069617690c10c868e73b8465746764f97c3479f" + integrity sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ== + dependencies: + is-property "^1.0.2" + generate-object-property@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" @@ -6057,6 +5434,11 @@ generate-object-property@^1.0.0: dependencies: is-property "^1.0.0" +generic-pool@3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/generic-pool/-/generic-pool-3.9.0.tgz#36f4a678e963f4fdb8707eab050823abc4e8f5e4" + integrity sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g== + gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -6100,16 +5482,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@ has-proto "^1.0.1" has-symbols "^1.0.3" -get-intrinsic@^1.2.0, get-intrinsic@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" - integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== - dependencies: - function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" - get-package-type@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" @@ -6137,14 +5509,6 @@ get-stream@^6.0.0, get-stream@^6.0.1: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -get-symbol-description@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" - integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.1" - glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -6187,7 +5551,7 @@ glob@^10.3.10, glob@^10.3.3: package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -6221,13 +5585,6 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" - integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== - dependencies: - define-properties "^1.1.3" - globby@^11.1.0: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" @@ -6297,11 +5654,6 @@ handlebars@^4.7.8: optionalDependencies: uglify-js "^3.1.4" -has-bigints@^1.0.1, has-bigints@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" - integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== - has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" @@ -6356,13 +5708,6 @@ hasha@^5.0.0, hasha@^5.2.2: is-stream "^2.0.0" type-fest "^0.8.0" -hasown@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" - integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== - dependencies: - function-bind "^1.1.2" - he@1.2.0, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -6373,6 +5718,11 @@ hexoid@^1.0.0: resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== +highlight.js@^10.7.1: + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== + html-entities@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.4.0.tgz#cfbd1b01d2afaf9adca1b10ae7dffab98c71d2dc" @@ -6461,14 +5811,6 @@ http2-wrapper@^1.0.0-beta.5.2: quick-lru "^5.1.1" resolve-alpn "^1.0.0" -https-proxy-agent@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" - integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== - dependencies: - agent-base "6" - debug "4" - human-signals@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" @@ -6498,7 +5840,7 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.12, ieee754@^1.1.13: +ieee754@^1.1.12, ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -6604,15 +5946,6 @@ inquirer@8.2.6: through "^2.3.6" wrap-ansi "^6.0.1" -internal-slot@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" - integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== - dependencies: - get-intrinsic "^1.2.2" - hasown "^2.0.0" - side-channel "^1.0.4" - interpret@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" @@ -6626,11 +5959,6 @@ into-stream@^6.0.0: from2 "^2.3.0" p-is-promise "^3.0.0" -ip@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" - integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== - ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -6649,27 +5977,11 @@ is-arguments@^1.0.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-array-buffer@^3.0.1, is-array-buffer@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe" - integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.0" - is-typed-array "^1.1.10" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-bigint@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3" - integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg== - dependencies: - has-bigints "^1.0.1" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -6677,24 +5989,11 @@ is-binary-path@~2.1.0: dependencies: binary-extensions "^2.0.0" -is-boolean-object@^1.1.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" - integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA== - dependencies: - call-bind "^1.0.2" - has-tostringtag "^1.0.0" - is-buffer@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" - integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== - is-core-module@^2.13.0: version "2.13.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" @@ -6771,18 +6070,6 @@ is-interactive@^1.0.0: resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== -is-negative-zero@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" - integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA== - -is-number-object@^1.0.4: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc" - integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ== - dependencies: - has-tostringtag "^1.0.0" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -6808,12 +6095,12 @@ is-promise@^4.0.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== -is-property@^1.0.0: +is-property@^1.0.0, is-property@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" integrity sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g== -is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.1.4: +is-regex@^1.0.3, is-regex@^1.0.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg== @@ -6821,13 +6108,6 @@ is-regex@^1.0.3, is-regex@^1.0.4, is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-shared-array-buffer@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" - integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA== - dependencies: - call-bind "^1.0.2" - is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" @@ -6843,27 +6123,6 @@ is-stream@^3.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== -is-string@^1.0.5, is-string@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" - integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg== - dependencies: - has-tostringtag "^1.0.0" - -is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" - integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== - dependencies: - has-symbols "^1.0.2" - -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: - version "1.1.12" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" - integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== - dependencies: - which-typed-array "^1.1.11" - is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -6879,13 +6138,6 @@ is-unicode-supported@^1.2.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== -is-weakref@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" - integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ== - dependencies: - call-bind "^1.0.2" - is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" @@ -6898,11 +6150,6 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" -isarray@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" - integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw== - isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -7514,11 +6761,6 @@ juice@^10.0.0: slick "^1.12.2" web-resource-inliner "^6.0.1" -kareem@2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.5.1.tgz#7b8203e11819a8e77a34b3517d3ead206764d15d" - integrity sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA== - keyv@^4.0.0, keyv@^4.5.3: version "4.5.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25" @@ -7531,6 +6773,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + leac@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/leac/-/leac-0.6.0.tgz#dcf136e382e666bd2475f44a1096061b70dc0912" @@ -7644,11 +6893,36 @@ locate-path@^7.1.0: dependencies: p-locate "^6.0.0" +lodash.clonedeep@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" + integrity sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ== + +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.flattendeep@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== +lodash.isplainobject@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" + integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -7659,6 +6933,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + lodash@4.17.21, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -7677,6 +6956,11 @@ long@^4.0.0: resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== +long@^5.2.1: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lower-case@^1.1.1: version "1.1.4" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac" @@ -7692,6 +6976,11 @@ lru-cache@^10.2.0: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== +lru-cache@^10.2.2: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -7706,6 +6995,16 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +lru-cache@^7.14.1: + version "7.18.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-7.18.3.tgz#f793896e0fd0e954a59dfdd82f0773808df6aa89" + integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== + +lru-cache@^8.0.0: + version "8.0.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-8.0.5.tgz#983fe337f3e176667f8e567cfcce7cb064ea214e" + integrity sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA== + "lru-cache@^9.1.1 || ^10.0.0": version "10.0.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" @@ -7800,11 +7099,6 @@ matcher@^5.0.0: dependencies: escape-string-regexp "^5.0.0" -md5-file@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/md5-file/-/md5-file-5.0.0.tgz#e519f631feca9c39e7f9ea1780b63c4745012e20" - integrity sha512-xbEFXCYVWrSx/gEKS1VPlg84h/4L20znVIulKw6kMfmBUAZNAnF00eczz9ICMl+/hjQGo5KSXRxbL/47X3rmMw== - md5-hex@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" @@ -7931,7 +7225,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -8305,6 +7599,11 @@ mjml@^4.15.3: mjml-preset-core "4.15.3" mjml-validator "4.15.3" +mkdirp-classic@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" + integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== + mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -8312,6 +7611,16 @@ mkdirp@^0.5.4: dependencies: minimist "^1.2.6" +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +mkdirp@^2.1.3: + version "2.1.6" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" + integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== + mnemonist@^0.38.1: version "0.38.5" resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.5.tgz#4adc7f4200491237fe0fa689ac0b86539685cade" @@ -8334,58 +7643,6 @@ mongodb-connection-string-url@^2.6.0: "@types/whatwg-url" "^8.2.1" whatwg-url "^11.0.0" -mongodb-memory-server-core@8.15.1: - version "8.15.1" - resolved "https://registry.yarnpkg.com/mongodb-memory-server-core/-/mongodb-memory-server-core-8.15.1.tgz#41d79cffacdc0e119fe825044b11da12372fb208" - integrity sha512-U6ntro5DvUD71C2juKCzzc2Hul0qLYUpQLiXcXDcvtNDbGMoJgwKScdvptQkzQwUdICeZUfvZo8By7Mc09Umog== - dependencies: - async-mutex "^0.3.2" - camelcase "^6.3.0" - debug "^4.3.4" - find-cache-dir "^3.3.2" - follow-redirects "^1.15.2" - get-port "^5.1.1" - https-proxy-agent "^5.0.1" - md5-file "^5.0.0" - mongodb "^4.16.0" - new-find-package-json "^2.0.0" - semver "^7.5.4" - tar-stream "^2.1.4" - tslib "^2.6.1" - uuid "^9.0.0" - yauzl "^2.10.0" - -mongodb-memory-server@^8.15.1: - version "8.15.1" - resolved "https://registry.yarnpkg.com/mongodb-memory-server/-/mongodb-memory-server-8.15.1.tgz#8cea839d3b3ae123028d129a96d66e37058fb2c4" - integrity sha512-nqIbM5oh1s46VV4InhqQdNFu5szx+xi6qT//87beQ10JCZHLG1nZ/SUMsXkKLNn9wvs19OAf5HwI60enK9ZOuA== - dependencies: - mongodb-memory-server-core "8.15.1" - tslib "^2.6.1" - -mongodb@5.9.0: - version "5.9.0" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-5.9.0.tgz#5a22065fa8cfaf1d58bf2e3c451cd2c4bfa983a2" - integrity sha512-g+GCMHN1CoRUA+wb1Agv0TI4YTSiWr42B5ulkiAfLLHitGK1R+PkSAf3Lr5rPZwi/3F04LiaZEW0Kxro9Fi2TA== - dependencies: - bson "^5.5.0" - mongodb-connection-string-url "^2.6.0" - socks "^2.7.1" - optionalDependencies: - "@mongodb-js/saslprep" "^1.1.0" - -mongodb@^4.16.0: - version "4.17.1" - resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.17.1.tgz#ccff6ddbda106d5e06c25b0e4df454fd36c5f819" - integrity sha512-MBuyYiPUPRTqfH2dV0ya4dcr2E5N52ocBuZ8Sgg/M030nGF78v855B3Z27mZJnp8PxjnUquEnAtjOsphgMZOlQ== - dependencies: - bson "^4.7.2" - mongodb-connection-string-url "^2.6.0" - socks "^2.7.1" - optionalDependencies: - "@aws-sdk/credential-providers" "^3.186.0" - "@mongodb-js/saslprep" "^1.1.0" - mongodb@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.1.0.tgz#5144bee74d50746f7b0ed68dbb974f31e1b40900" @@ -8395,44 +7652,6 @@ mongodb@^6.1.0: bson "^6.1.0" mongodb-connection-string-url "^2.6.0" -mongoose-lean-virtuals@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/mongoose-lean-virtuals/-/mongoose-lean-virtuals-0.9.1.tgz#7e5e8b7471237606d1a0d785f1b7240f86547d85" - integrity sha512-jx4rhXuaQPam/lwef3z/FfYHlKdbFkDr9Qb7JEMeoa7y4pOuyJ83RkcNL25HRaoi4Bt71zKmV1cuJdv243t9aA== - dependencies: - array.prototype.flat "1.2.3" - mpath "^0.8.4" - -mongoose@^7.6.0: - version "7.6.0" - resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-7.6.0.tgz#3037b754ccdf168385e52d4bcabeb7f5c3b17a5f" - integrity sha512-ztQ12rm0BQN5i7LB6xhWX4l9a9w2aa3jEwa/mM2vAutYJRyAwOzcusvKJBULMzFHyUDBOVW15grisexypgMIWA== - dependencies: - bson "^5.4.0" - kareem "2.5.1" - mongodb "5.9.0" - mpath "0.9.0" - mquery "5.0.0" - ms "2.1.3" - sift "16.0.1" - -mpath@0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.9.0.tgz#0c122fe107846e31fc58c75b09c35514b3871904" - integrity sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew== - -mpath@^0.8.4: - version "0.8.4" - resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.4.tgz#6b566d9581621d9e931dd3b142ed3618e7599313" - integrity sha512-DTxNZomBcTWlrMW76jy1wvV37X/cNNxPW1y2Jzd4DZkAaC5ZGsm8bfGfNOthcDuRJujXLqiuS6o3Tpy0JEoh7g== - -mquery@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/mquery/-/mquery-5.0.0.tgz#a95be5dfc610b23862df34a47d3e5d60e110695d" - integrity sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg== - dependencies: - debug "4.x" - ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -8466,6 +7685,41 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +mysql2@^3.9.7: + version "3.9.7" + resolved "https://registry.yarnpkg.com/mysql2/-/mysql2-3.9.7.tgz#843755daf65b5ef08afe545fe14b8fb62824741a" + integrity sha512-KnJT8vYRcNAZv73uf9zpXqNbvBG7DJrs+1nACsjZP1HMJ1TgXEy8wnNilXAn/5i57JizXKtrUtwDB7HxT9DDpw== + dependencies: + denque "^2.1.0" + generate-function "^2.3.1" + iconv-lite "^0.6.3" + long "^5.2.1" + lru-cache "^8.0.0" + named-placeholders "^1.1.3" + seq-queue "^0.0.5" + sqlstring "^2.3.2" + +mz@^2.4.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" + integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== + dependencies: + any-promise "^1.0.0" + object-assign "^4.0.1" + thenify-all "^1.0.0" + +named-placeholders@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/named-placeholders/-/named-placeholders-1.1.3.tgz#df595799a36654da55dda6152ba7a137ad1d9351" + integrity sha512-eLoBxg6wE/rZkJPhU/xRX1WTpkFEwDJEN96oxFrTsqBdbT5ec295Q+CoHrL9IT0DipqKhmGcaZmwOt8OON5x1w== + dependencies: + lru-cache "^7.14.1" + +nan@^2.18.0, nan@^2.19.0: + version "2.20.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.20.0.tgz#08c5ea813dd54ed16e5bd6505bf42af4f7838ca3" + integrity sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8491,13 +7745,6 @@ neo-async@^2.6.2: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -new-find-package-json@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/new-find-package-json/-/new-find-package-json-2.0.0.tgz#96553638781db35061f351e8ccb4d07126b6407d" - integrity sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew== - dependencies: - debug "^4.3.4" - nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -8532,7 +7779,7 @@ node-emoji@1.11.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@^2.6.0, node-fetch@^2.6.1, node-fetch@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -8654,16 +7901,11 @@ nyc@^15.1.0: test-exclude "^6.0.0" yargs "^15.0.2" -object-assign@*, object-assign@^4, object-assign@^4.1.1: +object-assign@*, object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== - object-inspect@^1.9.0: version "1.12.3" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9" @@ -8682,16 +7924,6 @@ object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object.assign@^4.1.4: - version "4.1.5" - resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.5.tgz#3a833f9ab7fdb80fc9e8d2300c803d216d8fdbb0" - integrity sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ== - dependencies: - call-bind "^1.0.5" - define-properties "^1.2.1" - has-symbols "^1.0.3" - object-keys "^1.1.1" - obliterator@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3" @@ -8963,6 +8195,13 @@ parse-ms@^3.0.0: resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-3.0.0.tgz#3ea24a934913345fcc3656deda72df921da3a70e" integrity sha512-Tpb8Z7r7XbbtBTrM9UhpkzzaMrqA2VXMT3YChzYltwV3P3pM6t8wl7TvpMnSTosz1aQAdVib7kdoys7vYOPerw== +parse5-htmlparser2-tree-adapter@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz#23c2cc233bcf09bb7beba8b8a69d46b08c62c2f1" @@ -8971,6 +8210,16 @@ parse5-htmlparser2-tree-adapter@^7.0.0: domhandler "^5.0.2" parse5 "^7.0.0" +parse5@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" + integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== + +parse5@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" + integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== + parse5@^7.0.0: version "7.1.2" resolved "https://registry.yarnpkg.com/parse5/-/parse5-7.1.2.tgz#0736bebbfd77793823240a23b7fc5e010b7f8e32" @@ -9075,10 +8324,61 @@ peberminta@^0.9.0: resolved "https://registry.yarnpkg.com/peberminta/-/peberminta-0.9.0.tgz#8ec9bc0eb84b7d368126e71ce9033501dca2a352" integrity sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ== -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.6.4: + version "2.6.4" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.4.tgz#f543862adfa49fa4e14bc8a8892d2a84d754246d" + integrity sha512-v+Z7W/0EO707aNMaAEfiGnGL9sxxumwLl2fJvCQtMn9Fxsg+lPpPkdcyBSv/KFgpGdYkMfn+EI1Or2EHjpgLCA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-pool@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.2.tgz#3a592370b8ae3f02a7c8130d245bc02fa2c5f3f2" + integrity sha512-Htjbg8BlwXqSBQ9V8Vjtc+vzf/6fVUuak/3/XXKA9oxZprwW3IMDQTGHP+KDmVL7rtd+R1QjbnCFPuTHm3G4hg== + +pg-protocol@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg@^8.11.5: + version "8.11.5" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.5.tgz#e722b0a5f1ed92931c31758ebec3ddf878dd4128" + integrity sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw== + dependencies: + pg-connection-string "^2.6.4" + pg-pool "^3.6.2" + pg-protocol "^1.6.1" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" picocolors@^1.0.0: version "1.0.0" @@ -9134,6 +8434,28 @@ polygon-clipping@^0.15.3: dependencies: splaytree "^3.1.0" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -9204,6 +8526,11 @@ proj4@^2.8.0: mgrs "1.0.0" wkt-parser "^1.3.1" +promise-coalesce@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/promise-coalesce/-/promise-coalesce-1.1.2.tgz#5d3bc4d0b2cf2e41e9df7cbeb6519b2a09459e3d" + integrity sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg== + promise@^7.0.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" @@ -9224,6 +8551,22 @@ propagate@^2.0.0: resolved "https://registry.yarnpkg.com/propagate/-/propagate-2.0.1.tgz#40cdedab18085c792334e64f0ac17256d38f9a45" integrity sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag== +proper-lockfile@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" + integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== + dependencies: + graceful-fs "^4.2.4" + retry "^0.12.0" + signal-exit "^3.0.2" + +properties-reader@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/properties-reader/-/properties-reader-2.3.0.tgz#f3ab84224c9535a7a36e011ae489a79a13b472b2" + integrity sha512-z597WicA7nDZxK12kZqHr2TcvwNU1GCfA5UwfDY/HDp3hXPoPlb5rlEx9bwGTiJnc0OqbBTkU975jDToth8Gxw== + dependencies: + mkdirp "^1.0.4" + proto-list@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" @@ -9401,6 +8744,11 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +queue-tick@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/queue-tick/-/queue-tick-1.0.1.tgz#f6f07ac82c1fd60f82e098b417a80e52f1f4c142" + integrity sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag== + quick-lru@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" @@ -9472,7 +8820,7 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -9485,7 +8833,7 @@ readable-stream@^2.0.0, readable-stream@^2.2.2, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1, readable-stream@^3.4.0: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -9494,6 +8842,13 @@ readable-stream@^3.1.1, readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== + dependencies: + minimatch "^5.1.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -9508,17 +8863,34 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" +redis@^4.3.1: + version "4.6.15" + resolved "https://registry.yarnpkg.com/redis/-/redis-4.6.15.tgz#b94599fbbd8279182b02f5bb34866c2a1556d71c" + integrity sha512-2NtuOpMW3tnYzBw6S8mbXSX7RPzvVFCA2wFJq9oErushO2UeBkxObk+uvo7gv7n0rhWeOj/IzrHO8TjcFlRSOg== + dependencies: + "@redis/bloom" "1.2.0" + "@redis/client" "1.5.17" + "@redis/graph" "1.1.1" + "@redis/json" "1.0.6" + "@redis/search" "1.1.6" + "@redis/time-series" "1.0.5" + reflect-metadata@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== +reflect-metadata@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + regenerator-runtime@^0.14.0: version "0.14.0" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45" integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA== -regexp.prototype.flags@^1.2.0, regexp.prototype.flags@^1.5.1: +regexp.prototype.flags@^1.2.0: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -9626,6 +8998,11 @@ restore-cursor@^3.1.0: onetime "^5.1.0" signal-exit "^3.0.2" +retry@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" + integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow== + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -9683,36 +9060,17 @@ rxjs@7.8.1, rxjs@^7.5.5, rxjs@^7.8.1: dependencies: tslib "^2.1.0" -safe-array-concat@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" - integrity sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - has-symbols "^1.0.3" - isarray "^2.0.5" - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@5.2.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-regex-test@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295" - integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.1.3" - is-regex "^1.1.4" - -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0": +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -9769,6 +9127,11 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +seq-queue@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/seq-queue/-/seq-queue-0.0.5.tgz#d56812e1c017a6e4e7c3e3a37a1da6d78dd3c93e" + integrity sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q== + serialize-error@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18" @@ -9798,16 +9161,6 @@ set-blocking@^2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== -set-function-length@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed" - integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ== - dependencies: - define-data-property "^1.1.1" - get-intrinsic "^1.2.1" - gopd "^1.0.1" - has-property-descriptors "^1.0.0" - set-function-name@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a" @@ -9822,6 +9175,14 @@ setprototypeof@1.2.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== +sha.js@^2.4.11: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -9864,11 +9225,6 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" -sift@16.0.1: - version "16.0.1" - resolved "https://registry.yarnpkg.com/sift/-/sift-16.0.1.tgz#e9c2ccc72191585008cf3e36fc447b2d2633a053" - integrity sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ== - signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -9912,19 +9268,6 @@ slick@^1.12.2: resolved "https://registry.yarnpkg.com/slick/-/slick-1.12.2.tgz#bd048ddb74de7d1ca6915faa4a57570b3550c2d7" integrity sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A== -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - -socks@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" - integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== - dependencies: - ip "^2.0.0" - smart-buffer "^4.2.0" - source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -9975,6 +9318,11 @@ splaytree@^3.1.0: resolved "https://registry.yarnpkg.com/splaytree/-/splaytree-3.1.2.tgz#d1db2691665a3c69d630de98d55145a6546dc166" integrity sha512-4OM2BJgC5UzrhVnnJA4BkHKGtjXNzzUfpQjCO8I05xYPsfS/VuQDwjCGGMi8rYQilHEV4j8NBqTFbls/PZEE7A== +split-ca@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/split-ca/-/split-ca-1.0.1.tgz#6c83aff3692fa61256e0cd197e05e9de157691a6" + integrity sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ== + split2@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/split2/-/split2-2.2.0.tgz#186b2575bcf83e85b7d18465756238ee4ee42493" @@ -9982,11 +9330,40 @@ split2@^2.1.0: dependencies: through2 "^2.0.2" +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== +sqlstring@^2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/sqlstring/-/sqlstring-2.3.3.tgz#2ddc21f03bce2c387ed60680e739922c65751d0c" + integrity sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg== + +ssh-remote-port-forward@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz#72b0c5df8ec27ca300c75805cc6b266dee07e298" + integrity sha512-x0LV1eVDwjf1gmG7TTnfqIzf+3VPRz7vrNIjX6oYLbeCrf/PeVY6hkT68Mg+q02qXxQhrLjB0jfgvhevoCRmLQ== + dependencies: + "@types/ssh2" "^0.5.48" + ssh2 "^1.4.0" + +ssh2@^1.11.0, ssh2@^1.4.0: + version "1.15.0" + resolved "https://registry.yarnpkg.com/ssh2/-/ssh2-1.15.0.tgz#2f998455036a7f89e0df5847efb5421748d9871b" + integrity sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw== + dependencies: + asn1 "^0.2.6" + bcrypt-pbkdf "^1.0.2" + optionalDependencies: + cpu-features "~0.0.9" + nan "^2.18.0" + stack-utils@^2.0.3, stack-utils@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f" @@ -10009,6 +9386,17 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== +streamx@^2.15.0, streamx@^2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/streamx/-/streamx-2.18.0.tgz#5bc1a51eb412a667ebfdcd4e6cf6a6fc65721ac7" + integrity sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ== + dependencies: + fast-fifo "^1.3.2" + queue-tick "^1.0.1" + text-decoder "^1.1.0" + optionalDependencies: + bare-events "^2.2.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" @@ -10044,33 +9432,6 @@ string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string.prototype.trim@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" - integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trimend@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" - integrity sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - -string.prototype.trimstart@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" - integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.2.0" - es-abstract "^1.22.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -10141,11 +9502,6 @@ strip-json-comments@~2.0.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== -strnum@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" - integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== - superagent@^8.0.5: version "8.1.2" resolved "https://registry.yarnpkg.com/superagent/-/superagent-8.1.2.tgz#03cb7da3ec8b32472c9d20f6c2a57c7f3765f30b" @@ -10241,7 +9597,28 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== -tar-stream@^2.1.4: +tar-fs@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-3.0.6.tgz#eaccd3a67d5672f09ca8e8f9c3d2b89fa173f217" + integrity sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w== + dependencies: + pump "^3.0.0" + tar-stream "^3.1.5" + optionalDependencies: + bare-fs "^2.1.1" + bare-path "^2.1.0" + +tar-fs@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.1.tgz#e44086c1c60d31a4f0cf893b1c4e155dabfae9e2" + integrity sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA== + dependencies: + chownr "^1.1.1" + mkdirp-classic "^0.5.2" + pump "^3.0.0" + tar-stream "^2.0.0" + +tar-stream@^2.0.0, tar-stream@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -10252,6 +9629,15 @@ tar-stream@^2.1.4: inherits "^2.0.3" readable-stream "^3.1.1" +tar-stream@^3.1.5: + version "3.1.7" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-3.1.7.tgz#24b3fb5eabada19fe7338ed6d26e5f7c482e792b" + integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== + dependencies: + b4a "^1.6.4" + fast-fifo "^1.2.0" + streamx "^2.15.0" + temp-dir@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-3.0.0.tgz#7f147b42ee41234cc6ba3138cd8e8aa2302acffa" @@ -10287,11 +9673,53 @@ test-exclude@^6.0.0: glob "^7.1.4" minimatch "^3.0.4" +testcontainers@^10.10.4: + version "10.10.4" + resolved "https://registry.yarnpkg.com/testcontainers/-/testcontainers-10.10.4.tgz#35b39be446f8511029e34429e2854c6464143dae" + integrity sha512-/vAEbFfF0m8oU+LmnRcETxKCQ28t+zC5Mkdi1QfydrR9HCFEsj1M4UrKWwiCbOa1Y2gLUZ3JX6553oxZb6n7zw== + dependencies: + "@balena/dockerignore" "^1.0.2" + "@types/dockerode" "^3.3.29" + archiver "^5.3.2" + async-lock "^1.4.1" + byline "^5.0.0" + debug "^4.3.5" + docker-compose "^0.24.8" + dockerode "^3.3.5" + get-port "^5.1.1" + node-fetch "^2.7.0" + proper-lockfile "^4.1.2" + properties-reader "^2.3.0" + ssh-remote-port-forward "^1.0.4" + tar-fs "^3.0.6" + tmp "^0.2.3" + +text-decoder@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/text-decoder/-/text-decoder-1.1.1.tgz#5df9c224cebac4a7977720b9f083f9efa1aefde8" + integrity sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA== + dependencies: + b4a "^1.6.4" + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thenify-all@^1.0.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" + integrity sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA== + dependencies: + thenify ">= 3.1.0 < 4" + +"thenify@>= 3.1.0 < 4": + version "3.3.1" + resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.1.tgz#8932e686a4066038a016dd9e2ca46add9838a95f" + integrity sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw== + dependencies: + any-promise "^1.0.0" + through2@^2.0.2, through2@^2.0.3: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" @@ -10332,6 +9760,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -10456,21 +9889,21 @@ tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2, tsconfig-paths@^4.2.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.6.2, tslib@^2.1.0, tslib@^2.3.1, tslib@^2.5.0, tslib@^2.6.0, tslib@^2.6.1: +tslib@2.6.2, tslib@^2.1.0, tslib@^2.5.0, tslib@^2.6.0: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== -tslib@^1.11.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - turf-jsts@*: version "1.2.3" resolved "https://registry.yarnpkg.com/turf-jsts/-/turf-jsts-1.2.3.tgz#59757f542afbff9a577bbf411f183b8f48d38aa4" integrity sha512-Ja03QIJlPuHt4IQ2FfGex4F4JAr8m3jpaHbFbQrgwr7s7L6U8ocrHiF3J1+wf9jzhGKxvDeaCAnGDot8OjGFyA== +tweetnacl@^0.14.3: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -10511,45 +9944,6 @@ type-is@^1.6.4, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typed-array-buffer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz#18de3e7ed7974b0a729d3feecb94338d1472cd60" - integrity sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw== - dependencies: - call-bind "^1.0.2" - get-intrinsic "^1.2.1" - is-typed-array "^1.1.10" - -typed-array-byte-length@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz#d787a24a995711611fb2b87a4052799517b230d0" - integrity sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-byte-offset@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz#cbbe89b51fdef9cd6aaf07ad4707340abbc4ea0b" - integrity sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - for-each "^0.3.3" - has-proto "^1.0.1" - is-typed-array "^1.1.10" - -typed-array-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb" - integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng== - dependencies: - call-bind "^1.0.2" - for-each "^0.3.3" - is-typed-array "^1.1.9" - typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -10562,6 +9956,27 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA== +typeorm@^0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/typeorm/-/typeorm-0.3.20.tgz#4b61d737c6fed4e9f63006f88d58a5e54816b7ab" + integrity sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q== + dependencies: + "@sqltools/formatter" "^1.2.5" + app-root-path "^3.1.0" + buffer "^6.0.3" + chalk "^4.1.2" + cli-highlight "^2.1.11" + dayjs "^1.11.9" + debug "^4.3.4" + dotenv "^16.0.3" + glob "^10.3.10" + mkdirp "^2.1.3" + reflect-metadata "^0.2.1" + sha.js "^2.4.11" + tslib "^2.5.0" + uuid "^9.0.0" + yargs "^17.6.2" + typescript@5.2.2, typescript@^5.1.3: version "5.2.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" @@ -10584,15 +9999,10 @@ uid@2.0.2: dependencies: "@lukeed/csprng" "^1.0.0" -unbox-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" - integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw== - dependencies: - call-bind "^1.0.2" - has-bigints "^1.0.2" - has-symbols "^1.0.3" - which-boxed-primitive "^1.0.2" +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== universalify@^2.0.0: version "2.0.0" @@ -10810,33 +10220,11 @@ whatwg-url@^5.0.0: tr46 "~0.0.3" webidl-conversions "^3.0.0" -which-boxed-primitive@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" - integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg== - dependencies: - is-bigint "^1.0.1" - is-boolean-object "^1.1.0" - is-number-object "^1.0.4" - is-string "^1.0.5" - is-symbol "^1.0.3" - which-module@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.1.tgz#776b1fe35d90aebe99e8ac15eb24093389a4a409" integrity sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ== -which-typed-array@^1.1.11, which-typed-array@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" - integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.4" - for-each "^0.3.3" - gopd "^1.0.1" - has-tostringtag "^1.0.0" - which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" @@ -10965,21 +10353,26 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== +yallist@4.0.0, yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== +yaml@^2.2.2: + version "2.4.5" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" + integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg== + yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" @@ -10993,6 +10386,11 @@ yargs-parser@^18.1.2: camelcase "^5.0.0" decamelize "^1.2.0" +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + yargs@^15.0.2: version "15.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" @@ -11010,7 +10408,20 @@ yargs@^15.0.2: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^17.3.1, yargs@^17.5.1, yargs@^17.7.2: +yargs@^16.0.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yargs@^17.3.1, yargs@^17.5.1, yargs@^17.6.2, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== @@ -11023,14 +10434,6 @@ yargs@^17.3.1, yargs@^17.5.1, yargs@^17.7.2: y18n "^5.0.5" yargs-parser "^21.1.1" -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" @@ -11045,3 +10448,12 @@ yocto-queue@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251" integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== + +zip-stream@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== + dependencies: + archiver-utils "^3.0.4" + compress-commons "^4.1.2" + readable-stream "^3.6.0"