diff --git a/apps/edr-passenger-api/package.json b/apps/edr-passenger-api/package.json index 84ea472d4..8ef7e669a 100644 --- a/apps/edr-passenger-api/package.json +++ b/apps/edr-passenger-api/package.json @@ -34,7 +34,6 @@ "@nestjs/schedule": "^6.1.3", "@nestjs/swagger": "^7.4.0", "@prisma/client": "^6.19.3", - "@sendgrid/mail": "^8.1.0", "axios": "^1.7.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", diff --git a/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts b/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts index c7db1a0a4..b7df05276 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notification.adapters.ts @@ -1,199 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import * as sgMail from '@sendgrid/mail'; -import { HttpService } from '@nestjs/axios'; -import { firstValueFrom } from 'rxjs'; export interface NotificationChannel { send(recipient: string, subject: string, body: string, context?: Record): Promise; } -@Injectable() -export class EmailAdapter implements NotificationChannel { - private readonly logger = new Logger(EmailAdapter.name); - - constructor(private readonly config: ConfigService) { - const apiKey = this.config.get('SENDGRID_API_KEY'); - if (apiKey) { - sgMail.setApiKey(apiKey); - this.logger.log('SendGrid Email adapter initialized'); - } else { - this.logger.warn('SENDGRID_API_KEY not configured - emails will be logged only'); - } - } - - async send( - recipient: string, - subject: string, - body: string, - context?: Record, - ): Promise { - const apiKey = this.config.get('SENDGRID_API_KEY'); - const fromEmail = this.config.get('SENDGRID_FROM_EMAIL') || 'noreply@edr-platform.com'; - - if (!apiKey) { - this.logger.log(`[EMAIL MOCK] To: ${recipient} | Subject: ${subject} | Body: ${body.substring(0, 100)}`); - return true; - } - - try { - const msg: sgMail.MailDataRequired = { - to: recipient, - from: fromEmail, - subject, - text: body, - html: this.formatHtml(body, context), - }; - - await sgMail.send(msg); - this.logger.log(`Email sent successfully to ${recipient}`); - return true; - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - this.logger.error(`Failed to send email to ${recipient}: ${message}`); - return false; - } - } - - private formatHtml(body: string, context?: Record): string { - const contextHtml = context - ? `
- ${JSON.stringify(context, null, 2)} -
` - : ''; - - return ` - - - - - - - -
-
-

Ethio-Djibouti Railway

-
-
- ${body.replace(/\n/g, '
')} - ${contextHtml} -
- -
- - - `; - } -} - -@Injectable() -export class SmsAdapter implements NotificationChannel { - private readonly logger = new Logger(SmsAdapter.name); - - constructor( - private readonly config: ConfigService, - private readonly http: HttpService, - ) { - const provider = this.config.get('SMS_PROVIDER'); - this.logger.log(`SMS adapter initialized with provider: ${provider || 'MOCK'}`); - } - - async send( - recipient: string, - subject: string, - body: string, - _context?: Record, - ): Promise { - const provider = this.config.get('SMS_PROVIDER'); - const apiKey = this.config.get('SMS_API_KEY'); - - if (!provider || !apiKey) { - this.logger.log(`[SMS MOCK] To: ${recipient} | Message: ${body.substring(0, 100)}`); - return true; - } - - try { - switch (provider.toLowerCase()) { - case 'twilio': - return await this.sendViaTwilio(recipient, body); - case 'africastalking': - return await this.sendViaAfricasTalking(recipient, body); - default: - this.logger.warn(`Unknown SMS provider: ${provider}`); - return false; - } - } catch (err) { - const message = err instanceof Error ? err.message : String(err); - this.logger.error(`Failed to send SMS to ${recipient}: ${message}`); - return false; - } - } - - private async sendViaTwilio(to: string, body: string): Promise { - const accountSid = this.config.get('TWILIO_ACCOUNT_SID'); - const authToken = this.config.get('TWILIO_AUTH_TOKEN'); - const fromNumber = this.config.get('TWILIO_FROM_NUMBER'); - - const url = `https://api.twilio.com/2010-04-01/Accounts/${accountSid}/Messages.json`; - const auth = Buffer.from(`${accountSid}:${authToken}`).toString('base64'); - - const response = await firstValueFrom( - this.http.post( - url, - new URLSearchParams({ - To: to, - From: fromNumber || '', - Body: body, - }), - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': `Basic ${auth}`, - }, - }, - ), - ); - - return response.status === 201; - } - - private async sendViaAfricasTalking(to: string, body: string): Promise { - const apiKey = this.config.get('SMS_API_KEY'); - const username = this.config.get('AFRICASTALKING_USERNAME'); - const from = this.config.get('AFRICASTALKING_FROM'); - - const url = 'https://api.africastalking.com/version1/messaging'; - - const response = await firstValueFrom( - this.http.post( - url, - new URLSearchParams({ - username: username || '', - to, - message: body, - from: from || '', - }), - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - 'apiKey': apiKey || '', - }, - }, - ), - ); - - return response.status === 201; - } -} - @Injectable() export class PushAdapter implements NotificationChannel { private readonly logger = new Logger(PushAdapter.name); diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts index c72552015..0ee178a03 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.module.ts @@ -1,15 +1,13 @@ import { Module } from '@nestjs/common'; -import { HttpModule } from '@nestjs/axios'; import { ClientsModule, Transport } from '@nestjs/microservices'; import { NotificationsController } from './notifications.controller'; import { NotificationsService } from './notifications.service'; -import { EmailAdapter, SmsAdapter, PushAdapter } from './notification.adapters'; +import { PushAdapter } from './notification.adapters'; import { EmailClientService } from './email-client.service'; import { SmsClientService } from './sms-client.service'; @Module({ imports: [ - HttpModule.register({ timeout: 10_000 }), ClientsModule.register([ { name: 'EMAIL_SERVICE', @@ -34,8 +32,6 @@ import { SmsClientService } from './sms-client.service'; controllers: [NotificationsController], providers: [ NotificationsService, - EmailAdapter, - SmsAdapter, PushAdapter, EmailClientService, SmsClientService, diff --git a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts index 1af7c7952..3f4e25984 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts @@ -1,7 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; import { OnEvent } from '@nestjs/event-emitter'; import { PrismaService } from '../../common/prisma.service'; -import { SendNotificationDto, NotificationCategoryEnum } from './notifications.dto'; import { PushAdapter, NotificationChannel } from './notification.adapters'; import { EmailClientService } from './email-client.service'; import { SmsClientService } from './sms-client.service'; @@ -83,37 +82,6 @@ export class NotificationsService { return { sent: sentChannels.length > 0, channels: sentChannels }; } - /** - * Legacy method for backward compatibility - */ - async sendDirect(dto: SendNotificationDto) { - const notification = await this.prisma.notification.create({ - data: { - passengerId: dto.passengerId, - title: dto.title, - body: dto.body, - category: dto.category as any, - deepLink: dto.deepLink, - metadata: dto.metadata, - }, - }); - - const passenger = await this.prisma.passenger.findUnique({ - where: { id: dto.passengerId }, - include: { user: true }, - }); - - if (passenger?.user) { - await this.emailClient.sendEmail({ - to: passenger.user.email, - subject: this.sanitize(dto.title), - text: this.sanitize(dto.body), - }); - } - - return notification; - } - private async createInAppNotification( recipient: string, title: string, @@ -221,12 +189,6 @@ export class NotificationsService { } } - private sanitize(value: string): string { - return value - .replace(/[\r\n]/g, ' ') - .replace(/[<>&"']/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c] ?? c)); - } - getForPassenger(passengerId: string) { return this.prisma.notification.findMany({ where: { passengerId }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c1b29edac..59c0e1d63 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -452,9 +452,6 @@ importers: '@prisma/client': specifier: ^6.19.3 version: 6.19.3(prisma@6.19.3(typescript@5.9.3))(typescript@5.9.3) - '@sendgrid/mail': - specifier: ^8.1.0 - version: 8.1.6 axios: specifier: ^1.7.7 version: 1.17.0 @@ -3756,18 +3753,6 @@ packages: '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sendgrid/client@8.1.6': - resolution: {integrity: sha512-/BHu0hqwXNHr2aLhcXU7RmmlVqrdfrbY9KpaNj00KZHlVOVoRxRVrpOCabIB+91ISXJ6+mLM9vpaVUhK6TwBWA==} - engines: {node: '>=12.*'} - - '@sendgrid/helpers@8.0.0': - resolution: {integrity: sha512-Ze7WuW2Xzy5GT5WRx+yEv89fsg/pgy3T1E3FS0QEx0/VvRmigMZ5qyVGhJz4SxomegDkzXv/i0aFPpHKN8qdAA==} - engines: {node: '>= 12.0.0'} - - '@sendgrid/mail@8.1.6': - resolution: {integrity: sha512-/ZqxUvKeEztU9drOoPC/8opEPOk+jLlB2q4+xpx6HVLq6aFu3pMpalkTpAQz8XfRfpLp8O25bh6pGPcHDCYpqg==} - engines: {node: '>=12.*'} - '@sinclair/typebox@0.27.10': resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} @@ -16642,26 +16627,6 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@sendgrid/client@8.1.6': - dependencies: - '@sendgrid/helpers': 8.0.0 - axios: 1.17.0 - transitivePeerDependencies: - - debug - - supports-color - - '@sendgrid/helpers@8.0.0': - dependencies: - deepmerge: 4.3.1 - - '@sendgrid/mail@8.1.6': - dependencies: - '@sendgrid/client': 8.1.6 - '@sendgrid/helpers': 8.0.0 - transitivePeerDependencies: - - debug - - supports-color - '@sinclair/typebox@0.27.10': {} '@sindresorhus/merge-streams@4.0.0': {}