From 8c377e86d8df6ca2c000a0c1dbe6a9c00be0a272 Mon Sep 17 00:00:00 2001 From: Abubeker Yasin Date: Mon, 8 Jun 2026 15:49:42 +0300 Subject: [PATCH] refactor( iam ): replace userId FK with iamUserId on UserPreferences, Device, FraudAlert --- .../migration.sql | 13 +++++++++++++ apps/edr-passenger-api/prisma/schema.prisma | 14 ++++---------- .../src/modules/auth/passenger-auth.service.ts | 2 +- .../src/modules/bookings/bookings.service.ts | 16 ++++++++-------- .../src/modules/fraud/fraud.service.ts | 4 ++-- .../notifications/notifications.service.ts | 17 +++++++++-------- 6 files changed, 37 insertions(+), 29 deletions(-) create mode 100644 apps/edr-passenger-api/prisma/migrations/20260608080000_rename_userid_to_iamuserid_on_preferences_device_fraudalert/migration.sql diff --git a/apps/edr-passenger-api/prisma/migrations/20260608080000_rename_userid_to_iamuserid_on_preferences_device_fraudalert/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260608080000_rename_userid_to_iamuserid_on_preferences_device_fraudalert/migration.sql new file mode 100644 index 000000000..ec1cfd078 --- /dev/null +++ b/apps/edr-passenger-api/prisma/migrations/20260608080000_rename_userid_to_iamuserid_on_preferences_device_fraudalert/migration.sql @@ -0,0 +1,13 @@ +-- Drop FK constraints (they reference iam.users indirectly via local User, but these are within passenger schema) +ALTER TABLE passenger."UserPreferences" DROP CONSTRAINT IF EXISTS "UserPreferences_userId_fkey"; +ALTER TABLE passenger."Device" DROP CONSTRAINT IF EXISTS "Device_userId_fkey"; +ALTER TABLE passenger."FraudAlert" DROP CONSTRAINT IF EXISTS "FraudAlert_userId_fkey"; + +-- Rename columns (preserves all existing data) +ALTER TABLE passenger."UserPreferences" RENAME COLUMN "userId" TO "iamUserId"; +ALTER TABLE passenger."Device" RENAME COLUMN "userId" TO "iamUserId"; +ALTER TABLE passenger."FraudAlert" RENAME COLUMN "userId" TO "iamUserId"; + +-- Rename indexes on FraudAlert to match new column name +DROP INDEX IF EXISTS passenger."FraudAlert_userId_createdAt_idx"; +CREATE INDEX "FraudAlert_iamUserId_createdAt_idx" ON passenger."FraudAlert"("iamUserId", "createdAt"); diff --git a/apps/edr-passenger-api/prisma/schema.prisma b/apps/edr-passenger-api/prisma/schema.prisma index 65d4b64a5..bcf96954b 100644 --- a/apps/edr-passenger-api/prisma/schema.prisma +++ b/apps/edr-passenger-api/prisma/schema.prisma @@ -232,10 +232,7 @@ model User { passenger Passenger? agent Agent? sessions Session[] - devices Device[] - preferences UserPreferences? auditLogs AuditLog[] - fraudAlerts FraudAlert[] faydaVerificationSessions FaydaVerificationSession[] @@ -870,7 +867,7 @@ model SupportMessage { model UserPreferences { id String @id @default(uuid()) - userId String @unique + iamUserId String @unique pushEnabled Boolean @default(true) emailEnabled Boolean @default(true) smsEnabled Boolean @default(false) @@ -883,20 +880,18 @@ model UserPreferences { locale String @default("en") darkMode Boolean @default(false) language String @default("en") - user User @relation(fields: [userId], references: [id]) @@schema("passenger") } model Device { id String @id @default(uuid()) - userId String + iamUserId String platform DevicePlatform name String pushToken String? trusted Boolean @default(false) lastSeenAt DateTime @default(now()) - user User @relation(fields: [userId], references: [id]) @@schema("passenger") } @@ -1222,15 +1217,14 @@ model FraudRule { model FraudAlert { id String @id @default(uuid()) - userId String + iamUserId String eventType String triggeredRules String[] context Json severity String @default("MEDIUM") acknowledged Boolean @default(false) createdAt DateTime @default(now()) - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - @@index([userId, createdAt]) + @@index([iamUserId, createdAt]) @@index([acknowledged]) @@schema("passenger") diff --git a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts index b634c607c..e78e814e0 100644 --- a/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts +++ b/apps/edr-passenger-api/src/modules/auth/passenger-auth.service.ts @@ -192,7 +192,7 @@ export class PassengerAuthService { }); await tx.loyaltyAccount.create({ data: { passengerId: passenger.id } }); await tx.walletAccount.create({ data: { passengerId: passenger.id } }); - await tx.userPreferences.create({ data: { userId: user.id } }); + await tx.userPreferences.create({ data: { iamUserId: data.iamUserId } }); await tx.auditLog.create({ data: { userId: user.id, diff --git a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts index 1fd74de9f..64002782e 100644 --- a/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts +++ b/apps/edr-passenger-api/src/modules/bookings/bookings.service.ts @@ -106,22 +106,22 @@ export class BookingsService { const { search, status, page = 1, pageSize = 20 } = filters; const skip = (page - 1) * pageSize; - // Find user with this device ID - const device = await this.prisma.device.findUnique({ - where: { id: deviceId }, - include: { user: { include: { passenger: true } } }, - }).catch(() => null); - + // Find passenger linked to this device via iamUserId + const device = await this.prisma.device.findUnique({ where: { id: deviceId } }).catch(() => null); + const passenger = device?.iamUserId + ? await this.prisma.passenger.findUnique({ where: { iamUserId: device.iamUserId } }).catch(() => null) + : null; + const searchConditions = search ? [ { bookingRef: { contains: search, mode: 'insensitive' } }, { schedule: { originStation: { name: { contains: search, mode: 'insensitive' } } } }, { schedule: { destinationStation: { name: { contains: search, mode: 'insensitive' } } } }, ] : []; - + const where: any = { OR: [ { userAgent: deviceId }, - ...(device?.user?.passenger ? [{ passengerId: device.user.passenger.id }] : []), + ...(passenger ? [{ passengerId: passenger.id }] : []), ], }; diff --git a/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts b/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts index 7c3b66e6b..4b1912750 100644 --- a/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts +++ b/apps/edr-passenger-api/src/modules/fraud/fraud.service.ts @@ -138,7 +138,7 @@ export class FraudService { ): Promise { const alert = await this.prisma.fraudAlert.create({ data: { - userId, + iamUserId: userId, eventType, triggeredRules, context: context as any, @@ -182,7 +182,7 @@ export class FraudService { */ async getAlerts(userId?: string, limit = 100, offset = 0) { return this.prisma.fraudAlert.findMany({ - where: userId ? { userId } : {}, + where: userId ? { iamUserId: userId } : {}, orderBy: { createdAt: 'desc' }, take: limit, skip: offset, 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 e793a1322..d0e58db85 100644 --- a/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts +++ b/apps/edr-passenger-api/src/modules/notifications/notifications.service.ts @@ -166,20 +166,21 @@ export class NotificationsService { private async getUserPreferredChannels(recipient: string): Promise { const user = await this.prisma.user.findFirst({ - where: { - OR: [{ id: recipient }, { email: recipient }, { phone: recipient }], - }, - include: { preferences: true }, + where: { OR: [{ id: recipient }, { email: recipient }, { phone: recipient }] }, + include: { passenger: { select: { iamUserId: true } } }, }); - if (!user?.preferences) { + const iamUserId = user?.passenger?.iamUserId ?? recipient; + const preferences = await this.prisma.userPreferences.findUnique({ where: { iamUserId } }); + + if (!preferences) { return ['IN_APP', 'EMAIL']; } const channels: NotificationChannelType[] = ['IN_APP']; - if (user.preferences.emailEnabled) channels.push('EMAIL'); - if (user.preferences.smsEnabled) channels.push('SMS'); - if (user.preferences.pushEnabled) channels.push('PUSH'); + if (preferences.emailEnabled) channels.push('EMAIL'); + if (preferences.smsEnabled) channels.push('SMS'); + if (preferences.pushEnabled) channels.push('PUSH'); return channels; }