feat(notifications): settings notification preferences

This commit is contained in:
Gabriel Brown
2026-07-11 16:41:35 -04:00
parent c241941e41
commit 8d7486bc8c
6 changed files with 463 additions and 1 deletions
+67
View File
@@ -199,6 +199,73 @@ export const markRead = mutation({
},
});
export interface NotificationPreferences {
emailEnabled: boolean;
emailMaintenance: boolean;
emailAgentTurnFinished: boolean;
emailAgentNeedsInput: boolean;
emailSyncFailed: boolean;
emailConnectionReauth: boolean;
}
/**
* The authed caller's email notification preferences, resolved for display.
* Unset preferences default to enabled, so a caller with no stored row gets an
* all-true object. Scoped to the caller via `getRequiredUserId`; never accepts
* a userId argument.
*/
export const getPreferences = query({
args: {},
handler: async (ctx): Promise<NotificationPreferences> => {
const userId = await getRequiredUserId(ctx);
const prefs = await ctx.db
.query('notificationPreferences')
.withIndex('by_user', (q) => q.eq('userId', userId))
.unique();
return {
emailEnabled: prefs?.emailEnabled !== false,
emailMaintenance: prefs?.emailMaintenance !== false,
emailAgentTurnFinished: prefs?.emailAgentTurnFinished !== false,
emailAgentNeedsInput: prefs?.emailAgentNeedsInput !== false,
emailSyncFailed: prefs?.emailSyncFailed !== false,
emailConnectionReauth: prefs?.emailConnectionReauth !== false,
};
},
});
/**
* Upserts the authed caller's email notification preferences. Only the provided
* fields are written; the row is patched when it exists and inserted otherwise.
* Scoped to the caller via `getRequiredUserId`; never accepts a userId argument.
*/
export const updatePreferences = mutation({
args: {
emailEnabled: v.optional(v.boolean()),
emailMaintenance: v.optional(v.boolean()),
emailAgentTurnFinished: v.optional(v.boolean()),
emailAgentNeedsInput: v.optional(v.boolean()),
emailSyncFailed: v.optional(v.boolean()),
emailConnectionReauth: v.optional(v.boolean()),
},
handler: async (ctx, args): Promise<void> => {
const userId = await getRequiredUserId(ctx);
const existing = await ctx.db
.query('notificationPreferences')
.withIndex('by_user', (q) => q.eq('userId', userId))
.unique();
const now = Date.now();
if (existing) {
await ctx.db.patch(existing._id, { ...args, updatedAt: now });
} else {
await ctx.db.insert('notificationPreferences', {
userId,
...args,
updatedAt: now,
});
}
},
});
/**
* Marks all of the caller's unread notifications read. Iterates the caller's
* unread rows via the `by_user_unread` index; only ever touches the caller's