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")
|
||||
reportsReceived UserReport[] @relation("ReportsReceived")
|
||||
|
||||
// Verification actions (admin)
|
||||
verificationActions VerificationHistory[] @relation("VerificationActions")
|
||||
|
||||
@@index([email])
|
||||
@@index([role])
|
||||
@@index([status])
|
||||
@@ -275,6 +278,9 @@ model AgentProfile {
|
||||
@@index([agentTypeId])
|
||||
@@index([slug])
|
||||
@@index([city, state])
|
||||
// Verification History
|
||||
verificationHistory VerificationHistory[]
|
||||
|
||||
@@index([isVerified])
|
||||
@@index([verificationStatus])
|
||||
@@index([isPublic])
|
||||
@@ -761,3 +767,22 @@ model ContactMessage {
|
||||
@@index([createdAt])
|
||||
@@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);
|
||||
}
|
||||
|
||||
@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)
|
||||
// ==========================================
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable, BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
|
||||
import { UserStatus, UserRole, Prisma, VerificationStatus } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import * as argon2 from 'argon2';
|
||||
|
||||
type UserWithProfiles = Prisma.UserGetPayload<{
|
||||
@@ -9,7 +10,10 @@ type UserWithProfiles = Prisma.UserGetPayload<{
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly eventEmitter: EventEmitter2,
|
||||
) {}
|
||||
|
||||
// =============================================
|
||||
// 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 {
|
||||
id: user.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
|
||||
// =============================================
|
||||
|
||||
Reference in New Issue
Block a user