mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-29 09:30:59 +00:00
Initial commit of edr-passenger-api alpha version
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
import { Controller, Get, Param, Patch, UseGuards } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
import { JwtGuard } from '../../common/jwt.guard';
|
||||
|
||||
@ApiTags('Notifications')
|
||||
@Controller('notifications')
|
||||
@UseGuards(JwtGuard)
|
||||
@ApiBearerAuth('JWT-auth')
|
||||
export class NotificationsController {
|
||||
constructor(private service: NotificationsService) {}
|
||||
|
||||
@Get(':passengerId')
|
||||
@ApiOperation({ summary: 'Get notifications for passenger' })
|
||||
getForPassenger(@Param('passengerId') id: string) { return this.service.getForPassenger(id); }
|
||||
|
||||
@Patch(':id/read')
|
||||
@ApiOperation({ summary: 'Mark notification as read' })
|
||||
markRead(@Param('id') id: string) { return this.service.markRead(id); }
|
||||
|
||||
@Patch(':passengerId/read-all')
|
||||
@ApiOperation({ summary: 'Mark all notifications as read' })
|
||||
markAllRead(@Param('passengerId') id: string) { return this.service.markAllRead(id); }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { IsString, IsEnum, IsOptional } from 'class-validator';
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||
|
||||
export enum NotificationCategoryEnum {
|
||||
BOOKING = 'BOOKING',
|
||||
PAYMENT = 'PAYMENT',
|
||||
DISRUPTION = 'DISRUPTION',
|
||||
PROMOTION = 'PROMOTION',
|
||||
SYSTEM = 'SYSTEM',
|
||||
}
|
||||
|
||||
export class SendNotificationDto {
|
||||
@ApiProperty() @IsString() passengerId: string;
|
||||
@ApiProperty({ example: 'Platform Change' }) @IsString() title: string;
|
||||
@ApiProperty({ example: 'Your train departs from Platform 3' }) @IsString() body: string;
|
||||
@ApiProperty({ enum: NotificationCategoryEnum }) @IsEnum(NotificationCategoryEnum) category: NotificationCategoryEnum;
|
||||
@ApiPropertyOptional({ example: 'edr://tickets/tkt_01' }) @IsOptional() @IsString() deepLink?: string;
|
||||
@ApiPropertyOptional() @IsOptional() metadata?: Record<string, any>;
|
||||
}
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { Module } from '@nestjs/common';
|
||||
import { NotificationsController } from './notifications.controller';
|
||||
import { NotificationsService } from './notifications.service';
|
||||
|
||||
import { NotificationsService } from "./notifications.service";
|
||||
|
||||
@Module({
|
||||
providers: [NotificationsService],
|
||||
exports: [NotificationsService],
|
||||
})
|
||||
@Module({ controllers: [NotificationsController], providers: [NotificationsService], exports: [NotificationsService] })
|
||||
export class NotificationsModule {}
|
||||
|
||||
@@ -1,14 +1,45 @@
|
||||
import { Injectable, Logger } from "@nestjs/common";
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
import { SendNotificationDto, NotificationCategoryEnum } from './notifications.dto';
|
||||
import * as sgMail from '@sendgrid/mail';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationsService {
|
||||
private readonly logger = new Logger(NotificationsService.name);
|
||||
|
||||
/**
|
||||
* Dispatch a notification to a passenger (booking confirmation, schedule change, etc.).
|
||||
* TODO: wire to email/SMS provider via a mailer service.
|
||||
*/
|
||||
async send(recipient: string, subject: string, body: string): Promise<void> {
|
||||
this.logger.log(`[notify] ${recipient} :: ${subject} :: ${body}`);
|
||||
constructor(private prisma: PrismaService) {
|
||||
if (process.env.SENDGRID_API_KEY) sgMail.setApiKey(process.env.SENDGRID_API_KEY);
|
||||
}
|
||||
}
|
||||
|
||||
private sanitize(value: string): string {
|
||||
return value.replace(/[\r\n]/g, ' ').replace(/[<>&"']/g, (c) => ({ '<': '<', '>': '>', '&': '&', '"': '"', "'": ''' }[c] ?? c));
|
||||
}
|
||||
|
||||
async send(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.sendEmail(passenger.user.email, this.sanitize(dto.title), this.sanitize(dto.body));
|
||||
return notification;
|
||||
}
|
||||
|
||||
getForPassenger(passengerId: string) { return this.prisma.notification.findMany({ where: { passengerId }, orderBy: { createdAt: 'desc' }, take: 50 }); }
|
||||
|
||||
markRead(id: string) { return this.prisma.notification.update({ where: { id }, data: { read: true } }); }
|
||||
|
||||
async markAllRead(passengerId: string) { await this.prisma.notification.updateMany({ where: { passengerId, read: false }, data: { read: true } }); return { updated: true }; }
|
||||
|
||||
@OnEvent('booking.created')
|
||||
async onBookingCreated(payload: any) {
|
||||
await this.send({ passengerId: payload.booking.passengerId, title: 'Booking Created', body: `Booking ${payload.booking.bookingRef} created. Complete payment within 15 minutes.`, category: NotificationCategoryEnum.BOOKING, deepLink: `edr://bookings/${payload.booking.bookingRef}`, metadata: { bookingRef: payload.booking.bookingRef } });
|
||||
}
|
||||
|
||||
@OnEvent('payment.succeeded')
|
||||
async onPaymentSucceeded(payload: any) {
|
||||
await this.send({ passengerId: payload.booking.passengerId, title: 'Payment Successful', body: `Your ticket for ${payload.booking.bookingRef} is confirmed. Have a great journey!`, category: NotificationCategoryEnum.PAYMENT, deepLink: `edr://tickets/${payload.booking.bookingRef}`, metadata: { bookingRef: payload.booking.bookingRef } });
|
||||
}
|
||||
|
||||
private async sendEmail(to: string, subject: string, text: string) {
|
||||
if (!process.env.SENDGRID_API_KEY) { console.log(`[EMAIL] To: ${to} | Subject: ${subject}`); return; }
|
||||
try { await sgMail.send({ to, from: process.env.SENDGRID_FROM_EMAIL || 'noreply@edr-platform.com', subject, text }); }
|
||||
catch (e) { console.error('[EMAIL] Send error:', String(e instanceof Error ? e.message : e).replace(/[\r\n<>&"']/g, ' ')); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user