first commit

This commit is contained in:
Oscar
2026-06-02 15:52:22 +03:00
commit dc44cdd639
105 changed files with 14674 additions and 0 deletions

View File

@@ -0,0 +1,147 @@
import {
BadRequestException,
Injectable,
} from '@nestjs/common';
import { and, eq, or } from 'drizzle-orm';
import { DrizzleService } from '../../database/drizzle.service';
import { like, match, 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()
export class LikesService {
constructor(
private readonly drizzleService: DrizzleService,
private readonly notificationsService: NotificationsService,
private readonly redisService: RedisService,
private readonly configService: ConfigService,
) {}
async createLike(sourceUserId: string, dto: CreateLikeDto) {
if (sourceUserId === dto.targetUserId) {
throw new BadRequestException('Cannot like yourself');
}
const maxMatches = this.configService.get<number>('app.maxMatchesBeforePause');
const activeMatchesCount = await this.getActiveMatchesCount(sourceUserId);
if (activeMatchesCount >= maxMatches) {
throw new BadRequestException(
`You have ${activeMatchesCount} matches. Resolve them before searching for new ones.`,
);
}
const existing = await this.drizzleService.db
.select()
.from(like)
.where(
and(
eq(like.sourceUser, sourceUserId),
eq(like.targetUser, dto.targetUserId),
),
)
.limit(1);
if (existing.length > 0) {
throw new BadRequestException('Already reacted to this user');
}
const [newLike] = await this.drizzleService.db
.insert(like)
.values({
sourceUser: sourceUserId,
targetUser: dto.targetUserId,
type: dto.type,
})
.returning();
if (dto.type === 'like') {
return this.checkAndCreateMatch(sourceUserId, dto.targetUserId, newLike);
}
return { like: newLike, match: null };
}
private async checkAndCreateMatch(userId1: string, userId2: string, newLike: any) {
const reverseLike = await this.drizzleService.db
.select()
.from(like)
.where(
and(
eq(like.sourceUser, userId2),
eq(like.targetUser, userId1),
eq(like.type, 'like'),
),
)
.limit(1);
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)),
),
)
.limit(1);
if (existingMatch.length > 0) {
return { like: newLike, match: existingMatch[0] };
}
const [newMatch] = await this.drizzleService.db
.insert(match)
.values({ user1Id: userId1, user2Id: userId2 })
.returning();
await this.notifyMatch(userId1, userId2, newMatch.id);
return { like: newLike, match: newMatch };
}
private async getActiveMatchesCount(userId: 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)),
);
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)));
for (const u of users) {
if (u.fcmToken) {
await this.notificationsService.sendPushNotification(
u.fcmToken,
'New Match!',
'You have a new match! Start chatting now.',
{ matchId, type: 'match' },
);
}
}
await this.redisService.publish('match:created', JSON.stringify({ matchId, userId1, userId2 }));
}
async getMyMatches(userId: string) {
return this.drizzleService.db
.select()
.from(match)
.where(
or(eq(match.user1Id, userId), eq(match.user2Id, userId)),
)
.orderBy(match.createdAt);
}
}