feat: add "cooked it" photo reviews

Users can rate a recipe with review text and an optional photo. Adds
ratings.photo_key column, a reviews list endpoint, and a review-purpose
presign path (reviewer isn't the recipe owner, so the upload
authorization differs from cover-photo uploads).

Also fixes CSP connect-src/img-src to allow the storage origin —
direct-to-S3/MinIO presigned uploads and stored images were silently
blocked by Content-Security-Policy in the browser.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-09 15:20:05 +02:00
parent a8406e9963
commit f2a0c20f07
12 changed files with 4507 additions and 11 deletions
@@ -0,0 +1,2 @@
ALTER TABLE "ratings" ADD COLUMN "photo_key" text;--> statement-breakpoint
CREATE INDEX "ratings_recipe_idx" ON "ratings" USING btree ("recipe_id");
File diff suppressed because it is too large Load Diff
@@ -148,6 +148,13 @@
"when": 1783109825851,
"tag": "0020_acoustic_exiles",
"breakpoints": true
},
{
"idx": 21,
"version": "7",
"when": 1783602591480,
"tag": "0021_mysterious_madame_masque",
"breakpoints": true
}
]
}
+2
View File
@@ -26,10 +26,12 @@ export const ratings = pgTable("ratings", {
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
score: integer("score").notNull(),
reviewText: text("review_text"),
photoKey: text("photo_key"),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
}, (t) => [
index("ratings_user_idx").on(t.userId),
index("ratings_recipe_idx").on(t.recipeId),
]);
export const favorites = pgTable("favorites", {