الملفات
back_end_oudelaa/src/modules/likes/likes.service.ts
boutmoun123 5bd5e19a89
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
feat: expand backend admin marketplace and scaling
2026-05-14 16:44:07 +03:00

155 أسطر
5.5 KiB
TypeScript

import { Injectable, Logger, NotFoundException } from '@nestjs/common';
import { Types } from 'mongoose';
import { FeedVersionService } from '../../infrastructure/cache/feed-version.service';
import { NotificationsService } from '../notifications/notifications.service';
import { CommentsRepository } from '../comments/comments.repository';
import { PostsRepository } from '../posts/posts.repository';
import { LikesRepository } from './likes.repository';
import { ToggleLikeDto } from './dto/toggle-like.dto';
@Injectable()
export class LikesService {
private readonly logger = new Logger(LikesService.name);
constructor(
private readonly likesRepository: LikesRepository,
private readonly postsRepository: PostsRepository,
private readonly commentsRepository: CommentsRepository,
private readonly feedVersionService: FeedVersionService,
private readonly notificationsService: NotificationsService,
) {}
async toggle(userId: string, dto: ToggleLikeDto): Promise<{ liked: boolean; targetId: string; targetType: string }> {
const existing = await this.likesRepository.findOne(userId, dto.targetId, dto.targetType);
return existing ? this.unlike(userId, dto) : this.like(userId, dto);
}
async like(userId: string, dto: ToggleLikeDto): Promise<{ liked: boolean; targetId: string; targetType: string }> {
await this.assertTargetExists(dto);
const notificationContext = await this.resolveNotificationContext(dto);
const existing = await this.likesRepository.findOne(userId, dto.targetId, dto.targetType);
if (existing) {
return { liked: true, targetId: dto.targetId, targetType: dto.targetType };
}
await this.likesRepository.create(userId, dto.targetId, dto.targetType);
if (dto.targetType === 'post') {
await this.postsRepository.incrementLikesCount(dto.targetId, 1);
}
await this.feedVersionService.bumpGlobalVersion();
if (notificationContext.recipientId && notificationContext.recipientId !== userId) {
try {
await this.notificationsService.createLikeNotification(
userId,
notificationContext.recipientId,
dto.targetId,
{
resourceType: dto.targetType,
previewText: notificationContext.previewText,
},
);
} catch (error) {
this.logger.warn(
`Like notification failed for actor=${userId} recipient=${notificationContext.recipientId}: ${
error instanceof Error ? error.message : 'unknown error'
}`,
);
}
}
return { liked: true, targetId: dto.targetId, targetType: dto.targetType };
}
async unlike(userId: string, dto: ToggleLikeDto): Promise<{ liked: boolean; targetId: string; targetType: string }> {
await this.assertTargetExists(dto);
const existing = await this.likesRepository.findOne(userId, dto.targetId, dto.targetType);
if (!existing) {
return { liked: false, targetId: dto.targetId, targetType: dto.targetType };
}
await this.likesRepository.deleteById(existing.id);
if (dto.targetType === 'post') {
await this.postsRepository.incrementLikesCount(dto.targetId, -1);
}
await this.feedVersionService.bumpGlobalVersion();
return { liked: false, targetId: dto.targetId, targetType: dto.targetType };
}
async getStatus(userId: string, dto: ToggleLikeDto): Promise<{ liked: boolean; targetId: string; targetType: string }> {
const targetExists = await this.targetExists(dto);
if (!targetExists) {
return { liked: false, targetId: dto.targetId, targetType: dto.targetType };
}
const existing = await this.likesRepository.findOne(userId, dto.targetId, dto.targetType);
return { liked: !!existing, targetId: dto.targetId, targetType: dto.targetType };
}
private async assertTargetExists(dto: ToggleLikeDto): Promise<void> {
const targetExists = await this.targetExists(dto);
if (!targetExists) {
throw new NotFoundException(dto.targetType === 'post' ? 'Post not found' : 'Comment not found');
}
}
private async targetExists(dto: ToggleLikeDto): Promise<boolean> {
if (dto.targetType === 'post') {
const post = await this.postsRepository.findById(dto.targetId);
return !!post;
}
const comment = await this.commentsRepository.findById(dto.targetId);
return !!comment;
}
private async resolveNotificationContext(
dto: ToggleLikeDto,
): Promise<{ recipientId: string; previewText: string }> {
if (dto.targetType === 'post') {
const post = await this.postsRepository.findById(dto.targetId);
return {
recipientId: this.extractEntityId(post?.authorId),
previewText: (post?.content ?? '').slice(0, 140),
};
}
const comment = await this.commentsRepository.findById(dto.targetId);
return {
recipientId: comment?.authorId?.toString?.() ?? '',
previewText: (comment?.content ?? '').slice(0, 140),
};
}
private extractEntityId(value: unknown): string {
if (!value) {
return '';
}
if (typeof value === 'string') {
return value;
}
if (value instanceof Types.ObjectId) {
return value.toString();
}
if (typeof value === 'object') {
const candidate = value as { _id?: unknown; id?: unknown };
if (candidate._id instanceof Types.ObjectId) {
return candidate._id.toString();
}
if (typeof candidate._id === 'string') {
return candidate._id;
}
if (typeof candidate.id === 'string') {
return candidate.id;
}
}
return '';
}
}