feat: add trending/leaderboard for collections

New collection_favorites table lets users star public collections.
/collections/explore mirrors the recipe explore page: trending (star
count in last 7 days) and recently-added public collections. Linked
from the "Discover" button on the collections page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-09 15:33:40 +02:00
parent cd444d4d23
commit b4b964aafb
11 changed files with 4618 additions and 2 deletions
@@ -0,0 +1,10 @@
CREATE TABLE "collection_favorites" (
"user_id" text NOT NULL,
"collection_id" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "collection_favorites" ADD CONSTRAINT "collection_favorites_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "collection_favorites" ADD CONSTRAINT "collection_favorites_collection_id_collections_id_fk" FOREIGN KEY ("collection_id") REFERENCES "public"."collections"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "collection_favorites_collection_idx" ON "collection_favorites" USING btree ("collection_id");--> statement-breakpoint
CREATE UNIQUE INDEX "collection_favorites_user_collection_idx" ON "collection_favorites" USING btree ("user_id","collection_id");
File diff suppressed because it is too large Load Diff
@@ -155,6 +155,13 @@
"when": 1783602591480,
"tag": "0021_mysterious_madame_masque",
"breakpoints": true
},
{
"idx": 22,
"version": "7",
"when": 1783603737477,
"tag": "0022_foamy_loki",
"breakpoints": true
}
]
}
+15
View File
@@ -6,6 +6,7 @@ import {
boolean,
pgEnum,
index,
uniqueIndex,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";
import { users } from "./users";
@@ -72,6 +73,15 @@ export const collectionRecipes = pgTable("collection_recipes", {
addedAt: timestamp("added_at").notNull().defaultNow(),
});
export const collectionFavorites = pgTable("collection_favorites", {
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
collectionId: text("collection_id").notNull().references(() => collections.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").notNull().defaultNow(),
}, (t) => [
index("collection_favorites_collection_idx").on(t.collectionId),
uniqueIndex("collection_favorites_user_collection_idx").on(t.userId, t.collectionId),
]);
export const cookingHistory = pgTable("cooking_history", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
@@ -117,6 +127,11 @@ export const collectionRecipesRelations = relations(collectionRecipes, ({ one })
recipe: one(recipes, { fields: [collectionRecipes.recipeId], references: [recipes.id] }),
}));
export const collectionFavoritesRelations = relations(collectionFavorites, ({ one }) => ({
collection: one(collections, { fields: [collectionFavorites.collectionId], references: [collections.id] }),
user: one(users, { fields: [collectionFavorites.userId], references: [users.id] }),
}));
export const collectionMemberRoleEnum = pgEnum("collection_member_role", ["viewer", "editor"]);
export const collectionMembers = pgTable("collection_members", {