diff --git a/.env.demo b/.env.demo index c9cd8c829..984d1b2b2 100644 --- a/.env.demo +++ b/.env.demo @@ -223,3 +223,7 @@ RESEND_API_KEY=re_xxxxxxxxxx # Prisma log type. Default set to error PRISMA_LOGS = error +# HIDE_EXPERIMENTAL_OIDC_CONTROLLERS=true + +# DB_ALERT_ENABLE= +# DB_ALERT_EMAILS= diff --git a/.env.sample b/.env.sample index cb422b084..3a2450ebe 100644 --- a/.env.sample +++ b/.env.sample @@ -242,3 +242,11 @@ RESEND_API_KEY=re_xxxxxxxxxx # Prisma log type. Ideally should have only error or warn enabled. Having query enabled can add a lot of unwanted logging for all types of queries being run # PRISMA_LOGS = error,warn,query + +# Default is true too, if nothing is passed +# HIDE_EXPERIMENTAL_OIDC_CONTROLLERS= + +# Comma separated emails that need to be alerted in case the 'ledgerId' is set to null +# DB_ALERT_EMAILS= +# Boolean: to enable/disable db alerts. This needs the 'utility' microservice +# DB_ALERT_ENABLE= diff --git a/apps/api-gateway/src/main.ts b/apps/api-gateway/src/main.ts index e92468ce8..6a57d5a32 100644 --- a/apps/api-gateway/src/main.ts +++ b/apps/api-gateway/src/main.ts @@ -119,6 +119,14 @@ async function bootstrap(): Promise { ); app.useGlobalInterceptors(new NatsInterceptor()); await app.listen(process.env.API_GATEWAY_PORT, `${process.env.API_GATEWAY_HOST}`); - Logger.log(`API Gateway is listening on port ${process.env.API_GATEWAY_PORT}`); + Logger.log(`API Gateway is listening on port ${process.env.API_GATEWAY_PORT}`, 'Success'); + + if ('true' === process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) { + // in case it is enabled, log that + Logger.log( + "We have enabled DB alert for 'ledger_null' instances. This would send email in case the 'ledger_id' column in 'org_agents' table is set to null", + 'DB alert enabled' + ); + } } bootstrap(); diff --git a/apps/api-gateway/src/utilities/utilities.service.ts b/apps/api-gateway/src/utilities/utilities.service.ts index 58f3f5cd7..d90b0f4e5 100644 --- a/apps/api-gateway/src/utilities/utilities.service.ts +++ b/apps/api-gateway/src/utilities/utilities.service.ts @@ -3,14 +3,121 @@ import { BaseService } from 'libs/service/base.service'; import { StoreObjectDto, UtilitiesDto } from './dtos/shortening-url.dto'; import { NATSClient } from '@credebl/common/NATSClient'; import { ClientProxy } from '@nestjs/microservices'; +import { Client as PgClient } from 'pg'; +import { CommonConstants } from '@credebl/common/common.constant'; @Injectable() export class UtilitiesService extends BaseService { + private readonly pg: PgClient; + private isSendingNatsAlert = false; + constructor( @Inject('NATS_CLIENT') private readonly serviceProxy: ClientProxy, private readonly natsClient: NATSClient ) { - super('OrganizationService'); + super('UtilitiesService'); + if ('true' === process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) { + if (!process.env.DATABASE_URL) { + throw new Error('DATABASE_URL environment variable is required'); + } else { + this.pg = new PgClient({ + connectionString: process.env.DATABASE_URL + }); + } + } + } + + // TODO: I think it would be better, if we add all the event listening and email sending logic in a common library instead of it being scattered across here and the utility microservice + async onModuleInit(): Promise { + try { + if ('true' !== process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) { + // in case it is not enabled, return + return; + } + await this.pg.connect(); + await this.pg.query('LISTEN ledger_null'); + this.logger.log('PostgreSQL notification listener connected'); + } catch (err) { + this.logger.error(`Failed to connect PostgreSQL listener: ${err?.message}`); + throw err; + } + + this.pg.on('notification', async (msg) => { + if ('true' !== process.env.DB_ALERT_ENABLE?.trim()?.toLowerCase()) { + // in case it is not enabled, return + return; + } + + if ('ledger_null' === msg.channel) { + try { + if (this.isSendingNatsAlert) { + this.logger.warn('Skipping duplicate NATS alert send...'); + return; + } + + this.isSendingNatsAlert = true; + + // Step 1: Count total records + const totalRes = await this.pg.query('SELECT COUNT(*) FROM org_agents'); + const total = Number(totalRes.rows[0].count); + + // If the org_agents table has no records, total will be 0, causing a division by zero resulting in Infinity or NaN + if (0 === total) { + this.logger.debug('No org_agents records found, skipping alert check'); + return; + } + + // Step 2: Count NULL ledgerId records + const nullRes = await this.pg.query('SELECT COUNT(*) FROM org_agents WHERE "ledgerId" IS NULL'); + const nullCount = Number(nullRes.rows[0].count); + + // Step 3: Calculate % + const percent = (nullCount / total) * 100; + + // Condition: > 30% for now + if (CommonConstants.AFFECTED_RECORDS_THRESHOLD_PERCENTAGE_FOR_DB_ALERT >= percent) { + return; + } + + const alertEmails = + process.env.DB_ALERT_EMAILS?.split(',') + .map((e) => e.trim()) + .filter((e) => 0 < e.length) || []; + + if (0 === alertEmails.length) { + this.logger.error( + `DB_ALERT_EMAILS is empty, skipping alert. There is a ${percent}% records are set to null for 'ledgerId' in 'org_agents' table`, + 'DB alert' + ); + return; + } + + const emailDto = { + emailFrom: '', + emailTo: alertEmails, + emailSubject: '[ALERT] More than 30% org_agents ledgerId is NULL', + emailText: `ALERT: ${percent.toFixed(2)}% of org_agents records currently have ledgerId = NULL.`, + emailHtml: `

ALERT: ${percent.toFixed( + 2 + )}% of org_agents have ledgerId = NULL.

` + }; + + const result = await this.natsClient.sendNatsMessage(this.serviceProxy, 'alert-db-ledgerId-null', { + emailDto + }); + this.logger.debug('Received result', JSON.stringify(result, null, 2)); + } catch (err) { + this.logger.error(err?.message ?? 'Error in ledgerId alert handler'); + } finally { + // Once its done, reset the flag + this.isSendingNatsAlert = false; + } + } + }); + } + + async onModuleDestroy(): Promise { + await this.pg?.end(); } async createShorteningUrl(shorteningUrlDto: UtilitiesDto): Promise { diff --git a/apps/ledger/src/ledger.module.ts b/apps/ledger/src/ledger.module.ts index ed5daa043..cdb5779d2 100644 --- a/apps/ledger/src/ledger.module.ts +++ b/apps/ledger/src/ledger.module.ts @@ -2,7 +2,7 @@ import { Logger, Module } from '@nestjs/common'; import { LedgerController } from './ledger.controller'; import { LedgerService } from './ledger.service'; import { SchemaModule } from './schema/schema.module'; -import { PrismaService } from '@credebl/prisma-service'; +import { PrismaServiceModule } from '@credebl/prisma-service'; import { CredentialDefinitionModule } from './credential-definition/credential-definition.module'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { LedgerRepository } from './repositories/ledger.repository'; @@ -16,23 +16,21 @@ import { ContextInterceptorModule } from '@credebl/context/contextInterceptorMod @Module({ imports: [ GlobalConfigModule, - LoggerModule, PlatformConfig, ContextInterceptorModule, + LoggerModule, + PlatformConfig, + ContextInterceptorModule, ClientsModule.register([ { name: 'NATS_CLIENT', transport: Transport.NATS, options: getNatsOptions(CommonConstants.LEDGER_SERVICE, process.env.LEDGER_NKEY_SEED) - } ]), - SchemaModule, CredentialDefinitionModule + SchemaModule, + CredentialDefinitionModule, + PrismaServiceModule ], controllers: [LedgerController], - providers: [ - LedgerService, - PrismaService, - LedgerRepository, - Logger - ] + providers: [LedgerService, LedgerRepository, Logger] }) -export class LedgerModule { } +export class LedgerModule {} diff --git a/apps/utility/src/utilities.controller.ts b/apps/utility/src/utilities.controller.ts index f9199625d..68a2775fc 100644 --- a/apps/utility/src/utilities.controller.ts +++ b/apps/utility/src/utilities.controller.ts @@ -2,6 +2,7 @@ import { Controller, Logger } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { UtilitiesService } from './utilities.service'; import { IShorteningUrlData } from '../interfaces/shortening-url.interface'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; @Controller() export class UtilitiesController { @@ -30,4 +31,17 @@ export class UtilitiesController { throw new Error('Error occured in Utility Microservices Controller'); } } + + @MessagePattern({ cmd: 'alert-db-ledgerId-null' }) + async handleLedgerAlert(payload: { emailDto: EmailDto }): Promise { + try { + this.logger.debug('Received msg in alert-db-service'); + const result = await this.utilitiesService.handleLedgerAlert(payload.emailDto); + this.logger.debug('Received result in alert-db-service'); + return result; + } catch (error) { + this.logger.error(error); + throw error; + } + } } diff --git a/apps/utility/src/utilities.repository.ts b/apps/utility/src/utilities.repository.ts index 7727d1b8e..a6a193337 100644 --- a/apps/utility/src/utilities.repository.ts +++ b/apps/utility/src/utilities.repository.ts @@ -1,51 +1,60 @@ -import { PrismaService } from "@credebl/prisma-service"; -import { Injectable, Logger } from "@nestjs/common"; +import { PrismaService } from '@credebl/prisma-service'; +import { Injectable, Logger } from '@nestjs/common'; // eslint-disable-next-line camelcase -import { shortening_url } from "@prisma/client"; +import { platform_config, shortening_url } from '@prisma/client'; @Injectable() export class UtilitiesRepository { - constructor( - private readonly prisma: PrismaService, - private readonly logger: Logger - ) { } - - async saveShorteningUrl( - payload - ): Promise { - - try { - - const { referenceId, invitationPayload } = payload; - const storeShorteningUrl = await this.prisma.shortening_url.upsert({ - where: { referenceId }, - update: { invitationPayload }, - create: { referenceId, invitationPayload } - }); - - this.logger.log(`[saveShorteningUrl] - shortening url details ${referenceId}`); - return storeShorteningUrl; - } catch (error) { - this.logger.error(`Error in saveShorteningUrl: ${error} `); - throw error; - } + constructor( + private readonly prisma: PrismaService, + private readonly logger: Logger + ) {} + + async saveShorteningUrl(payload): Promise { + try { + const { referenceId, invitationPayload } = payload; + const storeShorteningUrl = await this.prisma.shortening_url.upsert({ + where: { referenceId }, + update: { invitationPayload }, + create: { referenceId, invitationPayload } + }); + + this.logger.log(`[saveShorteningUrl] - shortening url details ${referenceId}`); + return storeShorteningUrl; + } catch (error) { + this.logger.error(`Error in saveShorteningUrl: ${error} `); + throw error; } - - // eslint-disable-next-line camelcase - async getShorteningUrl(referenceId): Promise { - try { - - const storeShorteningUrl = await this.prisma.shortening_url.findUnique({ - where: { - referenceId - } - }); - - this.logger.log(`[getShorteningUrl] - shortening url details ${referenceId}`); - return storeShorteningUrl; - } catch (error) { - this.logger.error(`Error in getShorteningUrl: ${error} `); - throw error; + } + + // eslint-disable-next-line camelcase + async getShorteningUrl(referenceId): Promise { + try { + const storeShorteningUrl = await this.prisma.shortening_url.findUnique({ + where: { + referenceId } + }); + + this.logger.log(`[getShorteningUrl] - shortening url details ${referenceId}`); + return storeShorteningUrl; + } catch (error) { + this.logger.error(`Error in getShorteningUrl: ${error} `); + throw error; + } + } + + /** + * Get platform config details + * @returns + */ + // eslint-disable-next-line camelcase + async getPlatformConfigDetails(): Promise { + try { + return this.prisma.platform_config.findFirst(); + } catch (error) { + this.logger.error(`[getPlatformConfigDetails] - error: ${JSON.stringify(error)}`); + throw error; } -} \ No newline at end of file + } +} diff --git a/apps/utility/src/utilities.service.ts b/apps/utility/src/utilities.service.ts index ae299f334..f6180bf9f 100644 --- a/apps/utility/src/utilities.service.ts +++ b/apps/utility/src/utilities.service.ts @@ -1,63 +1,142 @@ -import { Injectable, Logger } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { RpcException } from '@nestjs/microservices'; import { UtilitiesRepository } from './utilities.repository'; import { AwsService } from '@credebl/aws'; import { S3 } from 'aws-sdk'; import { v4 as uuidv4 } from 'uuid'; +import { EmailService } from '@credebl/common/email.service'; +import { EmailDto } from '@credebl/common/dtos/email.dto'; +import { BaseService } from 'libs/service/base.service'; +import { ResponseMessages } from '@credebl/common/response-messages'; @Injectable() -export class UtilitiesService { - constructor( - private readonly logger: Logger, - private readonly utilitiesRepository: UtilitiesRepository, - private readonly awsService: AwsService - ) { } - - async createAndStoreShorteningUrl(payload): Promise { - try { - const { credentialId, schemaId, credDefId, invitationUrl, attributes } = payload; - const invitationPayload = { - referenceId: credentialId, - invitationPayload: { - schemaId, - credDefId, - invitationUrl, - attributes - } - }; - await this.utilitiesRepository.saveShorteningUrl(invitationPayload); - return `${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_ENDPOINT}/invitation/qr-code/${credentialId}`; - } catch (error) { - this.logger.error(`[createAndStoreShorteningUrl] - error in create shortening url: ${JSON.stringify(error)}`); - throw new RpcException(error); +export class UtilitiesService extends BaseService { + private lastAlertTime: number | null = null; + private isSendingAlert = false; // Prevent concurrent retries + + constructor( + private readonly utilitiesRepository: UtilitiesRepository, + private readonly awsService: AwsService, + private readonly emailService: EmailService + ) { + super('UtilitiesService'); + } + + async createAndStoreShorteningUrl(payload): Promise { + try { + const { credentialId, schemaId, credDefId, invitationUrl, attributes } = payload; + const invitationPayload = { + referenceId: credentialId, + invitationPayload: { + schemaId, + credDefId, + invitationUrl, + attributes } + }; + await this.utilitiesRepository.saveShorteningUrl(invitationPayload); + return `${process.env.API_GATEWAY_PROTOCOL}://${process.env.API_ENDPOINT}/invitation/qr-code/${credentialId}`; + } catch (error) { + this.logger.error(`[createAndStoreShorteningUrl] - error in create shortening url: ${JSON.stringify(error)}`); + throw new RpcException(error); } + } - async getShorteningUrl(referenceId: string): Promise { - try { - const getShorteningUrl = await this.utilitiesRepository.getShorteningUrl(referenceId); - - const getInvitationUrl = { - referenceId: getShorteningUrl.referenceId, - invitationPayload: getShorteningUrl.invitationPayload - }; - - return getInvitationUrl; - } catch (error) { - this.logger.error(`[getShorteningUrl] - error in get shortening url: ${JSON.stringify(error)}`); - throw new RpcException(error); - } + async getShorteningUrl(referenceId: string): Promise { + try { + const getShorteningUrl = await this.utilitiesRepository.getShorteningUrl(referenceId); + + if (!getShorteningUrl) { + throw new NotFoundException(`Shortening URL not found for referenceId: ${referenceId}`); + } + + const getInvitationUrl = { + referenceId: getShorteningUrl.referenceId, + invitationPayload: getShorteningUrl.invitationPayload + }; + + return getInvitationUrl; + } catch (error) { + this.logger.error(`[getShorteningUrl] - error in get shortening url: ${JSON.stringify(error)}`); + throw new RpcException(error); + } + } + + async storeObject(payload: { persistent: boolean; storeObj: unknown }): Promise { + try { + const uuid = uuidv4(); + const uploadResult: S3.ManagedUpload.SendData = await this.awsService.storeObject( + payload.persistent, + uuid, + payload.storeObj + ); + const url: string = `${process.env.SHORTENED_URL_DOMAIN}/${uploadResult.Key}`; + return url; + } catch (error) { + this.logger.error(error); + throw new Error( + `An error occurred while uploading data to S3: ${error instanceof Error ? error?.message : error}` + ); + } + } + + async handleLedgerAlert(emailDto: EmailDto): Promise { + const now = Date.now(); + + // 1. Avoid more than once every 2 hours + if (this.lastAlertTime && now - this.lastAlertTime < 2 * 60 * 60 * 1000) { + this.logger.log(`ALERT EMAIL ALREADY SENT at ${new Date(this.lastAlertTime).toISOString()}`); + return; } - async storeObject(payload: {persistent: boolean, storeObj: unknown}): Promise { - try { - const uuid = uuidv4(); - const uploadResult:S3.ManagedUpload.SendData = await this.awsService.storeObject(payload.persistent, uuid, payload.storeObj); - const url: string = `${process.env.SHORTENED_URL_DOMAIN}/${uploadResult.Key}`; - return url; - } catch (error) { - this.logger.error(error); - throw new Error('An error occurred while uploading data to S3. Error::::::'); + // 2. If a retry flow is already in progress, do NOT start another + if (this.isSendingAlert) { + this.logger.log('Alert email sending already in progress, skipping...'); + return; + } + + const platformConfigData = await this.utilitiesRepository.getPlatformConfigDetails(); + if (!platformConfigData) { + throw new NotFoundException(ResponseMessages.issuance.error.platformConfigNotFound); + } + + emailDto.emailFrom = platformConfigData?.emailFrom; + + // 3. Start async retry flow — do not block the caller + this.isSendingAlert = true; + this.sendWithRetry(emailDto).finally(() => { + this.isSendingAlert = false; + }); + } + + private async sendWithRetry(emailDto: EmailDto, retries = 3, delayMs = 3000): Promise { + for (let attempt = 1; attempt <= retries; attempt++) { + try { + const result = await this.emailService.sendEmail(emailDto); + + if (true !== result) { + throw new Error('Email not sent'); } + + // Success + this.lastAlertTime = Date.now(); + this.logger.log(`ALERT EMAIL SENT SUCCESSFULLY (attempt ${attempt})`); + return; + } catch (err) { + this.logger.error( + `Email send failed (attempt ${attempt} of ${retries})`, + err instanceof Error ? err.stack : err + ); + + // If last attempt → throw + if (attempt === retries) { + this.logger.error('All email retry attempts failed.'); + return; + } + + // Wait before retrying + await new Promise((resolve) => setTimeout(resolve, delayMs)); + } } + } } diff --git a/libs/common/src/common.constant.ts b/libs/common/src/common.constant.ts index 7de2755a3..62cd4c7f2 100644 --- a/libs/common/src/common.constant.ts +++ b/libs/common/src/common.constant.ts @@ -383,7 +383,9 @@ export enum CommonConstants { GET_VERIFIED_PROOF = 'get-verified-proof', GET_QUESTION_ANSWER_RECORD = 'get-question-answer-record', SEND_QUESTION = 'send-question', - SEND_BASIC_MESSAGE = 'send-basic-message' + SEND_BASIC_MESSAGE = 'send-basic-message', + + AFFECTED_RECORDS_THRESHOLD_PERCENTAGE_FOR_DB_ALERT = 30 } export const MICRO_SERVICE_NAME = Symbol('MICRO_SERVICE_NAME'); export const ATTRIBUTE_NAME_REGEX = /\['(.*?)'\]/; diff --git a/libs/common/src/dtos/email.dto.ts b/libs/common/src/dtos/email.dto.ts index e395da884..58bc44cd2 100644 --- a/libs/common/src/dtos/email.dto.ts +++ b/libs/common/src/dtos/email.dto.ts @@ -1,17 +1,17 @@ export class EmailDto { - emailFrom: string; - emailTo: string; - emailSubject: string; - emailText: string; - emailHtml: string; - emailAttachments?: AttachmentJSON[]; + emailFrom: string; + emailTo: string | string[]; + emailSubject: string; + emailText: string; + emailHtml: string; + emailAttachments?: AttachmentJSON[]; } interface AttachmentJSON { - content: string; - filename: string; - contentType: string; - type?: string; - disposition?: string; - content_id?: string; - } \ No newline at end of file + content: string; + filename: string; + contentType: string; + type?: string; + disposition?: string; + content_id?: string; +} diff --git a/libs/common/src/resend-helper-file.ts b/libs/common/src/resend-helper-file.ts index 2beccbb0c..7eb4f6fd5 100644 --- a/libs/common/src/resend-helper-file.ts +++ b/libs/common/src/resend-helper-file.ts @@ -10,7 +10,7 @@ const apiKey = process.env.RESEND_API_KEY; if (!apiKey) { throw new Error('Missing RESEND_API_KEY in environment variables.'); } -const resend = new Resend(process.env.RESEND_API_KEY); +const resend = new Resend(apiKey); export const sendWithResend = async (emailDto: EmailDto): Promise => { try { diff --git a/libs/org-roles/src/org-roles.module.ts b/libs/org-roles/src/org-roles.module.ts index a53dff126..e2757c8b4 100644 --- a/libs/org-roles/src/org-roles.module.ts +++ b/libs/org-roles/src/org-roles.module.ts @@ -1,11 +1,12 @@ -import { PrismaService } from '@credebl/prisma-service'; +import { PrismaServiceModule } from '@credebl/prisma-service'; import { Logger } from '@nestjs/common'; import { Module } from '@nestjs/common'; import { OrgRolesRepository } from '../repositories'; import { OrgRolesService } from './org-roles.service'; @Module({ - providers: [OrgRolesService, OrgRolesRepository, Logger, PrismaService], + imports: [PrismaServiceModule], + providers: [OrgRolesService, OrgRolesRepository, Logger], exports: [OrgRolesService] }) export class OrgRolesModule {} diff --git a/libs/prisma-service/prisma/migrations/20251125062851_add_ledger_null_trigger/migration.sql b/libs/prisma-service/prisma/migrations/20251125062851_add_ledger_null_trigger/migration.sql new file mode 100644 index 000000000..8c0e1073b --- /dev/null +++ b/libs/prisma-service/prisma/migrations/20251125062851_add_ledger_null_trigger/migration.sql @@ -0,0 +1,21 @@ +-- Create the function +CREATE OR REPLACE FUNCTION alert_ledger_null() +RETURNS trigger AS $$ +BEGIN + IF NEW."ledgerId" IS NULL THEN + PERFORM pg_notify('ledger_null', json_build_object( + 'agentId', NEW.id, + 'orgId', NEW."orgId", + 'timestamp', now() + )::text); + END IF; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Create the trigger +CREATE TRIGGER ledger_null_trigger +AFTER UPDATE ON org_agents +FOR EACH ROW +WHEN (NEW."ledgerId" IS NULL AND OLD."ledgerId" IS NOT NULL) +EXECUTE FUNCTION alert_ledger_null(); diff --git a/libs/prisma-service/src/prisma-service.module.ts b/libs/prisma-service/src/prisma-service.module.ts index 2441ae4ad..c839f6d86 100644 --- a/libs/prisma-service/src/prisma-service.module.ts +++ b/libs/prisma-service/src/prisma-service.module.ts @@ -1,7 +1,9 @@ import { Logger, Module } from '@nestjs/common'; import { PrismaService } from './prisma-service.service'; +import { CommonModule } from '@credebl/common'; @Module({ + imports: [CommonModule], providers: [PrismaService, Logger], exports: [PrismaService] }) diff --git a/libs/prisma-service/src/prisma-service.service.ts b/libs/prisma-service/src/prisma-service.service.ts index 1a828eefd..f0a78eeeb 100644 --- a/libs/prisma-service/src/prisma-service.service.ts +++ b/libs/prisma-service/src/prisma-service.service.ts @@ -24,7 +24,6 @@ export class PrismaService extends PrismaClient implements OnModuleInit { .map((l) => l.trim()); } - UserDevicesRepository: any; async onModuleInit(): Promise { await this.$connect(); if (this.enable('error')) { diff --git a/package.json b/package.json index 418a81be2..c67063f19 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "passport-local": "^1.0.0", "path": "^0.12.7", "pdfkit": "^0.13.0", - "pg": "^8.11.2", + "pg": "^8.16.3", "puppeteer": "^21.5.0", "qrcode": "^1.5.3", "qs": "^6.11.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8be044baf..f3575872d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -318,7 +318,7 @@ importers: specifier: ^0.13.0 version: 0.13.0 pg: - specifier: ^8.11.2 + specifier: ^8.16.3 version: 8.16.3 puppeteer: specifier: ^21.5.0 @@ -1789,6 +1789,7 @@ packages: '@smithy/core@3.18.0': resolution: {integrity: sha512-vGSDXOJFZgOPTatSI1ly7Gwyy/d/R9zh2TO3y0JZ0uut5qQ88p9IaWaZYIWSSqtdekNM4CGok/JppxbAff4KcQ==} engines: {node: '>=18.0.0'} + deprecated: Please upgrade your lockfile to use the latest 3.x version of @smithy/core for various fixes, see https://github.com/smithy-lang/smithy-typescript/blob/main/packages/core/CHANGELOG.md '@smithy/credential-provider-imds@4.2.5': resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} @@ -5451,12 +5452,12 @@ packages: puppeteer@21.0.1: resolution: {integrity: sha512-KTjmSdPZ6bMkq3EbAzAUhcB3gMDXvdwd6912rxG9hNtjwRJzHSA568vh6vIbO2WQeNmozRdt1LtiUMLSWfeMrg==} engines: {node: '>=16.3.0'} - deprecated: < 22.8.2 is no longer supported + deprecated: < 24.10.2 is no longer supported puppeteer@21.11.0: resolution: {integrity: sha512-9jTHuYe22TD3sNxy0nEIzC7ZrlRnDgeX3xPkbS7PnbdwYjl2o/z/YuCrRBwezdKpbTDTJ4VqIggzNyeRcKq3cg==} engines: {node: '>=16.13.2'} - deprecated: < 22.8.2 is no longer supported + deprecated: < 24.10.2 is no longer supported hasBin: true pure-rand@6.1.0: