feat: expand backend admin marketplace and scaling
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
هذا الالتزام موجود في:
@@ -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 '';
|
||||
}
|
||||
}
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم