fix: broken recipe pages from bad cross-table SQL in followers-visibility check

visibleToViewer() referenced userFollows as a Drizzle column proxy
inside db.query.recipes.findFirst's where — the relational query
builder rewrites embedded column refs to the outer query's own table
alias regardless of which table they actually belong to, same gotcha
already documented and worked around in recipes/page.tsx's search
filter. That silently produced SQL referencing a column that doesn't
exist on recipes, breaking every recipe page load since 0.41.0. Fixed
by using a literal user_follows identifier instead, matching the
established workaround.

v0.44.1
This commit is contained in:
Arnaud
2026-07-17 18:09:59 +02:00
parent c31ab8771a
commit 7d290c5b1f
5 changed files with 25 additions and 5 deletions
+8 -1
View File
@@ -1,5 +1,5 @@
// Mirrors CHANGELOG.md at the repo root — update both together.
export const APP_VERSION = "0.44.0";
export const APP_VERSION = "0.44.1";
export type ChangelogEntry = {
version: string;
@@ -11,6 +11,13 @@ export type ChangelogEntry = {
};
export const CHANGELOG: ChangelogEntry[] = [
{
version: "0.44.1",
date: "2026-07-17 17:00",
fixed: [
"Every recipe page was broken (server error) since 0.41.0 — the new followers-visibility check referenced the user_follows table incorrectly inside a query the ORM rewrites in a way that silently produces invalid SQL for cross-table references. Fixed by using a literal table reference instead.",
],
},
{
version: "0.44.0",
date: "2026-07-17 16:15",
+10 -2
View File
@@ -1,4 +1,4 @@
import { recipes, userFollows, eq, or, and, inArray, sql } from "@epicure/db";
import { recipes, eq, or, and, inArray, sql } from "@epicure/db";
/**
* Drizzle condition: true when `viewerId` may view a given recipe row — the
@@ -6,6 +6,14 @@ import { recipes, userFollows, eq, or, and, inArray, sql } from "@epicure/db";
* recipes, only to viewers who follow the author. Meant to be `and()`ed with
* an `eq(recipes.id, id)` (or similar) in a single query, so the DB does the
* filtering rather than fetching content the viewer can't see.
*
* Uses a literal `user_follows` identifier rather than a Drizzle column
* proxy for that table — inside `db.query.recipes.findFirst`/`findMany`'s
* `where`, the relational query builder rewrites embedded column refs to
* the outer query's own table alias regardless of which table they actually
* belong to (same issue documented in recipes/page.tsx's search filter),
* which would otherwise silently produce broken SQL referencing a column
* that doesn't exist on `recipes`.
*/
export function visibleToViewer(viewerId: string) {
return or(
@@ -13,7 +21,7 @@ export function visibleToViewer(viewerId: string) {
inArray(recipes.visibility, ["public", "unlisted"]),
and(
eq(recipes.visibility, "followers"),
sql`exists (select 1 from ${userFollows} where ${userFollows.followerId} = ${viewerId} and ${userFollows.followingId} = ${recipes.authorId})`
sql`exists (select 1 from user_follows where user_follows.follower_id = ${viewerId} and user_follows.following_id = ${recipes.authorId})`
)
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@epicure/web",
"version": "0.44.0",
"version": "0.44.1",
"private": true,
"scripts": {
"dev": "next dev",