feat(notifications): add notifications + notificationPreferences tables
This commit is contained in:
@@ -835,6 +835,37 @@ const applicationTables = {
|
|||||||
})
|
})
|
||||||
.index('by_thread', ['threadId'])
|
.index('by_thread', ['threadId'])
|
||||||
.index('by_owner', ['ownerId']),
|
.index('by_owner', ['ownerId']),
|
||||||
|
notifications: defineTable({
|
||||||
|
userId: v.id('users'),
|
||||||
|
kind: v.union(
|
||||||
|
v.literal('maintenance_thread'),
|
||||||
|
v.literal('agent_turn_finished'),
|
||||||
|
v.literal('agent_needs_input'),
|
||||||
|
v.literal('sync_failed'),
|
||||||
|
v.literal('connection_needs_reauth'),
|
||||||
|
),
|
||||||
|
title: v.string(),
|
||||||
|
body: v.string(),
|
||||||
|
link: v.optional(v.string()),
|
||||||
|
spoonId: v.optional(v.id('spoons')),
|
||||||
|
threadId: v.optional(v.id('threads')),
|
||||||
|
readAt: v.optional(v.number()),
|
||||||
|
emailedAt: v.optional(v.number()),
|
||||||
|
createdAt: v.number(),
|
||||||
|
})
|
||||||
|
.index('by_user', ['userId'])
|
||||||
|
.index('by_user_unread', ['userId', 'readAt'])
|
||||||
|
.index('by_user_created', ['userId', 'createdAt']),
|
||||||
|
notificationPreferences: defineTable({
|
||||||
|
userId: v.id('users'),
|
||||||
|
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()),
|
||||||
|
updatedAt: v.number(),
|
||||||
|
}).index('by_user', ['userId']),
|
||||||
ignoredUpstreamChanges: defineTable({
|
ignoredUpstreamChanges: defineTable({
|
||||||
spoonId: v.id('spoons'),
|
spoonId: v.id('spoons'),
|
||||||
ownerId: v.id('users'),
|
ownerId: v.id('users'),
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
import { convexTest } from 'convex-test';
|
||||||
|
import { describe, expect, test } from 'vitest';
|
||||||
|
|
||||||
|
import schema from '../../convex/schema';
|
||||||
|
|
||||||
|
const modules = import.meta.glob('../../convex/**/*.*s');
|
||||||
|
|
||||||
|
describe('notifications schema', () => {
|
||||||
|
test('inserts and reads back a notifications row', async () => {
|
||||||
|
const t = convexTest(schema, modules);
|
||||||
|
|
||||||
|
const { notificationId, userId } = await t.run(async (ctx) => {
|
||||||
|
const userId = await ctx.db.insert('users', {
|
||||||
|
email: 'a@b.c',
|
||||||
|
name: 'A',
|
||||||
|
});
|
||||||
|
const now = Date.now();
|
||||||
|
const notificationId = await ctx.db.insert('notifications', {
|
||||||
|
userId,
|
||||||
|
kind: 'agent_turn_finished',
|
||||||
|
title: 'Agent finished',
|
||||||
|
body: 'Your agent completed its turn.',
|
||||||
|
link: '/threads/abc',
|
||||||
|
createdAt: now,
|
||||||
|
});
|
||||||
|
return { notificationId, userId };
|
||||||
|
});
|
||||||
|
|
||||||
|
const notification = await t.run(
|
||||||
|
async (ctx) => await ctx.db.get(notificationId),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(notification).not.toBeNull();
|
||||||
|
expect(notification?.userId).toBe(userId);
|
||||||
|
expect(notification?.kind).toBe('agent_turn_finished');
|
||||||
|
expect(notification?.title).toBe('Agent finished');
|
||||||
|
expect(notification?.body).toBe('Your agent completed its turn.');
|
||||||
|
expect(notification?.link).toBe('/threads/abc');
|
||||||
|
expect(notification?.readAt).toBeUndefined();
|
||||||
|
expect(notification?.emailedAt).toBeUndefined();
|
||||||
|
expect(typeof notification?.createdAt).toBe('number');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('inserts and reads back a notificationPreferences row', async () => {
|
||||||
|
const t = convexTest(schema, modules);
|
||||||
|
|
||||||
|
const { preferenceId, userId } = await t.run(async (ctx) => {
|
||||||
|
const userId = await ctx.db.insert('users', {
|
||||||
|
email: 'prefs@b.c',
|
||||||
|
name: 'Prefs',
|
||||||
|
});
|
||||||
|
const preferenceId = await ctx.db.insert('notificationPreferences', {
|
||||||
|
userId,
|
||||||
|
emailEnabled: true,
|
||||||
|
emailSyncFailed: false,
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
return { preferenceId, userId };
|
||||||
|
});
|
||||||
|
|
||||||
|
const preference = await t.run(
|
||||||
|
async (ctx) => await ctx.db.get(preferenceId),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(preference).not.toBeNull();
|
||||||
|
expect(preference?.userId).toBe(userId);
|
||||||
|
expect(preference?.emailEnabled).toBe(true);
|
||||||
|
expect(preference?.emailSyncFailed).toBe(false);
|
||||||
|
expect(preference?.emailMaintenance).toBeUndefined();
|
||||||
|
expect(typeof preference?.updatedAt).toBe('number');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user