mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 00:38:11 +00:00
Production checklists, issue fixes
This commit is contained in:
@@ -548,7 +548,7 @@ export class PassengerAuthService {
|
||||
|
||||
await this.dataSource.query(`DELETE FROM iam.users WHERE id = $1`, [iamUserId]);
|
||||
} catch (err) {
|
||||
console.error('[PassengerAuthService] IAM compensating cleanup failed for', email, (err as Error).message);
|
||||
this.logger.error(`[PassengerAuthService] IAM compensating cleanup failed for ${email}`, (err as Error).message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,10 @@ export class FleetService {
|
||||
}
|
||||
|
||||
getTrains() {
|
||||
return this.prisma.train.findMany({ include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } } });
|
||||
return this.prisma.train.findMany({
|
||||
where: { isActive: true },
|
||||
include: { schedules: { take: 5, orderBy: { departureAt: 'desc' } } },
|
||||
});
|
||||
}
|
||||
|
||||
createTrain(dto: CreateTrainDto) {
|
||||
@@ -462,6 +465,7 @@ export class FleetService {
|
||||
return this.prisma.coach.update({
|
||||
where: { id },
|
||||
data: {
|
||||
number: dto.number,
|
||||
arrangement: dto.arrangement,
|
||||
capacity: dto.capacity,
|
||||
status: dto.status,
|
||||
@@ -540,6 +544,7 @@ export class FleetService {
|
||||
]);
|
||||
if (!schedule) throw new NotFoundException('Schedule not found');
|
||||
if (!coach) throw new NotFoundException('Coach not found');
|
||||
if (coach.status !== 'ACTIVE') throw new BadRequestException('Coach is not active');
|
||||
return this.prisma.coachAssignment.create({ data: dto });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, HttpStatus, Res } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation } from '@nestjs/swagger';
|
||||
import { SkipThrottle } from '@nestjs/throttler';
|
||||
import { IsPublic } from '@tria-plc/api-common/modules/auth/decorators/public.decorator';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
import { Response } from 'express';
|
||||
|
||||
@ApiTags('Health')
|
||||
@Controller('health')
|
||||
@@ -20,17 +21,17 @@ export class HealthController {
|
||||
@Get('ready')
|
||||
@IsPublic()
|
||||
@ApiOperation({ summary: 'Readiness probe — checks database connectivity' })
|
||||
async readiness() {
|
||||
async readiness(@Res() res: Response) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
return {
|
||||
return res.status(HttpStatus.OK).json({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: { database: { status: 'ok', latencyMs: Date.now() - start } },
|
||||
};
|
||||
});
|
||||
} catch (err) {
|
||||
return {
|
||||
return res.status(HttpStatus.SERVICE_UNAVAILABLE).json({
|
||||
status: 'error',
|
||||
timestamp: new Date().toISOString(),
|
||||
checks: {
|
||||
@@ -40,7 +41,7 @@ export class HealthController {
|
||||
error: err instanceof Error ? err.message : 'Unknown error',
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { Injectable, Logger, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
@@ -25,6 +25,7 @@ type IamUserRow = {
|
||||
|
||||
@Injectable()
|
||||
export class PassengersService {
|
||||
private readonly logger = new Logger(PassengersService.name);
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
@InjectDataSource() private readonly dataSource: DataSource,
|
||||
@@ -373,7 +374,7 @@ export class PassengersService {
|
||||
verifiedData = verification.passengerData;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Fayda verification failed, using manual data:', error);
|
||||
this.logger.warn('Fayda verification failed, using manual data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
@@ -6,6 +6,7 @@ import { GenerateReportDto, ReportType } from './reports.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ReportsService {
|
||||
private readonly logger = new Logger(ReportsService.name);
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
@InjectDataSource() private dataSource: DataSource,
|
||||
@@ -60,8 +61,6 @@ export class ReportsService {
|
||||
include: { paymentIntent: true }
|
||||
});
|
||||
|
||||
console.log(`[Reports] Revenue Report: Found ${bookings.length} bookings between ${dateFrom} and ${dateTo}`);
|
||||
|
||||
const totalRevenue = bookings.reduce((sum, b) => sum + b.totalMinor, 0);
|
||||
const byPaymentMethod = bookings.reduce((acc, b) => {
|
||||
const method = b.paymentIntent?.method ?? 'UNKNOWN';
|
||||
|
||||
@@ -100,10 +100,15 @@ export class SchedulesService {
|
||||
const arr = parseEthiopianTime(dto.arrivalAt);
|
||||
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
|
||||
|
||||
const route = await this.prisma.route.findUnique({
|
||||
where: { id: dto.routeId },
|
||||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||||
});
|
||||
const [train, route] = await Promise.all([
|
||||
this.prisma.train.findUnique({ where: { id: dto.trainId } }),
|
||||
this.prisma.route.findUnique({
|
||||
where: { id: dto.routeId },
|
||||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||||
}),
|
||||
]);
|
||||
if (!train) throw new NotFoundException('Train not found');
|
||||
if (!train.isActive) throw new BadRequestException('Train is not active');
|
||||
if (!route) throw new NotFoundException('Route not found');
|
||||
if (!route.active) throw new BadRequestException('Route is not active');
|
||||
if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops');
|
||||
@@ -247,10 +252,15 @@ export class SchedulesService {
|
||||
const arr = parseEthiopianTime(dto.arrivalAt);
|
||||
if (arr <= dep) throw new BadRequestException('arrivalAt must be after departureAt');
|
||||
|
||||
const route = await this.prisma.route.findUnique({
|
||||
where: { id: dto.routeId },
|
||||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||||
});
|
||||
const [train, route] = await Promise.all([
|
||||
this.prisma.train.findUnique({ where: { id: dto.trainId } }),
|
||||
this.prisma.route.findUnique({
|
||||
where: { id: dto.routeId },
|
||||
include: { stops: { orderBy: { sequence: 'asc' } } },
|
||||
}),
|
||||
]);
|
||||
if (!train) throw new NotFoundException('Train not found');
|
||||
if (!train.isActive) throw new BadRequestException('Train is not active');
|
||||
if (!route) throw new NotFoundException('Route not found');
|
||||
if (!route.active) throw new BadRequestException('Route is not active');
|
||||
if (route.stops.length < 2) throw new BadRequestException('Route must have at least 2 stops');
|
||||
@@ -540,6 +550,8 @@ export class SchedulesService {
|
||||
const coachIds = coaches.map(c => c.coachId);
|
||||
const existingCoaches = await this.prisma.coach.findMany({ where: { id: { in: coachIds } } });
|
||||
if (existingCoaches.length !== coachIds.length) throw new NotFoundException('One or more coaches not found');
|
||||
const inactiveCoach = existingCoaches.find(c => c.status !== 'ACTIVE');
|
||||
if (inactiveCoach) throw new BadRequestException(`Coach ${inactiveCoach.number} is not active`);
|
||||
|
||||
await this.prisma.coachAssignment.deleteMany({ where: { scheduleId } });
|
||||
|
||||
|
||||
@@ -6,12 +6,16 @@ export class SeatClassesService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
listSeatClasses() {
|
||||
return this.prisma.seatClass.findMany({ orderBy: { createdAt: 'asc' } });
|
||||
return this.prisma.seatClass.findMany({
|
||||
where: { isActive: true },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
}
|
||||
|
||||
async getSeatClass(id: string) {
|
||||
const sc = await this.prisma.seatClass.findUnique({ where: { id } });
|
||||
if (!sc) throw new NotFoundException('SeatClass not found');
|
||||
if (!sc.isActive) throw new NotFoundException('SeatClass is not active');
|
||||
return sc;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,11 @@ export class StationsService {
|
||||
where.countryCode = filters.country;
|
||||
}
|
||||
|
||||
// Default to operational stations only; allow explicit override (e.g. back-office)
|
||||
if (filters.operational !== undefined && filters.operational !== '') {
|
||||
where.isOperational = filters.operational === 'true';
|
||||
} else {
|
||||
where.isOperational = true;
|
||||
}
|
||||
|
||||
return this.prisma.station.findMany({
|
||||
@@ -46,6 +49,7 @@ export class StationsService {
|
||||
async findOne(id: string) {
|
||||
const s = await this.prisma.station.findUnique({ where: { id } });
|
||||
if (!s) throw new NotFoundException('Station not found');
|
||||
if (!s.isOperational) throw new NotFoundException('Station is not operational');
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
@@ -99,18 +99,11 @@ export class TicketsService {
|
||||
let guestEmail = null;
|
||||
const matchingProfile = t.booking?.passenger?.travelerProfiles?.find((tp: any) => tp.fullName === t.passengerName);
|
||||
|
||||
// DEBUG: Log to see what we're getting
|
||||
this.logger.debug(`Ticket ${t.id}: passengerName=${t.passengerName}, profiles count=${t.booking?.passenger?.travelerProfiles?.length || 0}, matchingProfile=${!!matchingProfile}`);
|
||||
if (matchingProfile) {
|
||||
this.logger.debug(`Matching profile notes: ${matchingProfile.notes}`);
|
||||
}
|
||||
|
||||
if (matchingProfile?.notes) {
|
||||
try {
|
||||
const notesData = JSON.parse(matchingProfile.notes);
|
||||
guestPhone = notesData.phone || null;
|
||||
guestEmail = notesData.email || null;
|
||||
this.logger.debug(`Extracted from notes: phone=${guestPhone}, email=${guestEmail}`);
|
||||
} catch (err) {
|
||||
this.logger.error(`Failed to parse notes JSON: ${err}`);
|
||||
}
|
||||
@@ -120,13 +113,11 @@ export class TicketsService {
|
||||
if (!guestPhone) guestPhone = t.booking?.contactPhone;
|
||||
if (!guestEmail) guestEmail = t.booking?.contactEmail;
|
||||
|
||||
this.logger.debug(`Final values: phone=${guestPhone}, email=${guestEmail}`);
|
||||
|
||||
const passengerInfo = iam
|
||||
? { fullName: iam.name?.en ?? iam.name?.am ?? null, email: iam.email, phone: iam.phone_number }
|
||||
: { fullName: 'Guest', email: guestEmail, phone: guestPhone };
|
||||
|
||||
this.logger.debug(`Final passenger info: ${JSON.stringify(passengerInfo)}`);
|
||||
|
||||
return {
|
||||
id: t.id,
|
||||
@@ -596,8 +587,7 @@ export class TicketsService {
|
||||
}
|
||||
|
||||
private async fireBoardingPassNotification(booking: any, ticket: any, leg: string | null) {
|
||||
// TODO: Implement notification logic
|
||||
console.log(`Boarding pass notification for booking ${booking.bookingRef}, leg: ${leg}`);
|
||||
this.logger.log(`Boarding pass notification for booking ${booking.bookingRef}, leg: ${leg}`);
|
||||
}
|
||||
|
||||
async getValidationLogs(ticketId: string) {
|
||||
@@ -687,4 +677,4 @@ export class TicketsService {
|
||||
if (!ticket) throw new NotFoundException('Ticket not found');
|
||||
return this.prisma.ticket.update({ where: { id }, data: { status: 'ACTIVE' } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user