first commit
This commit is contained in:
189
src/modules/chat/chat.service.ts
Normal file
189
src/modules/chat/chat.service.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { and, eq, or } from 'drizzle-orm';
|
||||
import { DrizzleService } from '../../database/drizzle.service';
|
||||
import { chat, match, message, profile, user } from '../../database/schema';
|
||||
import { NotificationsService } from '../../notifications/notifications.service';
|
||||
import { CreateChatDto } from './dto/create-chat.dto';
|
||||
import { SendMessageDto } from './dto/send-message.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ChatService {
|
||||
constructor(
|
||||
private readonly drizzleService: DrizzleService,
|
||||
private readonly notificationsService: NotificationsService,
|
||||
) {}
|
||||
|
||||
async createChat(userId: string, dto: CreateChatDto) {
|
||||
const [foundMatch] = await this.drizzleService.db
|
||||
.select()
|
||||
.from(match)
|
||||
.where(
|
||||
and(
|
||||
eq(match.id, dto.matchId),
|
||||
or(eq(match.user1Id, userId), eq(match.user2Id, userId)),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (!foundMatch) throw new NotFoundException('Match not found');
|
||||
|
||||
const existingChat = await this.drizzleService.db
|
||||
.select()
|
||||
.from(chat)
|
||||
.where(
|
||||
or(
|
||||
and(
|
||||
eq(chat.profile1Id, foundMatch.user1Id),
|
||||
eq(chat.profile2Id, foundMatch.user2Id),
|
||||
),
|
||||
and(
|
||||
eq(chat.profile1Id, foundMatch.user2Id),
|
||||
eq(chat.profile2Id, foundMatch.user1Id),
|
||||
),
|
||||
),
|
||||
)
|
||||
.limit(1);
|
||||
|
||||
if (existingChat.length > 0) return existingChat[0];
|
||||
|
||||
const currentUser = await this.drizzleService.db
|
||||
.select({ activeChatId: user.activeChatId })
|
||||
.from(user)
|
||||
.where(eq(user.id, userId))
|
||||
.limit(1);
|
||||
|
||||
if (currentUser[0]?.activeChatId) {
|
||||
throw new BadRequestException(
|
||||
'You already have an active chat. Close it before opening a new one.',
|
||||
);
|
||||
}
|
||||
|
||||
const [newChat] = await this.drizzleService.db
|
||||
.insert(chat)
|
||||
.values({
|
||||
profile1Id: foundMatch.user1Id,
|
||||
profile2Id: foundMatch.user2Id,
|
||||
status: 'active',
|
||||
} as any)
|
||||
.returning();
|
||||
|
||||
await this.drizzleService.db
|
||||
.update(user)
|
||||
.set({ activeChatId: newChat.id } as any)
|
||||
.where(or(eq(user.id, foundMatch.user1Id), eq(user.id, foundMatch.user2Id)));
|
||||
|
||||
return newChat;
|
||||
}
|
||||
|
||||
async closeChat(userId: string, chatId: string) {
|
||||
const [foundChat] = await this.drizzleService.db
|
||||
.select()
|
||||
.from(chat)
|
||||
.where(eq(chat.id, chatId))
|
||||
.limit(1);
|
||||
|
||||
if (!foundChat) throw new NotFoundException('Chat not found');
|
||||
if (foundChat.profile1Id !== userId && foundChat.profile2Id !== userId) {
|
||||
throw new ForbiddenException('Not a chat participant');
|
||||
}
|
||||
|
||||
await this.drizzleService.db
|
||||
.update(chat)
|
||||
.set({ status: 'closed' } as any)
|
||||
.where(eq(chat.id, chatId));
|
||||
|
||||
await this.drizzleService.db
|
||||
.update(user)
|
||||
.set({ activeChatId: null } as any)
|
||||
.where(or(eq(user.id, foundChat.profile1Id), eq(user.id, foundChat.profile2Id)));
|
||||
|
||||
return { message: 'Chat closed' };
|
||||
}
|
||||
|
||||
async getMyChats(userId: string) {
|
||||
return this.drizzleService.db
|
||||
.select()
|
||||
.from(chat)
|
||||
.where(
|
||||
and(
|
||||
or(eq(chat.profile1Id, userId), eq(chat.profile2Id, userId)),
|
||||
eq(chat.status, 'active'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async getChatMessages(userId: string, chatId: string, page = 1, limit = 50) {
|
||||
const [foundChat] = await this.drizzleService.db
|
||||
.select()
|
||||
.from(chat)
|
||||
.where(eq(chat.id, chatId))
|
||||
.limit(1);
|
||||
|
||||
if (!foundChat) throw new NotFoundException('Chat not found');
|
||||
if (foundChat.profile1Id !== userId && foundChat.profile2Id !== userId) {
|
||||
throw new ForbiddenException('Not a chat participant');
|
||||
}
|
||||
|
||||
const offset = (page - 1) * limit;
|
||||
return this.drizzleService.db
|
||||
.select()
|
||||
.from(message)
|
||||
.where(eq(message.chatId, chatId))
|
||||
.orderBy(message.createdAt)
|
||||
.limit(limit)
|
||||
.offset(offset);
|
||||
}
|
||||
|
||||
async sendMessage(userId: string, chatId: string, dto: SendMessageDto) {
|
||||
const [foundChat] = await this.drizzleService.db
|
||||
.select()
|
||||
.from(chat)
|
||||
.where(and(eq(chat.id, chatId), eq(chat.status, 'active')))
|
||||
.limit(1);
|
||||
|
||||
if (!foundChat) throw new NotFoundException('Active chat not found');
|
||||
if (foundChat.profile1Id !== userId && foundChat.profile2Id !== userId) {
|
||||
throw new ForbiddenException('Not a chat participant');
|
||||
}
|
||||
|
||||
if (!dto.text && !dto.mediaUrl) {
|
||||
throw new BadRequestException('Message must have text or media');
|
||||
}
|
||||
|
||||
const [newMessage] = await this.drizzleService.db
|
||||
.insert(message)
|
||||
.values({
|
||||
chatId,
|
||||
userId,
|
||||
text: dto.text || null,
|
||||
mediaUrl: dto.mediaUrl || null,
|
||||
mediaType: dto.mediaType || null,
|
||||
} as any)
|
||||
.returning();
|
||||
|
||||
const recipientId =
|
||||
foundChat.profile1Id === userId ? foundChat.profile2Id : foundChat.profile1Id;
|
||||
|
||||
const [recipient] = await this.drizzleService.db
|
||||
.select({ fcmToken: user.fcmToken })
|
||||
.from(user)
|
||||
.where(eq(user.id, recipientId))
|
||||
.limit(1);
|
||||
|
||||
if (recipient?.fcmToken) {
|
||||
await this.notificationsService.sendPushNotification(
|
||||
recipient.fcmToken,
|
||||
'New message',
|
||||
dto.text?.substring(0, 100) || 'Media message',
|
||||
{ chatId, messageId: newMessage.id, type: 'message' },
|
||||
);
|
||||
}
|
||||
|
||||
return newMessage;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user