refactor( iam ): replace userId FK with iamUserId on UserPreferences, Device, FraudAlert

This commit is contained in:
Abubeker Yasin
2026-06-08 15:49:42 +03:00
parent c1dcfb04c3
commit 8c377e86d8
6 changed files with 37 additions and 29 deletions

View File

@@ -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");

View File

@@ -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")

View File

@@ -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,

View File

@@ -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 }] : []),
],
};

View File

@@ -138,7 +138,7 @@ export class FraudService {
): Promise<void> {
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,

View File

@@ -166,20 +166,21 @@ export class NotificationsService {
private async getUserPreferredChannels(recipient: string): Promise<NotificationChannelType[]> {
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;
}