From 7d290c5b1f6e149e848ce84c48c4659dccbfb4f6 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Fri, 17 Jul 2026 18:09:59 +0200 Subject: [PATCH] fix: broken recipe pages from bad cross-table SQL in followers-visibility check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CHANGELOG.md | 5 +++++ apps/web/lib/changelog.ts | 9 ++++++++- apps/web/lib/visibility.ts | 12 ++++++++++-- apps/web/package.json | 2 +- package.json | 2 +- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c53bda..2ff67a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ All notable changes to Epicure are documented here. This file is mirrored in-app at `/changelog` (and in the admin dashboard) via `apps/web/lib/changelog.ts` — update both together. +## 0.44.1 — 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. + ## 0.44.0 — 2026-07-17 16:15 ### Added diff --git a/apps/web/lib/changelog.ts b/apps/web/lib/changelog.ts index 0a3bd85..5d8f133 100644 --- a/apps/web/lib/changelog.ts +++ b/apps/web/lib/changelog.ts @@ -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", diff --git a/apps/web/lib/visibility.ts b/apps/web/lib/visibility.ts index b3461b8..042bcec 100644 --- a/apps/web/lib/visibility.ts +++ b/apps/web/lib/visibility.ts @@ -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})` ) ); } diff --git a/apps/web/package.json b/apps/web/package.json index f9e4edd..cc64315 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -1,6 +1,6 @@ { "name": "@epicure/web", - "version": "0.44.0", + "version": "0.44.1", "private": true, "scripts": { "dev": "next dev", diff --git a/package.json b/package.json index 67f57cf..524880d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "epicure", - "version": "0.44.0", + "version": "0.44.1", "private": true, "scripts": { "dev": "pnpm --filter web dev",