feat: implement connection request management including schema, API endpoints, and business logic for users and agents.
This commit is contained in:
@@ -56,6 +56,12 @@ enum VerificationStatus {
|
|||||||
REJECTED // Admin rejected verification
|
REJECTED // Admin rejected verification
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum ConnectionStatus {
|
||||||
|
PENDING
|
||||||
|
ACCEPTED
|
||||||
|
REJECTED
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ===========================================
|
// ===========================================
|
||||||
// USER - Authentication Only
|
// USER - Authentication Only
|
||||||
@@ -89,6 +95,9 @@ model User {
|
|||||||
// Auth Relations
|
// Auth Relations
|
||||||
sessions Session[]
|
sessions Session[]
|
||||||
|
|
||||||
|
// Connection Requests
|
||||||
|
connectionRequests ConnectionRequest[]
|
||||||
|
|
||||||
@@index([email])
|
@@index([email])
|
||||||
@@index([role])
|
@@index([role])
|
||||||
@@index([status])
|
@@index([status])
|
||||||
@@ -188,6 +197,7 @@ model AgentProfile {
|
|||||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
agentType AgentType? @relation(fields: [agentTypeId], references: [id])
|
agentType AgentType? @relation(fields: [agentTypeId], references: [id])
|
||||||
fieldValues AgentProfileFieldValue[]
|
fieldValues AgentProfileFieldValue[]
|
||||||
|
connectionRequests ConnectionRequest[]
|
||||||
|
|
||||||
@@index([userId])
|
@@index([userId])
|
||||||
@@index([agentTypeId])
|
@@index([agentTypeId])
|
||||||
@@ -366,3 +376,30 @@ model Session {
|
|||||||
@@index([token])
|
@@index([token])
|
||||||
@@map("sessions")
|
@@map("sessions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===========================================
|
||||||
|
// CONNECTION REQUEST - User to Agent connections
|
||||||
|
// ===========================================
|
||||||
|
|
||||||
|
model ConnectionRequest {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
userId String // User sending the request
|
||||||
|
agentProfileId String // Agent receiving the request
|
||||||
|
status ConnectionStatus @default(PENDING)
|
||||||
|
message String? @db.Text // Optional message from user
|
||||||
|
|
||||||
|
// Timestamps
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
respondedAt DateTime? // When agent responded
|
||||||
|
|
||||||
|
// Relations
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
agentProfile AgentProfile @relation(fields: [agentProfileId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([userId, agentProfileId]) // One request per user-agent pair
|
||||||
|
@@index([agentProfileId, status])
|
||||||
|
@@index([userId, status])
|
||||||
|
@@index([status])
|
||||||
|
@@map("connection_requests")
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { AgentTypesModule } from './agent-types';
|
|||||||
import { AgentsModule } from './agents';
|
import { AgentsModule } from './agents';
|
||||||
import { UploadModule } from './upload';
|
import { UploadModule } from './upload';
|
||||||
import { ProfileFieldsModule } from './profile-fields';
|
import { ProfileFieldsModule } from './profile-fields';
|
||||||
|
import { ConnectionRequestsModule } from './connection-requests';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ import { AppService } from './app.service';
|
|||||||
AgentsModule,
|
AgentsModule,
|
||||||
UploadModule,
|
UploadModule,
|
||||||
ProfileFieldsModule,
|
ProfileFieldsModule,
|
||||||
|
ConnectionRequestsModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
|||||||
191
src/connection-requests/connection-requests.controller.ts
Normal file
191
src/connection-requests/connection-requests.controller.ts
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Patch,
|
||||||
|
Delete,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
|
||||||
|
import { ConnectionRequestsService } from './connection-requests.service';
|
||||||
|
import { CreateConnectionRequestDto, RespondConnectionRequestDto } from './dto';
|
||||||
|
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
||||||
|
import { RolesGuard } from '../auth/guards/roles.guard';
|
||||||
|
import { Roles } from '../auth/decorators/roles.decorator';
|
||||||
|
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
||||||
|
import { UserRole, ConnectionStatus } from '@prisma/client';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
|
||||||
|
@ApiTags('Connection Requests')
|
||||||
|
@Controller('connection-requests')
|
||||||
|
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
export class ConnectionRequestsController {
|
||||||
|
constructor(
|
||||||
|
private readonly connectionRequestsService: ConnectionRequestsService,
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@Roles(UserRole.USER)
|
||||||
|
@ApiOperation({ summary: 'Create a connection request to an agent' })
|
||||||
|
@ApiResponse({ status: 201, description: 'Connection request created successfully' })
|
||||||
|
@ApiResponse({ status: 400, description: 'Agent is not available or invalid request' })
|
||||||
|
@ApiResponse({ status: 409, description: 'Connection request already exists' })
|
||||||
|
async createRequest(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Body() dto: CreateConnectionRequestDto,
|
||||||
|
) {
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.createRequest(user.id, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('my-requests')
|
||||||
|
@Roles(UserRole.USER)
|
||||||
|
@ApiOperation({ summary: 'Get all connection requests sent by the current user' })
|
||||||
|
@ApiQuery({ name: 'status', enum: ConnectionStatus, required: false })
|
||||||
|
@ApiResponse({ status: 200, description: 'Connection requests retrieved successfully' })
|
||||||
|
async getMyRequests(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Query('status') status?: ConnectionStatus,
|
||||||
|
) {
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.getMyRequests(user.id, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('received')
|
||||||
|
@Roles(UserRole.AGENT)
|
||||||
|
@ApiOperation({ summary: 'Get all connection requests received by the current agent' })
|
||||||
|
@ApiQuery({ name: 'status', enum: ConnectionStatus, required: false })
|
||||||
|
@ApiResponse({ status: 200, description: 'Connection requests retrieved successfully' })
|
||||||
|
async getReceivedRequests(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Query('status') status?: ConnectionStatus,
|
||||||
|
) {
|
||||||
|
// Get agent profile for the current user
|
||||||
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
||||||
|
where: { userId: user.id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!agentProfile) {
|
||||||
|
// Return empty array - TransformInterceptor will wrap it
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.getReceivedRequests(
|
||||||
|
agentProfile.id,
|
||||||
|
status,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('received/counts')
|
||||||
|
@Roles(UserRole.AGENT)
|
||||||
|
@ApiOperation({ summary: 'Get connection request counts for the current agent' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Request counts retrieved successfully' })
|
||||||
|
async getRequestCounts(@CurrentUser() user: { id: string }) {
|
||||||
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
||||||
|
where: { userId: user.id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!agentProfile) {
|
||||||
|
// Return default counts - TransformInterceptor will wrap it
|
||||||
|
return { pending: 0, accepted: 0, rejected: 0, total: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.getRequestCounts(agentProfile.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('status/:agentProfileId')
|
||||||
|
@Roles(UserRole.USER, UserRole.AGENT)
|
||||||
|
@ApiOperation({ summary: 'Get connection status between current user and an agent' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Connection status retrieved successfully' })
|
||||||
|
async getConnectionStatus(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Param('agentProfileId') agentProfileId: string,
|
||||||
|
) {
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.getConnectionStatus(
|
||||||
|
user.id,
|
||||||
|
agentProfileId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@ApiOperation({ summary: 'Get a specific connection request by ID' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Connection request retrieved successfully' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Connection request not found' })
|
||||||
|
async getRequestById(
|
||||||
|
@CurrentUser() user: { id: string; role: UserRole },
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
let agentProfileId: string | undefined;
|
||||||
|
|
||||||
|
if (user.role === UserRole.AGENT) {
|
||||||
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
||||||
|
where: { userId: user.id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
agentProfileId = agentProfile?.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.getRequestById(
|
||||||
|
id,
|
||||||
|
user.id,
|
||||||
|
agentProfileId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch(':id/respond')
|
||||||
|
@Roles(UserRole.AGENT)
|
||||||
|
@ApiOperation({ summary: 'Respond to a connection request (accept/reject)' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Response recorded successfully' })
|
||||||
|
@ApiResponse({ status: 400, description: 'Request already responded to' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Not authorized to respond to this request' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Connection request not found' })
|
||||||
|
async respondToRequest(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() dto: RespondConnectionRequestDto,
|
||||||
|
) {
|
||||||
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
||||||
|
where: { userId: user.id },
|
||||||
|
select: { id: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!agentProfile) {
|
||||||
|
// Throw an error instead of returning non-standard response
|
||||||
|
throw new Error('Agent profile not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return this.connectionRequestsService.respondToRequest(
|
||||||
|
id,
|
||||||
|
agentProfile.id,
|
||||||
|
dto,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@Roles(UserRole.USER)
|
||||||
|
@ApiOperation({ summary: 'Cancel a pending connection request' })
|
||||||
|
@ApiResponse({ status: 200, description: 'Connection request cancelled successfully' })
|
||||||
|
@ApiResponse({ status: 400, description: 'Only pending requests can be cancelled' })
|
||||||
|
@ApiResponse({ status: 403, description: 'Not authorized to cancel this request' })
|
||||||
|
@ApiResponse({ status: 404, description: 'Connection request not found' })
|
||||||
|
async cancelRequest(
|
||||||
|
@CurrentUser() user: { id: string },
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
await this.connectionRequestsService.cancelRequest(id, user.id);
|
||||||
|
// Return null/undefined - TransformInterceptor will wrap it as { success: true, data: null }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/connection-requests/connection-requests.module.ts
Normal file
12
src/connection-requests/connection-requests.module.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConnectionRequestsController } from './connection-requests.controller';
|
||||||
|
import { ConnectionRequestsService } from './connection-requests.service';
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [PrismaModule],
|
||||||
|
controllers: [ConnectionRequestsController],
|
||||||
|
providers: [ConnectionRequestsService],
|
||||||
|
exports: [ConnectionRequestsService],
|
||||||
|
})
|
||||||
|
export class ConnectionRequestsModule {}
|
||||||
318
src/connection-requests/connection-requests.service.ts
Normal file
318
src/connection-requests/connection-requests.service.ts
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
import {
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
ConflictException,
|
||||||
|
ForbiddenException,
|
||||||
|
BadRequestException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import { ConnectionStatus } from '@prisma/client';
|
||||||
|
import { CreateConnectionRequestDto, RespondConnectionRequestDto } from './dto';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ConnectionRequestsService {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new connection request from user to agent
|
||||||
|
*/
|
||||||
|
async createRequest(userId: string, dto: CreateConnectionRequestDto) {
|
||||||
|
// Check if agent profile exists and is available
|
||||||
|
const agentProfile = await this.prisma.agentProfile.findUnique({
|
||||||
|
where: { id: dto.agentProfileId },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
isAvailable: true,
|
||||||
|
isPublic: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
userId: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!agentProfile) {
|
||||||
|
throw new NotFoundException('Agent profile not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agentProfile.isPublic) {
|
||||||
|
throw new BadRequestException('This agent profile is not public');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!agentProfile.isAvailable) {
|
||||||
|
throw new BadRequestException('This agent is currently not available for connections');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent users from connecting to their own agent profile
|
||||||
|
if (agentProfile.userId === userId) {
|
||||||
|
throw new BadRequestException('You cannot send a connection request to yourself');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for existing request
|
||||||
|
const existingRequest = await this.prisma.connectionRequest.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_agentProfileId: {
|
||||||
|
userId,
|
||||||
|
agentProfileId: dto.agentProfileId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingRequest) {
|
||||||
|
if (existingRequest.status === ConnectionStatus.PENDING) {
|
||||||
|
throw new ConflictException('You already have a pending request to this agent');
|
||||||
|
}
|
||||||
|
if (existingRequest.status === ConnectionStatus.ACCEPTED) {
|
||||||
|
throw new ConflictException('You are already connected with this agent');
|
||||||
|
}
|
||||||
|
if (existingRequest.status === ConnectionStatus.REJECTED) {
|
||||||
|
// Allow re-requesting after rejection by updating the existing request
|
||||||
|
return this.prisma.connectionRequest.update({
|
||||||
|
where: { id: existingRequest.id },
|
||||||
|
data: {
|
||||||
|
status: ConnectionStatus.PENDING,
|
||||||
|
message: dto.message,
|
||||||
|
respondedAt: null,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
agentProfile: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
avatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new connection request
|
||||||
|
return this.prisma.connectionRequest.create({
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
agentProfileId: dto.agentProfileId,
|
||||||
|
message: dto.message,
|
||||||
|
status: ConnectionStatus.PENDING,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
agentProfile: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
avatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all connection requests sent by a user
|
||||||
|
*/
|
||||||
|
async getMyRequests(userId: string, status?: ConnectionStatus) {
|
||||||
|
const where: { userId: string; status?: ConnectionStatus } = { userId };
|
||||||
|
if (status) {
|
||||||
|
where.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prisma.connectionRequest.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
agentProfile: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
avatar: true,
|
||||||
|
headline: true,
|
||||||
|
city: true,
|
||||||
|
state: true,
|
||||||
|
isVerified: true,
|
||||||
|
agentType: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all connection requests received by an agent
|
||||||
|
*/
|
||||||
|
async getReceivedRequests(agentProfileId: string, status?: ConnectionStatus) {
|
||||||
|
const where: { agentProfileId: string; status?: ConnectionStatus } = { agentProfileId };
|
||||||
|
if (status) {
|
||||||
|
where.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prisma.connectionRequest.findMany({
|
||||||
|
where,
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
avatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Respond to a connection request (accept/reject)
|
||||||
|
*/
|
||||||
|
async respondToRequest(
|
||||||
|
requestId: string,
|
||||||
|
agentProfileId: string,
|
||||||
|
dto: RespondConnectionRequestDto,
|
||||||
|
) {
|
||||||
|
const request = await this.prisma.connectionRequest.findUnique({
|
||||||
|
where: { id: requestId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
throw new NotFoundException('Connection request not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.agentProfileId !== agentProfileId) {
|
||||||
|
throw new ForbiddenException('You are not authorized to respond to this request');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.status !== ConnectionStatus.PENDING) {
|
||||||
|
throw new BadRequestException('This request has already been responded to');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prisma.connectionRequest.update({
|
||||||
|
where: { id: requestId },
|
||||||
|
data: {
|
||||||
|
status: dto.status as ConnectionStatus,
|
||||||
|
respondedAt: new Date(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
avatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a single connection request by ID
|
||||||
|
*/
|
||||||
|
async getRequestById(requestId: string, userId: string, agentProfileId?: string) {
|
||||||
|
const request = await this.prisma.connectionRequest.findUnique({
|
||||||
|
where: { id: requestId },
|
||||||
|
include: {
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
email: true,
|
||||||
|
avatar: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
agentProfile: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
avatar: true,
|
||||||
|
headline: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
throw new NotFoundException('Connection request not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check authorization - either the user who sent it or the agent who received it
|
||||||
|
const isUserRequest = request.userId === userId;
|
||||||
|
const isAgentRequest = agentProfileId && request.agentProfileId === agentProfileId;
|
||||||
|
|
||||||
|
if (!isUserRequest && !isAgentRequest) {
|
||||||
|
throw new ForbiddenException('You are not authorized to view this request');
|
||||||
|
}
|
||||||
|
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel a pending connection request (user only)
|
||||||
|
*/
|
||||||
|
async cancelRequest(requestId: string, userId: string) {
|
||||||
|
const request = await this.prisma.connectionRequest.findUnique({
|
||||||
|
where: { id: requestId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
throw new NotFoundException('Connection request not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.userId !== userId) {
|
||||||
|
throw new ForbiddenException('You are not authorized to cancel this request');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.status !== ConnectionStatus.PENDING) {
|
||||||
|
throw new BadRequestException('Only pending requests can be cancelled');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.prisma.connectionRequest.delete({
|
||||||
|
where: { id: requestId },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get connection status between a user and an agent
|
||||||
|
*/
|
||||||
|
async getConnectionStatus(userId: string, agentProfileId: string) {
|
||||||
|
const request = await this.prisma.connectionRequest.findUnique({
|
||||||
|
where: {
|
||||||
|
userId_agentProfileId: {
|
||||||
|
userId,
|
||||||
|
agentProfileId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
status: true,
|
||||||
|
createdAt: true,
|
||||||
|
respondedAt: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return request || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get counts for agent's connection requests
|
||||||
|
*/
|
||||||
|
async getRequestCounts(agentProfileId: string) {
|
||||||
|
const [pending, accepted, rejected] = await Promise.all([
|
||||||
|
this.prisma.connectionRequest.count({
|
||||||
|
where: { agentProfileId, status: ConnectionStatus.PENDING },
|
||||||
|
}),
|
||||||
|
this.prisma.connectionRequest.count({
|
||||||
|
where: { agentProfileId, status: ConnectionStatus.ACCEPTED },
|
||||||
|
}),
|
||||||
|
this.prisma.connectionRequest.count({
|
||||||
|
where: { agentProfileId, status: ConnectionStatus.REJECTED },
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { pending, accepted, rejected, total: pending + accepted + rejected };
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/connection-requests/dto/create-connection-request.dto.ts
Normal file
20
src/connection-requests/dto/create-connection-request.dto.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
||||||
|
import { IsString, IsOptional, IsUUID, MaxLength } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateConnectionRequestDto {
|
||||||
|
@ApiProperty({
|
||||||
|
example: 'uuid-agent-profile-id',
|
||||||
|
description: 'The ID of the agent profile to connect with',
|
||||||
|
})
|
||||||
|
@IsUUID('4')
|
||||||
|
agentProfileId: string;
|
||||||
|
|
||||||
|
@ApiPropertyOptional({
|
||||||
|
example: 'Hi, I am interested in your services for buying a home.',
|
||||||
|
description: 'Optional message to send with the connection request',
|
||||||
|
})
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(1000)
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
2
src/connection-requests/dto/index.ts
Normal file
2
src/connection-requests/dto/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './create-connection-request.dto';
|
||||||
|
export * from './respond-connection-request.dto';
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsIn } from 'class-validator';
|
||||||
|
|
||||||
|
export class RespondConnectionRequestDto {
|
||||||
|
@ApiProperty({
|
||||||
|
enum: ['ACCEPTED', 'REJECTED'],
|
||||||
|
example: 'ACCEPTED',
|
||||||
|
description: 'The response status (ACCEPTED or REJECTED)',
|
||||||
|
})
|
||||||
|
@IsIn(['ACCEPTED', 'REJECTED'], {
|
||||||
|
message: 'Status must be ACCEPTED or REJECTED',
|
||||||
|
})
|
||||||
|
status: 'ACCEPTED' | 'REJECTED';
|
||||||
|
}
|
||||||
3
src/connection-requests/index.ts
Normal file
3
src/connection-requests/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export * from './connection-requests.module';
|
||||||
|
export * from './connection-requests.service';
|
||||||
|
export * from './connection-requests.controller';
|
||||||
@@ -228,7 +228,8 @@ export class ProfileFieldsService {
|
|||||||
|
|
||||||
await this.prisma.$transaction(operations);
|
await this.prisma.$transaction(operations);
|
||||||
|
|
||||||
return { success: true, updated: updates.length };
|
// Return raw data - TransformInterceptor will wrap it
|
||||||
|
return { updated: updates.length };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user