150 lines
3.3 KiB
TypeScript
150 lines
3.3 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
|
|
type FeedMode = 'mixed' | 'discovery';
|
|
|
|
type FeedOptions = {
|
|
mode?: FeedMode;
|
|
take?: number;
|
|
cursorId?: number;
|
|
};
|
|
|
|
@Injectable()
|
|
export class PostsService {
|
|
constructor(private readonly prisma: PrismaService) {}
|
|
|
|
async getFeed(userId: number, options?: FeedOptions) {
|
|
const mode: FeedMode = options?.mode ?? 'mixed';
|
|
const take = Math.min(Math.max(options?.take ?? 20, 1), 50);
|
|
const cursorId = options?.cursorId;
|
|
|
|
const include = {
|
|
pet: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
type: true,
|
|
birthDate: true,
|
|
},
|
|
},
|
|
author: {
|
|
select: {
|
|
name: true,
|
|
},
|
|
},
|
|
} as const;
|
|
|
|
// Sichtbarkeit
|
|
const baseWhere =
|
|
mode === 'discovery'
|
|
? {
|
|
pet: { visibility: 'public' },
|
|
}
|
|
: {
|
|
pet: {
|
|
OR: [
|
|
{ visibility: 'public' },
|
|
{
|
|
visibility: 'followers',
|
|
owner: {
|
|
followers: {
|
|
some: {
|
|
followerId: userId,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
|
|
const commonQuery = {
|
|
orderBy: [
|
|
{ createdAt: 'desc' as const },
|
|
{ id: 'desc' as const },
|
|
],
|
|
include,
|
|
...(cursorId ? { cursor: { id: cursorId }, skip: 1 } : {}),
|
|
};
|
|
|
|
// -------- Discovery only --------
|
|
if (mode === 'discovery') {
|
|
const items = await this.prisma.post.findMany({
|
|
where: baseWhere,
|
|
take,
|
|
...commonQuery,
|
|
});
|
|
|
|
return {
|
|
items,
|
|
nextCursorId:
|
|
items.length === take ? items[items.length - 1].id : null,
|
|
meta: { mode, take },
|
|
};
|
|
}
|
|
|
|
// -------- Mixed Feed (85 / 15) --------
|
|
const discoveryCount = Math.max(1, Math.round(take * 0.15));
|
|
const normalCount = take - discoveryCount;
|
|
|
|
const [normalItems, discoveryItems] = await Promise.all([
|
|
this.prisma.post.findMany({
|
|
where: baseWhere,
|
|
take: normalCount,
|
|
...commonQuery,
|
|
}),
|
|
this.prisma.post.findMany({
|
|
where: { pet: { visibility: 'public' } },
|
|
take: discoveryCount,
|
|
...commonQuery,
|
|
}),
|
|
]);
|
|
|
|
// Ruhige Mischung + Duplikat-Schutz
|
|
const seen = new Set<number>();
|
|
const merged = [];
|
|
|
|
const n = [...normalItems];
|
|
const d = [...discoveryItems];
|
|
|
|
while (merged.length < take && (n.length || d.length)) {
|
|
for (let i = 0; i < 6 && n.length && merged.length < take; i++) {
|
|
const item = n.shift();
|
|
if (item && !seen.has(item.id)) {
|
|
seen.add(item.id);
|
|
merged.push(item);
|
|
}
|
|
}
|
|
|
|
if (d.length && merged.length < take) {
|
|
const item = d.shift();
|
|
if (item && !seen.has(item.id)) {
|
|
seen.add(item.id);
|
|
merged.push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
while (merged.length < take && n.length) {
|
|
const item = n.shift();
|
|
if (item && !seen.has(item.id)) {
|
|
seen.add(item.id);
|
|
merged.push(item);
|
|
}
|
|
}
|
|
|
|
return {
|
|
items: merged,
|
|
nextCursorId:
|
|
merged.length === take ? merged[merged.length - 1].id : null,
|
|
meta: {
|
|
mode,
|
|
take,
|
|
normalCount,
|
|
discoveryCount,
|
|
},
|
|
};
|
|
}
|
|
}
|