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,6 +1,7 @@
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { CommentsModule } from '../comments/comments.module';
import { NotificationsModule } from '../notifications/notifications.module';
import { PostsModule } from '../posts/posts.module';
import { Like, LikeSchema } from './schemas/like.schema';
import { LikesController } from './likes.controller';
@@ -12,9 +13,10 @@ import { LikesService } from './likes.service';
MongooseModule.forFeature([{ name: Like.name, schema: LikeSchema }]),
PostsModule,
CommentsModule,
NotificationsModule,
],
controllers: [LikesController],
providers: [LikesService, LikesRepository],
exports: [LikesService],
exports: [LikesService, LikesRepository],
})
export class LikesModule {}

عرض الملف

@@ -25,6 +25,24 @@ export class LikesRepository {
});
}
async findLikedPostIds(userId: string, postIds: string[]): Promise<string[]> {
if (!postIds.length) {
return [];
}
const rows = await this.likeModel
.find({
userId: new Types.ObjectId(userId),
targetType: 'post',
targetId: { $in: postIds.map((id) => new Types.ObjectId(id)) },
})
.select({ targetId: 1 })
.lean()
.exec();
return rows.map((row) => row.targetId.toString());
}
async deleteById(id: string): Promise<void> {
await this.likeModel.findByIdAndDelete(id).exec();
}

عرض الملف

@@ -16,6 +16,8 @@ describe('LikesService', () => {
likesRepository as any,
postsRepository as any,
commentsRepository as any,
{ bumpGlobalVersion: jest.fn() } as any,
{ createLikeNotification: jest.fn() } as any,
);
await expect(

عرض الملف

@@ -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 '';
}
}