feat: Introduce agent profile verification history tracking and status notifications for agents.
This commit is contained in:
@@ -161,6 +161,9 @@ model User {
|
|||||||
reportsSubmitted UserReport[] @relation("ReportsSubmitted")
|
reportsSubmitted UserReport[] @relation("ReportsSubmitted")
|
||||||
reportsReceived UserReport[] @relation("ReportsReceived")
|
reportsReceived UserReport[] @relation("ReportsReceived")
|
||||||
|
|
||||||
|
// Verification actions (admin)
|
||||||
|
verificationActions VerificationHistory[] @relation("VerificationActions")
|
||||||
|
|
||||||
@@index([email])
|
@@index([email])
|
||||||
@@index([role])
|
@@index([role])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -275,6 +278,9 @@ model AgentProfile {
|
|||||||
@@index([agentTypeId])
|
@@index([agentTypeId])
|
||||||
@@index([slug])
|
@@index([slug])
|
||||||
@@index([city, state])
|
@@index([city, state])
|
||||||
|
// Verification History
|
||||||
|
verificationHistory VerificationHistory[]
|
||||||
|
|
||||||
@@index([isVerified])
|
@@index([isVerified])
|
||||||
@@index([verificationStatus])
|
@@index([verificationStatus])
|
||||||
@@index([isPublic])
|
@@index([isPublic])
|
||||||
@@ -761,3 +767,22 @@ model ContactMessage {
|
|||||||
@@index([createdAt])
|
@@index([createdAt])
|
||||||
@@map("contact_messages")
|
@@map("contact_messages")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// VERIFICATION HISTORY
|
||||||
|
// ============================================
|
||||||
|
model VerificationHistory {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
agentProfileId String
|
||||||
|
status VerificationStatus
|
||||||
|
note String?
|
||||||
|
adminId String?
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
agentProfile AgentProfile @relation(fields: [agentProfileId], references: [id], onDelete: Cascade)
|
||||||
|
admin User? @relation("VerificationActions", fields: [adminId], references: [id])
|
||||||
|
|
||||||
|
@@index([agentProfileId])
|
||||||
|
@@index([createdAt])
|
||||||
|
@@map("verification_history")
|
||||||
|
}
|
||||||
|
|||||||
@@ -473,4 +473,55 @@ export class NotificationsService implements OnModuleInit {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnEvent('notification.verification')
|
||||||
|
async handleVerificationNotification(event: {
|
||||||
|
recipientUserId: string;
|
||||||
|
status: string;
|
||||||
|
note?: string;
|
||||||
|
agentName: string;
|
||||||
|
}) {
|
||||||
|
const isApproved = event.status === 'APPROVED';
|
||||||
|
const actionUrl = '/agent/dashboard';
|
||||||
|
|
||||||
|
const title = isApproved
|
||||||
|
? 'Profile Verified!'
|
||||||
|
: 'Profile Verification Update';
|
||||||
|
|
||||||
|
const description = isApproved
|
||||||
|
? 'Congratulations! Your profile has been verified. You now have the verified badge on your profile.'
|
||||||
|
: `Your profile verification was not approved.${event.note ? ` Reason: ${event.note}` : ''} Please review and resubmit.`;
|
||||||
|
|
||||||
|
// Create in-app notification + push (always for verification)
|
||||||
|
const user = await this.prisma.user.findUnique({
|
||||||
|
where: { id: event.recipientUserId },
|
||||||
|
select: { email: true, fcmTokens: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) return;
|
||||||
|
|
||||||
|
// Always create in-app notification
|
||||||
|
await this.prisma.notification.create({
|
||||||
|
data: {
|
||||||
|
userId: event.recipientUserId,
|
||||||
|
type: 'system',
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
actionUrl,
|
||||||
|
data: { type: 'verification', status: event.status, link: actionUrl },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Always send push notification
|
||||||
|
await this.sendPushNotification(event.recipientUserId, user, {
|
||||||
|
title,
|
||||||
|
body: description,
|
||||||
|
data: { type: 'verification', status: event.status, link: actionUrl },
|
||||||
|
});
|
||||||
|
|
||||||
|
// Always send email — forced, ignores preferences
|
||||||
|
if (user.email) {
|
||||||
|
await this.sendEmailNotification(user.email, title, description, actionUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -330,6 +330,16 @@ export class UsersController {
|
|||||||
return this.usersService.updateVerification(id, data, admin.id);
|
return this.usersService.updateVerification(id, data, admin.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get(':id/verification-history')
|
||||||
|
@Roles(UserRole.ADMIN)
|
||||||
|
@ApiOperation({ summary: 'Admin: Get verification history for an agent' })
|
||||||
|
async getVerificationHistory(@Param('id') id: string) {
|
||||||
|
const user = await this.usersService.findOne(id);
|
||||||
|
const agentProfileId = (user as any)?.agentProfile?.id;
|
||||||
|
if (!agentProfileId) throw new NotFoundException('Agent profile not found');
|
||||||
|
return this.usersService.getVerificationHistory(agentProfileId);
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// ADMIN MANAGEMENT (SUPER_ADMIN only)
|
// ADMIN MANAGEMENT (SUPER_ADMIN only)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||||
import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client';
|
import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
|
|
||||||
type UserWithProfiles = Prisma.UserGetPayload<{
|
type UserWithProfiles = Prisma.UserGetPayload<{
|
||||||
@@ -9,7 +10,10 @@ type UserWithProfiles = Prisma.UserGetPayload<{
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersService {
|
export class UsersService {
|
||||||
constructor(private readonly prisma: PrismaService) {}
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly eventEmitter: EventEmitter2,
|
||||||
|
) {}
|
||||||
|
|
||||||
// =============================================
|
// =============================================
|
||||||
// USER PROFILE METHODS (for regular users)
|
// USER PROFILE METHODS (for regular users)
|
||||||
@@ -390,6 +394,24 @@ export class UsersService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Save verification history
|
||||||
|
await this.prisma.verificationHistory.create({
|
||||||
|
data: {
|
||||||
|
agentProfileId: user.agentProfile.id,
|
||||||
|
status: data.status,
|
||||||
|
note: data.note || null,
|
||||||
|
adminId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Emit notification to the agent
|
||||||
|
this.eventEmitter.emit('notification.verification', {
|
||||||
|
recipientUserId: userId,
|
||||||
|
status: data.status,
|
||||||
|
note: data.note,
|
||||||
|
agentName: `${updatedProfile.firstName || ''} ${updatedProfile.lastName || ''}`.trim(),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
agentProfileId: updatedProfile.id,
|
agentProfileId: updatedProfile.id,
|
||||||
@@ -402,6 +424,17 @@ export class UsersService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get verification history for an agent
|
||||||
|
async getVerificationHistory(agentProfileId: string) {
|
||||||
|
return this.prisma.verificationHistory.findMany({
|
||||||
|
where: { agentProfileId },
|
||||||
|
include: {
|
||||||
|
admin: { select: { id: true, email: true } },
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================
|
// =============================================
|
||||||
// NOTIFICATION PREFERENCES
|
// NOTIFICATION PREFERENCES
|
||||||
// =============================================
|
// =============================================
|
||||||
|
|||||||
Reference in New Issue
Block a user