This commit is contained in:
Oscar
2026-06-02 16:22:53 +03:00
parent dc44cdd639
commit bc3e48bcad
37 changed files with 973 additions and 1894 deletions

View File

@@ -1,13 +1,10 @@
import {
BadRequestException,
Injectable,
} from '@nestjs/common';
import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { and, eq, or } from 'drizzle-orm';
import { ConfigService } from '@nestjs/config';
import { DrizzleService } from '../../database/drizzle.service';
import { like, match, user } from '../../database/schema';
import { like, match, profile, user } from '../../database/schema';
import { NotificationsService } from '../../notifications/notifications.service';
import { RedisService } from '../../redis/redis.service';
import { ConfigService } from '@nestjs/config';
import { CreateLikeDto } from './dto/create-like.dto';
@Injectable()
@@ -19,16 +16,18 @@ export class LikesService {
private readonly configService: ConfigService,
) {}
async createLike(sourceUserId: string, dto: CreateLikeDto) {
if (sourceUserId === dto.targetUserId) {
async createLike(userId: string, dto: CreateLikeDto) {
await this.assertProfileOwnership(userId, dto.sourceProfileId);
if (dto.sourceProfileId === dto.targetProfileId) {
throw new BadRequestException('Cannot like yourself');
}
const maxMatches = this.configService.get<number>('app.maxMatchesBeforePause');
const activeMatchesCount = await this.getActiveMatchesCount(sourceUserId);
const activeMatchesCount = await this.getMatchesCount(dto.sourceProfileId);
if (activeMatchesCount >= maxMatches) {
throw new BadRequestException(
`You have ${activeMatchesCount} matches. Resolve them before searching for new ones.`,
`Profile has ${activeMatchesCount} matches. Resolve them before searching for new ones.`,
);
}
@@ -37,92 +36,99 @@ export class LikesService {
.from(like)
.where(
and(
eq(like.sourceUser, sourceUserId),
eq(like.targetUser, dto.targetUserId),
eq(like.sourceProfileId, dto.sourceProfileId),
eq(like.targetProfileId, dto.targetProfileId),
),
)
.limit(1);
if (existing.length > 0) {
throw new BadRequestException('Already reacted to this user');
}
if (existing.length > 0) throw new BadRequestException('Already reacted to this profile');
const [newLike] = await this.drizzleService.db
.insert(like)
.values({
sourceUser: sourceUserId,
targetUser: dto.targetUserId,
sourceProfileId: dto.sourceProfileId,
targetProfileId: dto.targetProfileId,
type: dto.type,
})
.returning();
if (dto.type === 'like') {
return this.checkAndCreateMatch(sourceUserId, dto.targetUserId, newLike);
return this.checkAndCreateMatch(dto.sourceProfileId, dto.targetProfileId, newLike);
}
return { like: newLike, match: null };
}
private async checkAndCreateMatch(userId1: string, userId2: string, newLike: any) {
async getMyMatches(userId: string, profileId: string) {
await this.assertProfileOwnership(userId, profileId);
return this.drizzleService.db
.select()
.from(match)
.where(or(eq(match.profile1Id, profileId), eq(match.profile2Id, profileId)))
.orderBy(match.createdAt);
}
private async checkAndCreateMatch(profileId1: string, profileId2: string, newLike: any) {
const reverseLike = await this.drizzleService.db
.select()
.from(like)
.where(
and(
eq(like.sourceUser, userId2),
eq(like.targetUser, userId1),
eq(like.sourceProfileId, profileId2),
eq(like.targetProfileId, profileId1),
eq(like.type, 'like'),
),
)
.limit(1);
if (reverseLike.length === 0) {
return { like: newLike, match: null };
}
if (reverseLike.length === 0) return { like: newLike, match: null };
const existingMatch = await this.drizzleService.db
.select()
.from(match)
.where(
or(
and(eq(match.user1Id, userId1), eq(match.user2Id, userId2)),
and(eq(match.user1Id, userId2), eq(match.user2Id, userId1)),
and(eq(match.profile1Id, profileId1), eq(match.profile2Id, profileId2)),
and(eq(match.profile1Id, profileId2), eq(match.profile2Id, profileId1)),
),
)
.limit(1);
if (existingMatch.length > 0) {
return { like: newLike, match: existingMatch[0] };
}
if (existingMatch.length > 0) return { like: newLike, match: existingMatch[0] };
const [newMatch] = await this.drizzleService.db
.insert(match)
.values({ user1Id: userId1, user2Id: userId2 })
.values({ profile1Id: profileId1, profile2Id: profileId2 })
.returning();
await this.notifyMatch(userId1, userId2, newMatch.id);
await this.notifyMatch(profileId1, profileId2, newMatch.id);
return { like: newLike, match: newMatch };
}
private async getActiveMatchesCount(userId: string): Promise<number> {
private async getMatchesCount(profileId: string): Promise<number> {
const matches = await this.drizzleService.db
.select({ id: match.id })
.from(match)
.where(
or(eq(match.user1Id, userId), eq(match.user2Id, userId)),
);
.where(or(eq(match.profile1Id, profileId), eq(match.profile2Id, profileId)));
return matches.length;
}
private async notifyMatch(userId1: string, userId2: string, matchId: string) {
const users = await this.drizzleService.db
.select({ id: user.id, fcmToken: user.fcmToken })
.from(user)
.where(or(eq(user.id, userId1), eq(user.id, userId2)));
private async notifyMatch(profileId1: string, profileId2: string, matchId: string) {
const profiles = await this.drizzleService.db
.select({ userId: profile.userId })
.from(profile)
.where(or(eq(profile.id, profileId1), eq(profile.id, profileId2)));
for (const u of users) {
if (u.fcmToken) {
for (const p of profiles) {
const [u] = await this.drizzleService.db
.select({ fcmToken: user.fcmToken })
.from(user)
.where(eq(user.id, p.userId))
.limit(1);
if (u?.fcmToken) {
await this.notificationsService.sendPushNotification(
u.fcmToken,
'New Match!',
@@ -132,16 +138,20 @@ export class LikesService {
}
}
await this.redisService.publish('match:created', JSON.stringify({ matchId, userId1, userId2 }));
await this.redisService.publish(
'match:created',
JSON.stringify({ matchId, profileId1, profileId2 }),
);
}
async getMyMatches(userId: string) {
return this.drizzleService.db
.select()
.from(match)
.where(
or(eq(match.user1Id, userId), eq(match.user2Id, userId)),
)
.orderBy(match.createdAt);
private async assertProfileOwnership(userId: string, profileId: string) {
const [found] = await this.drizzleService.db
.select({ userId: profile.userId })
.from(profile)
.where(eq(profile.id, profileId))
.limit(1);
if (!found) throw new NotFoundException('Profile not found');
if (found.userId !== userId) throw new ForbiddenException('Profile does not belong to you');
}
}