File tree Expand file tree Collapse file tree
src/app/[locale]/(authenticated)/settings Expand file tree Collapse file tree Original file line number Diff line number Diff line change 99 */
1010
1111import type * as auth from "../auth.js" ;
12+ import type * as crons from "../crons.js" ;
13+ import type * as email from "../email.js" ;
1214import type * as http from "../http.js" ;
15+ import type * as migrations from "../migrations.js" ;
16+ import type * as model_activity from "../model/activity.js" ;
17+ import type * as model_tasks from "../model/tasks.js" ;
18+ import type * as notifications from "../notifications.js" ;
1319import type * as tasks from "../tasks.js" ;
1420import type * as users from "../users.js" ;
1521
@@ -21,7 +27,13 @@ import type {
2127
2228declare const fullApi : ApiFromModules < {
2329 auth : typeof auth ;
30+ crons : typeof crons ;
31+ email : typeof email ;
2432 http : typeof http ;
33+ migrations : typeof migrations ;
34+ "model/activity" : typeof model_activity ;
35+ "model/tasks" : typeof model_tasks ;
36+ notifications : typeof notifications ;
2537 tasks : typeof tasks ;
2638 users : typeof users ;
2739} > ;
@@ -54,4 +66,5 @@ export declare const internal: FilterApi<
5466
5567export declare const components : {
5668 workOSAuthKit : import ( "@convex-dev/workos-authkit/_generated/component.js" ) . ComponentApi < "workOSAuthKit" > ;
69+ resend : import ( "@convex-dev/resend/_generated/component.js" ) . ComponentApi < "resend" > ;
5770} ;
Original file line number Diff line number Diff line change 1+ import { internalMutation } from "./_generated/server" ;
2+
3+ /**
4+ * One-off migration: convert user preferences from old shape
5+ * { notifications: boolean } to new shape { notifyOnShare, notifyOnComment, notifyOnDueDate }.
6+ *
7+ * Run with: bunx convex run migrations:migrateUserPreferences
8+ */
9+ export const migrateUserPreferences = internalMutation ( {
10+ args : { } ,
11+ handler : async ( ctx ) => {
12+ const users = await ctx . db . query ( "users" ) . collect ( ) ;
13+ let migrated = 0 ;
14+
15+ for ( const user of users ) {
16+ if (
17+ user . preferences &&
18+ "notifications" in user . preferences &&
19+ ! ( "notifyOnShare" in user . preferences )
20+ ) {
21+ const enabled = ( user . preferences as { notifications : boolean } )
22+ . notifications ;
23+ await ctx . db . patch ( user . _id , {
24+ preferences : {
25+ notifyOnShare : enabled ,
26+ notifyOnComment : enabled ,
27+ notifyOnDueDate : enabled ,
28+ } ,
29+ } ) ;
30+ migrated ++ ;
31+ }
32+ }
33+
34+ return { migrated, total : users . length } ;
35+ } ,
36+ } ) ;
Original file line number Diff line number Diff line change @@ -8,14 +8,24 @@ const DEFAULT_PREFS = {
88 notifyOnDueDate : true ,
99} ;
1010
11+ type NewPrefs = typeof DEFAULT_PREFS ;
12+
1113function getUserPrefs ( user : {
12- preferences ?: {
13- notifyOnShare : boolean ;
14- notifyOnComment : boolean ;
15- notifyOnDueDate : boolean ;
16- } | null ;
17- } ) {
18- return user . preferences ?? DEFAULT_PREFS ;
14+ preferences ?: NewPrefs | { notifications : boolean } | null ;
15+ } ) : NewPrefs {
16+ const p = user . preferences ;
17+ if ( ! p ) {
18+ return DEFAULT_PREFS ;
19+ }
20+ // Old shape: map single boolean to all three
21+ if ( "notifications" in p ) {
22+ return {
23+ notifyOnShare : p . notifications ,
24+ notifyOnComment : p . notifications ,
25+ notifyOnDueDate : p . notifications ,
26+ } ;
27+ }
28+ return p ;
1929}
2030
2131/**
Original file line number Diff line number Diff line change @@ -42,12 +42,18 @@ export default defineSchema({
4242 // Optional avatar image (Convex file storage)
4343 avatarId : v . optional ( v . id ( "_storage" ) ) ,
4444 // User notification preferences (per event type)
45+ // Accepts both old shape { notifications } and new shape for migration
4546 preferences : v . optional (
46- v . object ( {
47- notifyOnShare : v . boolean ( ) ,
48- notifyOnComment : v . boolean ( ) ,
49- notifyOnDueDate : v . boolean ( ) ,
50- } )
47+ v . union (
48+ v . object ( {
49+ notifyOnShare : v . boolean ( ) ,
50+ notifyOnComment : v . boolean ( ) ,
51+ notifyOnDueDate : v . boolean ( ) ,
52+ } ) ,
53+ v . object ( {
54+ notifications : v . boolean ( ) ,
55+ } )
56+ )
5157 ) ,
5258 } )
5359 . index ( "by_auth_id" , [ "authId" ] )
Original file line number Diff line number Diff line change @@ -36,11 +36,14 @@ export function SettingsClient() {
3636 api . users . updateNotificationPreferences
3737 ) ;
3838
39- const prefs = user ?. preferences ?? {
39+ const defaults = {
4040 notifyOnShare : true ,
4141 notifyOnComment : true ,
4242 notifyOnDueDate : true ,
4343 } ;
44+ const rawPrefs = user ?. preferences ;
45+ // Handle both old { notifications } and new { notifyOn* } shapes
46+ const prefs = rawPrefs && "notifyOnShare" in rawPrefs ? rawPrefs : defaults ;
4447
4548 const handleAvatarUpload = useCallback (
4649 async ( storageId : Id < "_storage" > ) => {
You can’t perform that action at this time.
0 commit comments