import { NextRequest, NextResponse } from "next/server"; import { db, recipes, users, favorites, ratings, eq, and, ne, gte, notInArray, inArray, desc } from "@epicure/db"; import { requireSession } from "@/lib/api-auth"; import { buildPreferenceMap, rankForYou } from "@/lib/for-you-ranking"; export async function GET(req: NextRequest) { const { session, response } = await requireSession(); if (response) return response; const userId = session!.user.id; const { searchParams } = new URL(req.url); const limit = Math.min(parseInt(searchParams.get("limit") ?? "20"), 50); // Recipes the user has favorited, or rated 4+, define their taste profile. const [favoritedRows, highRatedRows] = await Promise.all([ db.select({ recipeId: favorites.recipeId }).from(favorites).where(eq(favorites.userId, userId)), db.select({ recipeId: ratings.recipeId }).from(ratings).where(and(eq(ratings.userId, userId), gte(ratings.score, 4))), ]); const likedIds = [...new Set([...favoritedRows.map((r) => r.recipeId), ...highRatedRows.map((r) => r.recipeId)])]; const likedRecipes = likedIds.length > 0 ? await db.select({ tags: recipes.tags, dietaryTags: recipes.dietaryTags }).from(recipes).where(inArray(recipes.id, likedIds)) : []; const preferences = buildPreferenceMap(likedRecipes); const excludeIds = likedIds.length > 0 ? likedIds : ["__none__"]; const candidates = await db .select({ id: recipes.id, title: recipes.title, description: recipes.description, baseServings: recipes.baseServings, prepMins: recipes.prepMins, cookMins: recipes.cookMins, difficulty: recipes.difficulty, visibility: recipes.visibility, aiGenerated: recipes.aiGenerated, createdAt: recipes.createdAt, authorId: recipes.authorId, authorName: users.name, authorUsername: users.username, authorAvatarUrl: users.avatarUrl, tags: recipes.tags, dietaryTags: recipes.dietaryTags, }) .from(recipes) .innerJoin(users, eq(recipes.authorId, users.id)) .where(and( eq(recipes.visibility, "public"), ne(recipes.authorId, userId), notInArray(recipes.id, excludeIds) )) .orderBy(desc(recipes.createdAt)) .limit(200); // score a bounded recent window rather than the whole table const ranked = preferences.size > 0 ? rankForYou(candidates, preferences) : [...candidates].sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); const data = ranked.slice(0, limit).map(({ tags: _tags, dietaryTags: _dietaryTags, ...r }) => ({ ...r, createdAt: r.createdAt.toISOString(), })); return NextResponse.json({ data }); }