feat: expand backend admin marketplace and scaling
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s

هذا الالتزام موجود في:
2026-05-14 16:17:12 +03:00
الأصل 0e76a4a9fc
التزام 5bd5e19a89
158 ملفات معدلة مع 19563 إضافات و3315 حذوفات

عرض الملف

@@ -1,4 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
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';
@@ -6,10 +9,14 @@ 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 }> {
@@ -19,6 +26,7 @@ export class LikesService {
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) {
@@ -29,6 +37,26 @@ export class LikesService {
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 };
}
@@ -45,6 +73,7 @@ export class LikesService {
if (dto.targetType === 'post') {
await this.postsRepository.incrementLikesCount(dto.targetId, -1);
}
await this.feedVersionService.bumpGlobalVersion();
return { liked: false, targetId: dto.targetId, targetType: dto.targetType };
}
@@ -75,4 +104,51 @@ export class LikesService {
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 '';
}
}