feat: followers-only recipe visibility
Adds a fourth visibility tier alongside private/unlisted/public: "followers" — visible to the author and anyone who follows them, excluded from public search/explore/profile listings and from the anonymous /r/[id] share route, included in the Following feed. The in-app recipe detail gate and the public share route both check follow status directly against the DB rather than the union type alone, since neither previously had any per-viewer access logic for a non-public/non-owner case. Requires the generated migration (0041) to run against a live DB — not applied in this sandbox (no Docker here); run `pnpm db:migrate`. v0.41.0
This commit is contained in:
@@ -7,6 +7,7 @@ import Link from "next/link";
|
||||
import { Clock, Users, ChefHat, Globe } from "lucide-react";
|
||||
import { auth } from "@/lib/auth/server";
|
||||
import { db, recipes, eq } from "@epicure/db";
|
||||
import { isFollowing } from "@/lib/social-follows";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
@@ -54,6 +55,16 @@ export default async function PublicRecipePage({ params }: Params) {
|
||||
|
||||
if (!recipe) notFound();
|
||||
|
||||
// "Followers only" recipes are excluded from the anonymous-accessible query
|
||||
// above only by being neither public nor unlisted — but ne("private") still
|
||||
// lets them through. This route has no session-based access control at all
|
||||
// otherwise, so gate them explicitly: the author, or a signed-in follower.
|
||||
if (recipe.visibility === "followers") {
|
||||
const viewerId = session?.user?.id;
|
||||
const allowed = !!viewerId && (viewerId === recipe.authorId || await isFollowing(viewerId, recipe.authorId));
|
||||
if (!allowed) notFound();
|
||||
}
|
||||
|
||||
const cover = recipe.photos.find((p) => p.isCover) ?? recipe.photos[0];
|
||||
const totalMins = (recipe.prepMins ?? 0) + (recipe.cookMins ?? 0);
|
||||
const activeTags = Object.entries(recipe.dietaryTags ?? {})
|
||||
|
||||
Reference in New Issue
Block a user