feat: expand backend admin marketplace and scaling
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
فشلت بعض الفحوصات
/ deploy (push) Failing after 1m22s
هذا الالتزام موجود في:
@@ -25,14 +25,14 @@ export class FollowsController {
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('followers/:userId')
|
||||
async followers(@Param('userId') userId: string, @Query() query: PaginationQueryDto) {
|
||||
return this.followsService.getFollowers(userId, query.page, query.limit);
|
||||
return this.followsService.getFollowers(userId, query);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Get('following/:userId')
|
||||
async following(@Param('userId') userId: string, @Query() query: PaginationQueryDto) {
|
||||
return this.followsService.getFollowing(userId, query.page, query.limit);
|
||||
return this.followsService.getFollowing(userId, query);
|
||||
}
|
||||
|
||||
@ApiBearerAuth()
|
||||
@@ -47,6 +47,6 @@ export class FollowsController {
|
||||
@Get('suggestions')
|
||||
@Throttle(60, 60_000)
|
||||
async suggestions(@CurrentUser() user: JwtPayload, @Query() query: PaginationQueryDto) {
|
||||
return this.followsService.getSuggestions(user.sub, query.page, query.limit);
|
||||
return this.followsService.getSuggestions(user.sub, query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,12 +38,17 @@ export class FollowsRepository {
|
||||
await this.followModel.findByIdAndDelete(id, { session }).exec();
|
||||
}
|
||||
|
||||
async findMany(filter: FilterQuery<FollowDocument>, skip: number, limit: number): Promise<FollowDocument[]> {
|
||||
async findMany(
|
||||
filter: FilterQuery<FollowDocument>,
|
||||
skip: number,
|
||||
limit: number,
|
||||
sort: Record<string, 1 | -1> = { createdAt: -1 },
|
||||
): Promise<FollowDocument[]> {
|
||||
return this.followModel
|
||||
.find(filter)
|
||||
.populate({ path: 'followerId', select: 'name username stageName avatar isVerified isDisabled' })
|
||||
.populate({ path: 'followingId', select: 'name username stageName avatar isVerified isDisabled' })
|
||||
.sort({ createdAt: -1 })
|
||||
.sort(sort)
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.exec();
|
||||
|
||||
@@ -26,6 +26,7 @@ describe('FollowsService', () => {
|
||||
followsRepository as any,
|
||||
usersRepository as any,
|
||||
outboxService as any,
|
||||
{ bumpGlobalVersion: jest.fn().mockResolvedValue(1) } as any,
|
||||
);
|
||||
|
||||
await expect(service.toggleFollow(currentUserId, { targetUserId })).resolves.toEqual({
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
||||
import { Types } from 'mongoose';
|
||||
import { PaginationQueryDto } from '../../common/dto/pagination-query.dto';
|
||||
import { buildPaginatedResponse } from '../../common/utils/pagination.util';
|
||||
import { resolveMongoSortDirection } from '../../common/utils/sort.util';
|
||||
import { FeedVersionService } from '../../infrastructure/cache/feed-version.service';
|
||||
import { OutboxService } from '../outbox/outbox.service';
|
||||
import { UsersRepository } from '../users/users.repository';
|
||||
import { UserDocument } from '../users/schemas/user.schema';
|
||||
@@ -14,6 +18,7 @@ export class FollowsService {
|
||||
private readonly followsRepository: FollowsRepository,
|
||||
private readonly usersRepository: UsersRepository,
|
||||
private readonly outboxService: OutboxService,
|
||||
private readonly feedVersionService: FeedVersionService,
|
||||
) {}
|
||||
|
||||
async toggleFollow(currentUserId: string, dto: ToggleFollowDto) {
|
||||
@@ -37,11 +42,13 @@ export class FollowsService {
|
||||
if (existing) {
|
||||
await this.followsRepository.deleteById(existing.id);
|
||||
await this.syncFollowCounts(currentUserId, targetUserId);
|
||||
await this.feedVersionService.bumpGlobalVersion();
|
||||
return { following: false };
|
||||
}
|
||||
|
||||
const follow = await this.followsRepository.create(currentUserId, targetUserId);
|
||||
await this.syncFollowCounts(currentUserId, targetUserId);
|
||||
await this.feedVersionService.bumpGlobalVersion();
|
||||
|
||||
try {
|
||||
await this.outboxService.enqueueFollowNotification(currentUserId, targetUserId, follow.id);
|
||||
@@ -56,36 +63,40 @@ export class FollowsService {
|
||||
return { following: true };
|
||||
}
|
||||
|
||||
async getFollowers(userId: string, page = 1, limit = 20) {
|
||||
async getFollowers(userId: string, query: PaginationQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
const limit = query.limit ?? 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const sort = { createdAt: resolveMongoSortDirection(query.sortOrder) } as Record<string, 1 | -1>;
|
||||
const [items, total] = await Promise.all([
|
||||
this.followsRepository.findMany({ followingId: userId }, skip, limit),
|
||||
this.followsRepository.findMany({ followingId: userId }, skip, limit, sort),
|
||||
this.followsRepository.count({ followingId: userId }),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
return buildPaginatedResponse(items, {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit) || 1,
|
||||
};
|
||||
offset: skip,
|
||||
});
|
||||
}
|
||||
|
||||
async getFollowing(userId: string, page = 1, limit = 20) {
|
||||
async getFollowing(userId: string, query: PaginationQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
const limit = query.limit ?? 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const sort = { createdAt: resolveMongoSortDirection(query.sortOrder) } as Record<string, 1 | -1>;
|
||||
const [items, total] = await Promise.all([
|
||||
this.followsRepository.findMany({ followerId: userId }, skip, limit),
|
||||
this.followsRepository.findMany({ followerId: userId }, skip, limit, sort),
|
||||
this.followsRepository.count({ followerId: userId }),
|
||||
]);
|
||||
|
||||
return {
|
||||
items,
|
||||
return buildPaginatedResponse(items, {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit) || 1,
|
||||
};
|
||||
offset: skip,
|
||||
});
|
||||
}
|
||||
|
||||
async getFollowStatus(currentUserId: string, targetUserId: string) {
|
||||
@@ -104,7 +115,9 @@ export class FollowsService {
|
||||
};
|
||||
}
|
||||
|
||||
async getSuggestions(currentUserId: string, page = 1, limit = 20) {
|
||||
async getSuggestions(currentUserId: string, query: PaginationQueryDto) {
|
||||
const page = query.page ?? 1;
|
||||
const limit = query.limit ?? 20;
|
||||
const currentUser = await this.usersRepository.findById(currentUserId);
|
||||
if (!currentUser) {
|
||||
throw new NotFoundException('Current user not found');
|
||||
@@ -128,6 +141,10 @@ export class FollowsService {
|
||||
}))
|
||||
.sort((a, b) => b.score - a.score || b.user.followersCount - a.user.followersCount);
|
||||
|
||||
if (query.sortOrder === 'asc') {
|
||||
ranked.reverse();
|
||||
}
|
||||
|
||||
const total = ranked.length;
|
||||
const skip = (page - 1) * limit;
|
||||
const items = ranked.slice(skip, skip + limit).map((entry) => ({
|
||||
@@ -136,13 +153,12 @@ export class FollowsService {
|
||||
reasons: this.buildSuggestionReasons(currentUser, entry.user),
|
||||
}));
|
||||
|
||||
return {
|
||||
items,
|
||||
return buildPaginatedResponse(items, {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit) || 1,
|
||||
};
|
||||
offset: skip,
|
||||
});
|
||||
}
|
||||
|
||||
private calculateSuggestionScore(currentUser: UserDocument, candidate: UserDocument): number {
|
||||
|
||||
المرجع في مشكلة جديدة
حظر مستخدم