Add reels feed support
This commit is contained in:
@@ -17,7 +17,7 @@ import { PostShareTarget } from '../posts/dto/share-post.dto';
|
||||
import { SavesRepository } from '../saves/saves.repository';
|
||||
import { UserDocument } from '../users/schemas/user.schema';
|
||||
import { UsersRepository } from '../users/users.repository';
|
||||
import { FeedQueryDto } from './dto/feed-query.dto';
|
||||
import { FeedPostTypeFilter, FeedQueryDto } from './dto/feed-query.dto';
|
||||
import { FeedRepository } from './feed.repository';
|
||||
|
||||
type FeedPostItem = Record<string, unknown> & {
|
||||
@@ -119,6 +119,7 @@ export class FeedService {
|
||||
const page = query.page ?? 1;
|
||||
const radiusKm = query.radiusKm ?? 30;
|
||||
const skip = cursorOffset ?? (page - 1) * limit;
|
||||
const requestedPostType = this.resolveRequestedPostType(query.preferredPostType);
|
||||
|
||||
const [followingIds, invisibleUserIds] = await Promise.all([
|
||||
this.feedRepository.findFollowingIds(currentUserId),
|
||||
@@ -126,6 +127,9 @@ export class FeedService {
|
||||
]);
|
||||
const relationLookupMs = this.markTiming(timing);
|
||||
let filter = this.buildVisiblePostsFilter(currentUserId, followingIds, followingOnly, invisibleUserIds);
|
||||
if (requestedPostType) {
|
||||
filter = { $and: [filter, { postType: requestedPostType }] };
|
||||
}
|
||||
let candidates = await this.feedRepository.findCandidatePosts(filter, Math.max(limit * 12, 300));
|
||||
const firstCandidateLookupMs = this.markTiming(timing);
|
||||
|
||||
@@ -137,17 +141,18 @@ export class FeedService {
|
||||
followingOnly
|
||||
) {
|
||||
filter = this.buildVisiblePostsFilter(currentUserId, followingIds, false, invisibleUserIds);
|
||||
if (requestedPostType) {
|
||||
filter = { $and: [filter, { postType: requestedPostType }] };
|
||||
}
|
||||
candidates = await this.feedRepository.findCandidatePosts(filter, Math.max(limit * 12, 300));
|
||||
}
|
||||
const fallbackCandidateLookupMs = this.markTiming(timing);
|
||||
|
||||
const scored = candidates
|
||||
.filter((post) => {
|
||||
if (!query.preferredPostType) {
|
||||
return true;
|
||||
}
|
||||
return post.postType === query.preferredPostType;
|
||||
})
|
||||
const filteredCandidates = requestedPostType
|
||||
? candidates.filter((post) => post.postType === requestedPostType)
|
||||
: candidates;
|
||||
|
||||
const scored = filteredCandidates
|
||||
.map((post) => ({
|
||||
post,
|
||||
score: this.scorePost({
|
||||
@@ -173,10 +178,14 @@ export class FeedService {
|
||||
feedScore: Number(entry.score.toFixed(3)),
|
||||
}));
|
||||
const decoratedPosts = await this.decoratePostsForViewer(currentUserId, pagedPosts, followingIds);
|
||||
const normalizedPosts = this.normalizePreferredPostTypeForResponse(
|
||||
decoratedPosts,
|
||||
query.preferredPostType,
|
||||
);
|
||||
const decorationMs = this.markTiming(timing);
|
||||
const items = includeSuggestions
|
||||
? await this.mixHomeFeedItems(currentUserId, decoratedPosts, query.suggestionInterval ?? 4)
|
||||
: decoratedPosts;
|
||||
? await this.mixHomeFeedItems(currentUserId, normalizedPosts, query.suggestionInterval ?? 4)
|
||||
: normalizedPosts;
|
||||
const cardsMs = this.markTiming(timing);
|
||||
const nextOffset = skip + pagedPosts.length;
|
||||
const nextCursor = nextOffset < total ? encodeOffsetCursor(nextOffset) : null;
|
||||
@@ -209,9 +218,10 @@ export class FeedService {
|
||||
followingCount: followingIds.length,
|
||||
invisibleUserCount: invisibleUserIds.length,
|
||||
candidateCount: candidates.length,
|
||||
filteredCandidateCount: filteredCandidates.length,
|
||||
scoredCount: scored.length,
|
||||
itemCount: items.length,
|
||||
postItemCount: decoratedPosts.length,
|
||||
postItemCount: normalizedPosts.length,
|
||||
nextCursor,
|
||||
relationLookupMs,
|
||||
firstCandidateLookupMs,
|
||||
@@ -233,6 +243,14 @@ export class FeedService {
|
||||
});
|
||||
}
|
||||
|
||||
async getReels(currentUserId: string, query: FeedQueryDto) {
|
||||
return this.getMyFeed(currentUserId, {
|
||||
...query,
|
||||
preferredPostType: 'reel',
|
||||
includeSuggestions: false,
|
||||
});
|
||||
}
|
||||
|
||||
async getTrending(currentUserId: string, query: FeedQueryDto) {
|
||||
const timing = this.startTiming('feed.trending');
|
||||
const cacheEnabled =
|
||||
@@ -272,8 +290,9 @@ export class FeedService {
|
||||
if (invisibleUserIds.length) {
|
||||
trendingFilter.authorId = { $nin: invisibleUserIds.map((id) => new Types.ObjectId(id)) };
|
||||
}
|
||||
if (query.preferredPostType) {
|
||||
trendingFilter.postType = query.preferredPostType;
|
||||
const requestedPostType = this.resolveRequestedPostType(query.preferredPostType);
|
||||
if (requestedPostType) {
|
||||
trendingFilter.postType = requestedPostType;
|
||||
}
|
||||
|
||||
const [rows, total] = await Promise.all([
|
||||
@@ -286,11 +305,15 @@ export class FeedService {
|
||||
rows.map((item) => item.toObject() as unknown as Record<string, unknown>),
|
||||
followingIds,
|
||||
);
|
||||
const normalizedPosts = this.normalizePreferredPostTypeForResponse(
|
||||
decoratedPosts,
|
||||
query.preferredPostType,
|
||||
);
|
||||
const decorationMs = this.markTiming(timing);
|
||||
const nextOffset = skip + rows.length;
|
||||
const nextCursor = nextOffset < total ? encodeOffsetCursor(nextOffset) : null;
|
||||
|
||||
const result = buildPaginatedResponse(decoratedPosts, {
|
||||
const result = buildPaginatedResponse(normalizedPosts, {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
@@ -316,7 +339,7 @@ export class FeedService {
|
||||
preferredPostType: query.preferredPostType ?? '',
|
||||
followingCount: followingIds.length,
|
||||
invisibleUserCount: invisibleUserIds.length,
|
||||
itemCount: decoratedPosts.length,
|
||||
itemCount: normalizedPosts.length,
|
||||
total,
|
||||
nextCursor,
|
||||
relationLookupMs,
|
||||
@@ -537,7 +560,7 @@ export class FeedService {
|
||||
currentUserId: string;
|
||||
followingIds: string[];
|
||||
post: Record<string, any>;
|
||||
preferredPostType?: PostType;
|
||||
preferredPostType?: FeedPostTypeFilter;
|
||||
radiusKm: number;
|
||||
}): number {
|
||||
const { currentUser, currentUserId, followingIds, post, preferredPostType, radiusKm } = input;
|
||||
@@ -680,6 +703,28 @@ export class FeedService {
|
||||
return '';
|
||||
}
|
||||
|
||||
private resolveRequestedPostType(preferredPostType?: FeedPostTypeFilter): PostType | null {
|
||||
if (!preferredPostType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return preferredPostType === 'reel' ? PostType.VIDEO : preferredPostType;
|
||||
}
|
||||
|
||||
private normalizePreferredPostTypeForResponse<T extends FeedPostItem>(
|
||||
items: T[],
|
||||
preferredPostType?: FeedPostTypeFilter,
|
||||
): T[] {
|
||||
if (preferredPostType !== 'reel') {
|
||||
return items;
|
||||
}
|
||||
|
||||
return items.map((item) => ({
|
||||
...item,
|
||||
postType: 'reel',
|
||||
}));
|
||||
}
|
||||
|
||||
private startTiming(scope: string): { scope: string; startedAt: number; lastAt: number } {
|
||||
const now = performance.now();
|
||||
return { scope, startedAt: now, lastAt: now };
|
||||
|
||||
Reference in New Issue
Block a user