feat: implement privacy-aware profile visibility and activity status filtering using optional authentication guard
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
Param,
|
Param,
|
||||||
Query,
|
Query,
|
||||||
|
Req,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
ParseUUIDPipe,
|
ParseUUIDPipe,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
@@ -25,6 +26,7 @@ import {
|
|||||||
} from './dto';
|
} from './dto';
|
||||||
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||||
import { RolesGuard } from '../auth/guards/roles.guard';
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
|
import { OptionalAuthGuard } from '../auth/guards/optional-auth.guard';
|
||||||
import { Roles } from '../auth/decorators/roles.decorator';
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
import { Public } from '../auth/decorators/public.decorator';
|
import { Public } from '../auth/decorators/public.decorator';
|
||||||
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
@@ -243,11 +245,13 @@ export class AgentsController {
|
|||||||
// Public parameterized routes - MUST come after specific routes like profile/*
|
// Public parameterized routes - MUST come after specific routes like profile/*
|
||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Public()
|
@Public()
|
||||||
|
@UseGuards(OptionalAuthGuard)
|
||||||
@ApiOperation({ summary: 'Get agent profile by ID' })
|
@ApiOperation({ summary: 'Get agent profile by ID' })
|
||||||
@ApiResponse({ status: 200, description: 'Agent profile retrieved successfully' })
|
@ApiResponse({ status: 200, description: 'Agent profile retrieved successfully' })
|
||||||
@ApiResponse({ status: 404, description: 'Agent profile not found' })
|
@ApiResponse({ status: 404, description: 'Agent profile not found' })
|
||||||
async getAgentById(@Param('id', ParseUUIDPipe) id: string) {
|
async getAgentById(@Param('id', ParseUUIDPipe) id: string, @Req() req: any) {
|
||||||
return this.agentsService.getProfileById(id);
|
const requestingUserId = req.user?.id || null;
|
||||||
|
return this.agentsService.getProfileById(id, requestingUserId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':id/field-values')
|
@Get(':id/field-values')
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
|
|||||||
import { AgentsController } from './agents.controller';
|
import { AgentsController } from './agents.controller';
|
||||||
import { AgentsService } from './agents.service';
|
import { AgentsService } from './agents.service';
|
||||||
import { PrismaModule } from '../prisma/prisma.module';
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
import { ConnectionRequestsModule } from '../connection-requests/connection-requests.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule, ConnectionRequestsModule],
|
||||||
controllers: [AgentsController],
|
controllers: [AgentsController],
|
||||||
providers: [AgentsService],
|
providers: [AgentsService],
|
||||||
exports: [AgentsService],
|
exports: [AgentsService],
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
NotFoundException,
|
NotFoundException,
|
||||||
ConflictException,
|
ConflictException,
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
ForbiddenException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
SaveFieldValuesDto,
|
SaveFieldValuesDto,
|
||||||
} from './dto';
|
} from './dto';
|
||||||
import { Prisma, Prisma as PrismaTypes, VerificationStatus } from '@prisma/client';
|
import { Prisma, Prisma as PrismaTypes, VerificationStatus } from '@prisma/client';
|
||||||
|
import { ConnectionRequestsService } from '../connection-requests/connection-requests.service';
|
||||||
|
|
||||||
function generateSlug(firstName: string, lastName: string): string {
|
function generateSlug(firstName: string, lastName: string): string {
|
||||||
const base = `${firstName}-${lastName}`
|
const base = `${firstName}-${lastName}`
|
||||||
@@ -24,7 +26,24 @@ function generateSlug(firstName: string, lastName: string): string {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AgentsService {
|
export class AgentsService {
|
||||||
constructor(private prisma: PrismaService) {}
|
constructor(
|
||||||
|
private prisma: PrismaService,
|
||||||
|
private connectionRequestsService: ConnectionRequestsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get profile_visibility setting from user's privacy preferences
|
||||||
|
*/
|
||||||
|
private getProfileVisibility(privacyPreferences: any): string {
|
||||||
|
return privacyPreferences?.privacySettings?.profile_visibility || 'public';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get activity_status setting from user's privacy preferences
|
||||||
|
*/
|
||||||
|
private getActivityStatus(privacyPreferences: any): string {
|
||||||
|
return privacyPreferences?.privacySettings?.activity_status || 'public';
|
||||||
|
}
|
||||||
|
|
||||||
async createProfile(userId: string, dto: CreateAgentProfileDto) {
|
async createProfile(userId: string, dto: CreateAgentProfileDto) {
|
||||||
// Check if user already has an agent profile
|
// Check if user already has an agent profile
|
||||||
@@ -81,7 +100,7 @@ export class AgentsService {
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProfileById(profileId: string) {
|
async getProfileById(profileId: string, requestingUserId?: string) {
|
||||||
const profile = await this.prisma.agentProfile.findUnique({
|
const profile = await this.prisma.agentProfile.findUnique({
|
||||||
where: { id: profileId },
|
where: { id: profileId },
|
||||||
include: {
|
include: {
|
||||||
@@ -90,6 +109,7 @@ export class AgentsService {
|
|||||||
id: true,
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
avatar: true,
|
avatar: true,
|
||||||
|
privacyPreferences: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
agentType: true,
|
agentType: true,
|
||||||
@@ -100,7 +120,30 @@ export class AgentsService {
|
|||||||
throw new NotFoundException('Agent profile not found');
|
throw new NotFoundException('Agent profile not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile;
|
const visibility = this.getProfileVisibility(profile.user.privacyPreferences);
|
||||||
|
|
||||||
|
// Own profile — always visible
|
||||||
|
if (requestingUserId && requestingUserId === profile.user.id) {
|
||||||
|
const { privacyPreferences, ...userWithoutPrefs } = profile.user;
|
||||||
|
return { ...profile, user: userWithoutPrefs };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibility === 'private') {
|
||||||
|
throw new ForbiddenException('This profile is not available');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visibility === 'connections' && requestingUserId) {
|
||||||
|
const connected = await this.connectionRequestsService.isConnected(requestingUserId, profile.user.id);
|
||||||
|
if (!connected) {
|
||||||
|
throw new ForbiddenException('This profile is only visible to connections');
|
||||||
|
}
|
||||||
|
} else if (visibility === 'connections' && !requestingUserId) {
|
||||||
|
throw new ForbiddenException('This profile is only visible to connections');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strip privacyPreferences from response
|
||||||
|
const { privacyPreferences, ...userWithoutPrefs } = profile.user;
|
||||||
|
return { ...profile, user: userWithoutPrefs };
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProfile(userId: string, dto: UpdateAgentProfileDto) {
|
async updateProfile(userId: string, dto: UpdateAgentProfileDto) {
|
||||||
@@ -197,8 +240,17 @@ export class AgentsService {
|
|||||||
const skip = (page - 1) * limit;
|
const skip = (page - 1) * limit;
|
||||||
|
|
||||||
// Build where clause — only show admin-approved profiles in search
|
// Build where clause — only show admin-approved profiles in search
|
||||||
|
// Exclude agents who set profile_visibility to 'private'
|
||||||
const where: Prisma.AgentProfileWhereInput = {
|
const where: Prisma.AgentProfileWhereInput = {
|
||||||
verificationStatus: 'APPROVED',
|
verificationStatus: 'APPROVED',
|
||||||
|
NOT: {
|
||||||
|
user: {
|
||||||
|
privacyPreferences: {
|
||||||
|
path: ['privacySettings', 'profile_visibility'],
|
||||||
|
equals: 'private',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (search) {
|
if (search) {
|
||||||
@@ -484,6 +536,14 @@ export class AgentsService {
|
|||||||
isVerified: true,
|
isVerified: true,
|
||||||
isFeatured: true,
|
isFeatured: true,
|
||||||
verificationStatus: 'APPROVED',
|
verificationStatus: 'APPROVED',
|
||||||
|
NOT: {
|
||||||
|
user: {
|
||||||
|
privacyPreferences: {
|
||||||
|
path: ['privacySettings', 'profile_visibility'],
|
||||||
|
equals: 'private',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }],
|
orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }],
|
||||||
@@ -508,6 +568,14 @@ export class AgentsService {
|
|||||||
isVerified: true,
|
isVerified: true,
|
||||||
totalReviews: { gte: 5 },
|
totalReviews: { gte: 5 },
|
||||||
verificationStatus: 'APPROVED',
|
verificationStatus: 'APPROVED',
|
||||||
|
NOT: {
|
||||||
|
user: {
|
||||||
|
privacyPreferences: {
|
||||||
|
path: ['privacySettings', 'profile_visibility'],
|
||||||
|
equals: 'private',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }],
|
orderBy: [{ averageRating: 'desc' }, { totalReviews: 'desc' }],
|
||||||
|
|||||||
18
src/auth/guards/optional-auth.guard.ts
Normal file
18
src/auth/guards/optional-auth.guard.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional JWT auth guard — extracts user if token is present, but doesn't reject if missing.
|
||||||
|
* Use on public endpoints that behave differently for authenticated users (e.g., privacy filtering).
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class OptionalAuthGuard extends AuthGuard('jwt') {
|
||||||
|
canActivate(context: ExecutionContext) {
|
||||||
|
return super.canActivate(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequest(err: any, user: any) {
|
||||||
|
// Don't throw on missing/invalid token — just return null
|
||||||
|
return user || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,50 @@ export class ConnectionRequestsService {
|
|||||||
private eventEmitter: EventEmitter2,
|
private eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user is connected to an agent (ACCEPTED connection exists)
|
||||||
|
*/
|
||||||
|
async isConnected(userId: string, agentUserId: string): Promise<boolean> {
|
||||||
|
const connection = await this.prisma.connectionRequest.findFirst({
|
||||||
|
where: {
|
||||||
|
status: ConnectionStatus.ACCEPTED,
|
||||||
|
OR: [
|
||||||
|
// User → Agent connection
|
||||||
|
{ userId, agentProfile: { userId: agentUserId } },
|
||||||
|
// Agent → User connection (if agent has a userId match)
|
||||||
|
{ userId: agentUserId, agentProfile: { userId } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return !!connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all user IDs connected to a given user (ACCEPTED connections)
|
||||||
|
*/
|
||||||
|
async getConnectedUserIds(userId: string): Promise<string[]> {
|
||||||
|
const connections = await this.prisma.connectionRequest.findMany({
|
||||||
|
where: {
|
||||||
|
status: ConnectionStatus.ACCEPTED,
|
||||||
|
OR: [
|
||||||
|
{ userId },
|
||||||
|
{ agentProfile: { userId } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
userId: true,
|
||||||
|
agentProfile: { select: { userId: true } },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const connectedIds = new Set<string>();
|
||||||
|
for (const conn of connections) {
|
||||||
|
if (conn.userId !== userId) connectedIds.add(conn.userId);
|
||||||
|
if (conn.agentProfile.userId !== userId) connectedIds.add(conn.agentProfile.userId);
|
||||||
|
}
|
||||||
|
return Array.from(connectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new connection request from user to agent
|
* Create a new connection request from user to agent
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import { JwtService } from '@nestjs/jwt';
|
|||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { MessagesService } from './messages.service';
|
import { MessagesService } from './messages.service';
|
||||||
import { SupportChatService } from '../support-chat/support-chat.service';
|
import { SupportChatService } from '../support-chat/support-chat.service';
|
||||||
|
import { ConnectionRequestsService } from '../connection-requests/connection-requests.service';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import { CreateMessageDto } from './dto';
|
import { CreateMessageDto } from './dto';
|
||||||
import { Logger } from '@nestjs/common';
|
import { Logger } from '@nestjs/common';
|
||||||
|
|
||||||
@@ -37,6 +39,8 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly messagesService: MessagesService,
|
private readonly messagesService: MessagesService,
|
||||||
private readonly supportChatService: SupportChatService,
|
private readonly supportChatService: SupportChatService,
|
||||||
|
private readonly connectionRequestsService: ConnectionRequestsService,
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
private readonly jwtService: JwtService,
|
private readonly jwtService: JwtService,
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
@@ -415,14 +419,46 @@ export class MessagesGateway implements OnGatewayConnection, OnGatewayDisconnect
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Broadcast user online/offline status
|
* Broadcast user online/offline status respecting activity_status privacy setting
|
||||||
*/
|
*/
|
||||||
private broadcastUserStatus(userId: string, isOnline: boolean) {
|
private async broadcastUserStatus(userId: string, isOnline: boolean) {
|
||||||
this.server.emit('user_status_change', {
|
const statusEvent = {
|
||||||
userId,
|
userId,
|
||||||
isOnline,
|
isOnline,
|
||||||
lastSeenAt: isOnline ? null : new Date(),
|
lastSeenAt: isOnline ? null : new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get user's activity_status privacy setting
|
||||||
|
const user = await this.prisma.user.findUnique({
|
||||||
|
where: { id: userId },
|
||||||
|
select: { privacyPreferences: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const prefs = user?.privacyPreferences as any;
|
||||||
|
const activityStatus = prefs?.privacySettings?.activity_status || 'public';
|
||||||
|
|
||||||
|
if (activityStatus === 'private') {
|
||||||
|
// Don't broadcast status to anyone
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activityStatus === 'connections') {
|
||||||
|
// Only send status to connected users
|
||||||
|
const connectedIds = await this.connectionRequestsService.getConnectedUserIds(userId);
|
||||||
|
for (const connectedId of connectedIds) {
|
||||||
|
this.sendToUser(connectedId, 'user_status_change', statusEvent);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'public' — broadcast to everyone
|
||||||
|
this.server.emit('user_status_change', statusEvent);
|
||||||
|
} catch (err) {
|
||||||
|
// Fallback to public broadcast if privacy check fails
|
||||||
|
this.logger.warn(`Privacy check failed for ${userId}, broadcasting publicly: ${err.message}`);
|
||||||
|
this.server.emit('user_status_change', statusEvent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { MessagesService } from './messages.service';
|
|||||||
import { MessagesGateway } from './messages.gateway';
|
import { MessagesGateway } from './messages.gateway';
|
||||||
import { PrismaModule } from '../prisma/prisma.module';
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
import { SupportChatModule } from '../support-chat/support-chat.module';
|
import { SupportChatModule } from '../support-chat/support-chat.module';
|
||||||
|
import { ConnectionRequestsModule } from '../connection-requests/connection-requests.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
SupportChatModule,
|
SupportChatModule,
|
||||||
|
ConnectionRequestsModule,
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
JwtModule.registerAsync({
|
JwtModule.registerAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ export class MessagesService {
|
|||||||
private readonly eventEmitter: EventEmitter2,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a user's activity status allows showing online status.
|
||||||
|
* Connected users always see online status for 'connections' setting.
|
||||||
|
*/
|
||||||
|
private shouldShowOnlineStatus(privacyPreferences: any, isConnected = true): boolean {
|
||||||
|
const activityStatus = (privacyPreferences as any)?.privacySettings?.activity_status || 'public';
|
||||||
|
if (activityStatus === 'public') return true;
|
||||||
|
if (activityStatus === 'connections' && isConnected) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if a user can message an agent (must have ACCEPTED connection)
|
* Validate if a user can message an agent (must have ACCEPTED connection)
|
||||||
*/
|
*/
|
||||||
@@ -87,6 +98,7 @@ export class MessagesService {
|
|||||||
select: {
|
select: {
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
lastSeenAt: true,
|
lastSeenAt: true,
|
||||||
|
privacyPreferences: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -96,6 +108,7 @@ export class MessagesService {
|
|||||||
id: true,
|
id: true,
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
lastSeenAt: true,
|
lastSeenAt: true,
|
||||||
|
privacyPreferences: true,
|
||||||
userProfile: {
|
userProfile: {
|
||||||
select: {
|
select: {
|
||||||
firstName: true,
|
firstName: true,
|
||||||
@@ -130,8 +143,10 @@ export class MessagesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Transform to include otherParty based on caller role
|
// Transform to include otherParty based on caller role
|
||||||
|
// Conversations only exist between connected users, so isConnected=true
|
||||||
if (callerRole === UserRole.AGENT) {
|
if (callerRole === UserRole.AGENT) {
|
||||||
// Agent is viewing — other party is the user
|
// Agent is viewing — other party is the user
|
||||||
|
const showStatus = this.shouldShowOnlineStatus(conversation.user.privacyPreferences);
|
||||||
return {
|
return {
|
||||||
...conversation,
|
...conversation,
|
||||||
unreadCount: conversation.agentUnreadCount,
|
unreadCount: conversation.agentUnreadCount,
|
||||||
@@ -139,12 +154,13 @@ export class MessagesService {
|
|||||||
id: conversation.user.id,
|
id: conversation.user.id,
|
||||||
name: `${conversation.user.userProfile?.firstName || ''} ${conversation.user.userProfile?.lastName || ''}`.trim() || 'User',
|
name: `${conversation.user.userProfile?.firstName || ''} ${conversation.user.userProfile?.lastName || ''}`.trim() || 'User',
|
||||||
avatar: conversation.user.userProfile?.avatar,
|
avatar: conversation.user.userProfile?.avatar,
|
||||||
isOnline: conversation.user.isOnline,
|
isOnline: showStatus ? conversation.user.isOnline : false,
|
||||||
lastSeenAt: conversation.user.lastSeenAt,
|
lastSeenAt: showStatus ? conversation.user.lastSeenAt : null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
// User is viewing — other party is the agent
|
// User is viewing — other party is the agent
|
||||||
|
const showStatus = this.shouldShowOnlineStatus(conversation.agentProfile.user?.privacyPreferences);
|
||||||
return {
|
return {
|
||||||
...conversation,
|
...conversation,
|
||||||
unreadCount: conversation.userUnreadCount,
|
unreadCount: conversation.userUnreadCount,
|
||||||
@@ -154,8 +170,8 @@ export class MessagesService {
|
|||||||
name: `${conversation.agentProfile.firstName || ''} ${conversation.agentProfile.lastName || ''}`.trim() || 'Agent',
|
name: `${conversation.agentProfile.firstName || ''} ${conversation.agentProfile.lastName || ''}`.trim() || 'Agent',
|
||||||
avatar: conversation.agentProfile.avatar,
|
avatar: conversation.agentProfile.avatar,
|
||||||
headline: conversation.agentProfile.headline,
|
headline: conversation.agentProfile.headline,
|
||||||
isOnline: conversation.agentProfile.user?.isOnline || false,
|
isOnline: showStatus ? (conversation.agentProfile.user?.isOnline || false) : false,
|
||||||
lastSeenAt: conversation.agentProfile.user?.lastSeenAt || null,
|
lastSeenAt: showStatus ? (conversation.agentProfile.user?.lastSeenAt || null) : null,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -306,6 +322,7 @@ export class MessagesService {
|
|||||||
id: true,
|
id: true,
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
lastSeenAt: true,
|
lastSeenAt: true,
|
||||||
|
privacyPreferences: true,
|
||||||
userProfile: {
|
userProfile: {
|
||||||
select: {
|
select: {
|
||||||
firstName: true,
|
firstName: true,
|
||||||
@@ -324,17 +341,20 @@ export class MessagesService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Map to include unread count from agent perspective
|
// Map to include unread count from agent perspective
|
||||||
return conversations.map((conv) => ({
|
return conversations.map((conv) => {
|
||||||
|
const showStatus = this.shouldShowOnlineStatus(conv.user.privacyPreferences);
|
||||||
|
return {
|
||||||
...conv,
|
...conv,
|
||||||
unreadCount: conv.agentUnreadCount,
|
unreadCount: conv.agentUnreadCount,
|
||||||
otherParty: {
|
otherParty: {
|
||||||
id: conv.user.id,
|
id: conv.user.id,
|
||||||
name: `${conv.user.userProfile?.firstName || ''} ${conv.user.userProfile?.lastName || ''}`.trim() || 'User',
|
name: `${conv.user.userProfile?.firstName || ''} ${conv.user.userProfile?.lastName || ''}`.trim() || 'User',
|
||||||
avatar: conv.user.userProfile?.avatar,
|
avatar: conv.user.userProfile?.avatar,
|
||||||
isOnline: conv.user.isOnline,
|
isOnline: showStatus ? conv.user.isOnline : false,
|
||||||
lastSeenAt: conv.user.lastSeenAt,
|
lastSeenAt: showStatus ? conv.user.lastSeenAt : null,
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
// Regular user
|
// Regular user
|
||||||
conversations = await this.prisma.conversation.findMany({
|
conversations = await this.prisma.conversation.findMany({
|
||||||
@@ -352,6 +372,7 @@ export class MessagesService {
|
|||||||
select: {
|
select: {
|
||||||
isOnline: true,
|
isOnline: true,
|
||||||
lastSeenAt: true,
|
lastSeenAt: true,
|
||||||
|
privacyPreferences: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -365,7 +386,9 @@ export class MessagesService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Map to include unread count from user perspective
|
// Map to include unread count from user perspective
|
||||||
return conversations.map((conv) => ({
|
return conversations.map((conv) => {
|
||||||
|
const showStatus = this.shouldShowOnlineStatus(conv.agentProfile.user.privacyPreferences);
|
||||||
|
return {
|
||||||
...conv,
|
...conv,
|
||||||
unreadCount: conv.userUnreadCount,
|
unreadCount: conv.userUnreadCount,
|
||||||
otherParty: {
|
otherParty: {
|
||||||
@@ -374,10 +397,11 @@ export class MessagesService {
|
|||||||
name: `${conv.agentProfile.firstName || ''} ${conv.agentProfile.lastName || ''}`.trim() || 'Agent',
|
name: `${conv.agentProfile.firstName || ''} ${conv.agentProfile.lastName || ''}`.trim() || 'Agent',
|
||||||
avatar: conv.agentProfile.avatar,
|
avatar: conv.agentProfile.avatar,
|
||||||
headline: conv.agentProfile.headline,
|
headline: conv.agentProfile.headline,
|
||||||
isOnline: conv.agentProfile.user.isOnline,
|
isOnline: showStatus ? conv.agentProfile.user.isOnline : false,
|
||||||
lastSeenAt: conv.agentProfile.user.lastSeenAt,
|
lastSeenAt: showStatus ? conv.agentProfile.user.lastSeenAt : null,
|
||||||
},
|
},
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user