mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 23:00:57 +00:00
refactor(iam): remove remaining prisma.user refs from passenger side align RegisterDto to IAM body
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
|
||||
import { InjectDataSource } from '@nestjs/typeorm';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { PrismaService } from '../../common/prisma.service';
|
||||
import { CreateTravelerProfileDto, CreateSavedRouteDto, RegisterPassengerDto } from './passengers.dto';
|
||||
import { VerifaydaService } from '../verifayda/verifayda.service';
|
||||
@@ -10,36 +12,68 @@ interface PassengerFilters {
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
type IamUserRow = {
|
||||
id: string;
|
||||
email: string;
|
||||
name: { en: string; am: string } | null;
|
||||
phone_number: string | null;
|
||||
metadata: Record<string, any> | null;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PassengersService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private verifaydaService: VerifaydaService,
|
||||
private readonly prisma: PrismaService,
|
||||
@InjectDataSource() private readonly dataSource: DataSource,
|
||||
private readonly verifaydaService: VerifaydaService,
|
||||
) {}
|
||||
|
||||
async findAll(filters: PassengerFilters = {}) {
|
||||
const { search, verified, page = 1, pageSize = 20 } = filters;
|
||||
const skip = (page - 1) * pageSize;
|
||||
|
||||
|
||||
let iamUserIdFilter: string[] | null = null;
|
||||
|
||||
if (search || verified !== undefined) {
|
||||
const conditions: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (search) {
|
||||
conditions.push(`(
|
||||
u.email ILIKE $${idx} OR
|
||||
u.phone_number ILIKE $${idx} OR
|
||||
(u.name->>'en') ILIKE $${idx} OR
|
||||
(u.name->>'am') ILIKE $${idx}
|
||||
)`);
|
||||
params.push(`%${search}%`);
|
||||
idx++;
|
||||
}
|
||||
|
||||
if (verified !== undefined) {
|
||||
if (verified) {
|
||||
conditions.push(`u.metadata->>'faydaVerified' = 'true'`);
|
||||
} else {
|
||||
conditions.push(`(u.metadata IS NULL OR u.metadata->>'faydaVerified' IS DISTINCT FROM 'true')`);
|
||||
}
|
||||
}
|
||||
|
||||
const rows = await this.dataSource.query<{ id: string }[]>(
|
||||
`SELECT u.id FROM iam.users u WHERE ${conditions.join(' AND ')}`,
|
||||
params,
|
||||
);
|
||||
iamUserIdFilter = rows.map(r => r.id);
|
||||
|
||||
if (iamUserIdFilter.length === 0) {
|
||||
return { items: [], meta: { page, pageSize, total: 0, totalPages: 0 } };
|
||||
}
|
||||
}
|
||||
|
||||
const where: any = {};
|
||||
|
||||
if (search) {
|
||||
where.user = {
|
||||
OR: [
|
||||
{ fullName: { contains: search, mode: 'insensitive' } },
|
||||
{ email: { contains: search, mode: 'insensitive' } },
|
||||
{ phone: { contains: search, mode: 'insensitive' } },
|
||||
],
|
||||
};
|
||||
if (iamUserIdFilter) {
|
||||
where.iamUserId = { in: iamUserIdFilter };
|
||||
}
|
||||
|
||||
if (verified !== undefined) {
|
||||
where.user = {
|
||||
...where.user,
|
||||
nationalId: verified ? { not: null } : null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
const [items, total] = await Promise.all([
|
||||
this.prisma.passenger.findMany({
|
||||
where,
|
||||
@@ -47,41 +81,38 @@ export class PassengersService {
|
||||
take: pageSize,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
fullName: true,
|
||||
email: true,
|
||||
phone: true,
|
||||
nationalId: true,
|
||||
nationality: true,
|
||||
},
|
||||
},
|
||||
loyalty: true,
|
||||
_count: {
|
||||
select: {
|
||||
bookings: true,
|
||||
},
|
||||
},
|
||||
_count: { select: { bookings: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.passenger.count({ where }),
|
||||
]);
|
||||
|
||||
|
||||
const iamUserIds = items.map(p => p.iamUserId).filter(Boolean) as string[];
|
||||
const iamRows = iamUserIds.length > 0
|
||||
? await this.dataSource.query<IamUserRow[]>(
|
||||
`SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = ANY($1)`,
|
||||
[iamUserIds],
|
||||
)
|
||||
: [];
|
||||
const iamMap = new Map(iamRows.map(r => [r.id, r]));
|
||||
|
||||
return {
|
||||
items: items.map(passenger => ({
|
||||
id: passenger.id,
|
||||
fullName: passenger.user?.fullName ?? null,
|
||||
email: passenger.user?.email ?? null,
|
||||
phone: passenger.user?.phone ?? null,
|
||||
nationalId: passenger.user?.nationalId ?? null,
|
||||
nationality: passenger.user?.nationality ?? null,
|
||||
verified: !!passenger.user?.nationalId,
|
||||
loyaltyTier: passenger.loyalty?.tier || 'BRONZE',
|
||||
loyaltyPoints: passenger.loyalty?.pointsBalance || 0,
|
||||
totalBookings: passenger._count.bookings,
|
||||
createdAt: passenger.createdAt,
|
||||
})),
|
||||
items: items.map(passenger => {
|
||||
const iam = passenger.iamUserId ? iamMap.get(passenger.iamUserId) : undefined;
|
||||
const faydaVerified = iam?.metadata?.faydaVerified === true || iam?.metadata?.faydaVerified === 'true';
|
||||
return {
|
||||
id: passenger.id,
|
||||
fullName: iam?.name?.en ?? iam?.name?.am ?? null,
|
||||
email: iam?.email ?? null,
|
||||
phone: iam?.phone_number ?? null,
|
||||
verified: faydaVerified,
|
||||
loyaltyTier: passenger.loyalty?.tier || 'BRONZE',
|
||||
loyaltyPoints: passenger.loyalty?.pointsBalance || 0,
|
||||
totalBookings: passenger._count.bookings,
|
||||
createdAt: passenger.createdAt,
|
||||
};
|
||||
}),
|
||||
meta: {
|
||||
page,
|
||||
pageSize,
|
||||
@@ -92,33 +123,56 @@ export class PassengersService {
|
||||
}
|
||||
|
||||
async getProfile(passengerId: string) {
|
||||
|
||||
console.log("here");
|
||||
|
||||
const p = await this.prisma.passenger.findUnique({
|
||||
where: { id: passengerId },
|
||||
include: {
|
||||
user: { select: { fullName: true, email: true, phone: true } },
|
||||
bookings: { orderBy: { createdAt: 'desc' }, take: 10, include: { schedule: { include: { originStation: true, destinationStation: true, train: true } }, seats: { include: { seat: { include: { coach: { include: { seatClass: true } } } } } } } },
|
||||
loyalty: true, wallet: true, travelerProfiles: true, savedRoutes: true,
|
||||
bookings: {
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 10,
|
||||
include: {
|
||||
schedule: { include: { originStation: true, destinationStation: true, train: true } },
|
||||
seats: { include: { seat: { include: { coach: { include: { seatClass: true } } } } } },
|
||||
},
|
||||
},
|
||||
loyalty: true,
|
||||
wallet: true,
|
||||
travelerProfiles: true,
|
||||
savedRoutes: true,
|
||||
},
|
||||
});
|
||||
if (!p) throw new NotFoundException('Passenger not found');
|
||||
|
||||
let iamUser: IamUserRow | null = null;
|
||||
if (p.iamUserId) {
|
||||
const rows = await this.dataSource.query<IamUserRow[]>(
|
||||
`SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = $1 LIMIT 1`,
|
||||
[p.iamUserId],
|
||||
);
|
||||
iamUser = rows[0] ?? null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: p.id,
|
||||
fullName: p.user?.fullName ?? null,
|
||||
email: p.user?.email ?? null,
|
||||
phone: p.user?.phone ?? null,
|
||||
fullName: iamUser?.name?.en ?? iamUser?.name?.am ?? null,
|
||||
email: iamUser?.email ?? null,
|
||||
phone: iamUser?.phone_number ?? null,
|
||||
createdAt: p.createdAt,
|
||||
bookings: p.bookings.map((b) => ({
|
||||
id: b.id, bookingRef: b.bookingRef, status: b.status, totalFare: b.totalMinor / 100, createdAt: b.createdAt,
|
||||
id: b.id,
|
||||
bookingRef: b.bookingRef,
|
||||
status: b.status,
|
||||
totalFare: b.totalMinor / 100,
|
||||
createdAt: b.createdAt,
|
||||
trip: {
|
||||
number: b.schedule.train.number,
|
||||
origin: { id: b.schedule.originStation.id, name: b.schedule.originStation.name, code: b.schedule.originStation.code, city: b.schedule.originStation.city },
|
||||
destination: { id: b.schedule.destinationStation.id, name: b.schedule.destinationStation.name, code: b.schedule.destinationStation.code, city: b.schedule.destinationStation.city },
|
||||
departureAt: b.schedule.departureAt,
|
||||
},
|
||||
passengers: b.seats.map((bs) => ({ fullName: bs.passengerName, seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.seatClass?.name ?? 'N/A' } })),
|
||||
passengers: b.seats.map((bs) => ({
|
||||
fullName: bs.passengerName,
|
||||
seat: { number: bs.seat.label, coach: bs.seat.coach.label, class: bs.seat.coach.seatClass?.name ?? 'N/A' },
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -186,23 +240,53 @@ export class PassengersService {
|
||||
async updatePassenger(id: string, dto: any) {
|
||||
const passenger = await this.prisma.passenger.findUnique({ where: { id } });
|
||||
if (!passenger) throw new NotFoundException('Passenger not found');
|
||||
return this.prisma.passenger.update({
|
||||
where: { id },
|
||||
data: {
|
||||
user: {
|
||||
update: {
|
||||
fullName: dto.fullName || undefined,
|
||||
email: dto.email || undefined,
|
||||
phone: dto.phone || undefined,
|
||||
nationality: dto.nationality || undefined,
|
||||
},
|
||||
},
|
||||
},
|
||||
include: {
|
||||
user: { select: { fullName: true, email: true, phone: true, nationality: true } },
|
||||
loyalty: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (passenger.iamUserId && (dto.fullName || dto.email || dto.phone)) {
|
||||
const updates: string[] = [];
|
||||
const params: any[] = [];
|
||||
let idx = 1;
|
||||
|
||||
if (dto.fullName) {
|
||||
updates.push(`name = COALESCE(name, '{}') || jsonb_build_object('en', $${idx}::text, 'am', $${idx}::text)`);
|
||||
params.push(dto.fullName);
|
||||
idx++;
|
||||
}
|
||||
if (dto.email) {
|
||||
updates.push(`email = $${idx}`);
|
||||
params.push(dto.email);
|
||||
idx++;
|
||||
}
|
||||
if (dto.phone) {
|
||||
updates.push(`phone_number = $${idx}`);
|
||||
params.push(dto.phone);
|
||||
idx++;
|
||||
}
|
||||
|
||||
params.push(passenger.iamUserId);
|
||||
await this.dataSource.query(
|
||||
`UPDATE iam.users SET ${updates.join(', ')} WHERE id = $${idx}`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
const [updated, iamRows] = await Promise.all([
|
||||
this.prisma.passenger.findUnique({ where: { id }, include: { loyalty: true } }),
|
||||
passenger.iamUserId
|
||||
? this.dataSource.query<IamUserRow[]>(
|
||||
`SELECT id, email, name, phone_number, metadata FROM iam.users WHERE id = $1 LIMIT 1`,
|
||||
[passenger.iamUserId],
|
||||
)
|
||||
: Promise.resolve([] as IamUserRow[]),
|
||||
]);
|
||||
|
||||
const iamUser = iamRows[0] ?? null;
|
||||
return {
|
||||
id: updated!.id,
|
||||
fullName: iamUser?.name?.en ?? iamUser?.name?.am ?? null,
|
||||
email: iamUser?.email ?? null,
|
||||
phone: iamUser?.phone_number ?? null,
|
||||
loyalty: updated!.loyalty,
|
||||
};
|
||||
}
|
||||
|
||||
async registerPassenger(dto: RegisterPassengerDto) {
|
||||
@@ -210,7 +294,6 @@ export class PassengersService {
|
||||
const isLoggedIn = !!dto.userId;
|
||||
let verifiedData: any = null;
|
||||
|
||||
// Auto-verify Ethiopian passengers with national ID if Fayda is enabled
|
||||
if (isEthiopian && dto.verifyWithFayda !== false) {
|
||||
try {
|
||||
const verification = await this.verifaydaService.verifyNationalId(dto.nationalId!);
|
||||
@@ -218,12 +301,10 @@ export class PassengersService {
|
||||
verifiedData = verification.passengerData;
|
||||
}
|
||||
} catch (error) {
|
||||
// If verification fails, continue with manual data
|
||||
console.warn('Fayda verification failed, using manual data:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Use verified data if available, otherwise use provided data
|
||||
const finalData = {
|
||||
passengerName: verifiedData?.fullName || dto.passengerName,
|
||||
dateOfBirth: verifiedData?.dateOfBirth || new Date(dto.dateOfBirth),
|
||||
@@ -233,7 +314,6 @@ export class PassengersService {
|
||||
email: dto.email,
|
||||
};
|
||||
|
||||
// If logged in, link passenger
|
||||
if (isLoggedIn) {
|
||||
const linkedPassenger = await this.prisma.passenger.findUnique({
|
||||
where: { iamUserId: dto.userId },
|
||||
@@ -254,7 +334,6 @@ export class PassengersService {
|
||||
};
|
||||
}
|
||||
|
||||
// Guest user - save to SavedPassengerProfile
|
||||
const profile = await this.prisma.savedPassengerProfile.create({
|
||||
data: {
|
||||
deviceId: dto.deviceId,
|
||||
@@ -283,7 +362,7 @@ export class PassengersService {
|
||||
async deletePassenger(id: string) {
|
||||
const passenger = await this.prisma.passenger.findUnique({ where: { id } });
|
||||
if (!passenger) throw new NotFoundException('Passenger not found');
|
||||
|
||||
|
||||
await this.prisma.passenger.delete({ where: { id } });
|
||||
return { deleted: true, passengerId: id };
|
||||
}
|
||||
@@ -305,4 +384,4 @@ export class PassengersService {
|
||||
affectedModules: usage,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user