feat: granular per-category push notification settings
New Settings → Notifications section with a toggle per category (follow, comment, reply, reaction, rating, mention, leftover-expiring, shared shopping list) — previously it was all-or-nothing (browser permission only). userNotificationPrefs (one row per user, defaults all-on so existing users see no behavior change until they opt out of something). Gated the push send in the three places that dispatch one: lib/notifications.ts (the 6 in-app notification types), the leftover-expiry cron, and shopping-list-notify — in-app notification-center entries and email are unaffected, this only gates the push itself, matching the literal ask. Verified locally: GET/PUT round-trips correctly, disabling a category and confirming the settings page renders all 8 toggles.
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
CREATE TABLE "user_notification_prefs" (
|
||||
"id" text PRIMARY KEY NOT NULL,
|
||||
"user_id" text NOT NULL,
|
||||
"follow" boolean DEFAULT true NOT NULL,
|
||||
"comment" boolean DEFAULT true NOT NULL,
|
||||
"reply" boolean DEFAULT true NOT NULL,
|
||||
"reaction" boolean DEFAULT true NOT NULL,
|
||||
"rating" boolean DEFAULT true NOT NULL,
|
||||
"mention" boolean DEFAULT true NOT NULL,
|
||||
"leftover_expiring" boolean DEFAULT true NOT NULL,
|
||||
"shopping_list" boolean DEFAULT true NOT NULL,
|
||||
"updated_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "user_notification_prefs_user_id_unique" UNIQUE("user_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "user_notification_prefs" ADD CONSTRAINT "user_notification_prefs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -239,6 +239,13 @@
|
||||
"when": 1783864805460,
|
||||
"tag": "0033_graceful_jetstream",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 34,
|
||||
"version": "7",
|
||||
"when": 1783875228288,
|
||||
"tag": "0034_dapper_nocturne",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -145,10 +145,28 @@ export const userNutritionGoals = pgTable("user_nutrition_goals", {
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const userNotificationPrefs = pgTable("user_notification_prefs", {
|
||||
id: text("id").primaryKey(),
|
||||
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }).unique(),
|
||||
follow: boolean("follow").notNull().default(true),
|
||||
comment: boolean("comment").notNull().default(true),
|
||||
reply: boolean("reply").notNull().default(true),
|
||||
reaction: boolean("reaction").notNull().default(true),
|
||||
rating: boolean("rating").notNull().default(true),
|
||||
mention: boolean("mention").notNull().default(true),
|
||||
leftoverExpiring: boolean("leftover_expiring").notNull().default(true),
|
||||
shoppingList: boolean("shopping_list").notNull().default(true),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const pushSubscriptionsRelations = relations(pushSubscriptions, ({ one }) => ({
|
||||
user: one(users, { fields: [pushSubscriptions.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const userNotificationPrefsRelations = relations(userNotificationPrefs, ({ one }) => ({
|
||||
user: one(users, { fields: [userNotificationPrefs.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const userNutritionGoalsRelations = relations(userNutritionGoals, ({ one }) => ({
|
||||
user: one(users, { fields: [userNutritionGoals.userId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user