This repository has been archived on 2026-08-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
onlypets/backend/src/posts/posts.service.ts
T

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,
},
};
}
}