cognova 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/app.vue +1 -1
- package/app/composables/useNotificationBus.ts +58 -42
- package/app/pages/settings.vue +147 -12
- package/package.json +1 -1
- package/server/api/agents/[id].delete.ts +3 -0
- package/server/api/agents/[id].patch.ts +3 -0
- package/server/api/agents/index.post.ts +3 -0
- package/server/api/conversations/[id].delete.ts +3 -0
- package/server/api/documents/[id]/index.delete.ts +3 -0
- package/server/api/documents/[id]/index.put.ts +3 -0
- package/server/api/documents/[id]/restore.post.ts +3 -0
- package/server/api/hooks/events/index.post.ts +3 -0
- package/server/api/memory/[id].delete.ts +3 -0
- package/server/api/memory/store.post.ts +3 -0
- package/server/api/projects/[id]/index.delete.ts +3 -0
- package/server/api/projects/[id]/index.put.ts +3 -0
- package/server/api/projects/index.post.ts +3 -0
- package/server/api/secrets/[key].delete.ts +3 -0
- package/server/api/secrets/[key].put.ts +3 -0
- package/server/api/secrets/index.post.ts +3 -0
- package/server/api/settings/index.get.ts +34 -0
- package/server/api/settings/index.put.ts +44 -0
- package/server/api/tasks/[id]/index.delete.ts +3 -0
- package/server/api/tasks/[id]/index.put.ts +3 -0
- package/server/api/tasks/[id]/restore.post.ts +3 -0
- package/server/api/tasks/index.post.ts +3 -0
- package/server/db/schema.ts +16 -0
- package/server/drizzle/migrations/0010_condemned_skrulls.sql +10 -0
- package/server/drizzle/migrations/meta/0010_snapshot.json +1556 -0
- package/server/drizzle/migrations/meta/_journal.json +7 -0
- package/server/services/agent-executor.ts +25 -26
- package/server/utils/notify-resource.ts +68 -0
- package/shared/types/index.ts +44 -13
- package/shared/utils/notification-defaults.ts +13 -0
package/app/app.vue
CHANGED
|
@@ -1,16 +1,30 @@
|
|
|
1
|
-
import type { NotificationPayload } from '~~/shared/types'
|
|
1
|
+
import type { NotificationPayload, NotificationPreferences, NotificationAction } from '~~/shared/types'
|
|
2
|
+
import { defaultNotificationPreferences } from '~~/shared/utils/notification-defaults'
|
|
2
3
|
|
|
3
4
|
export type NotificationBusStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
4
5
|
|
|
5
6
|
// Shared state across all component instances
|
|
6
7
|
const status = ref<NotificationBusStatus>('disconnected')
|
|
7
8
|
const runningAgentIds = ref<Set<string>>(new Set())
|
|
9
|
+
const notificationPreferences = ref<NotificationPreferences>({ ...defaultNotificationPreferences })
|
|
8
10
|
const ws = ref<WebSocket | null>(null)
|
|
9
11
|
const reconnectAttempts = ref(0)
|
|
10
12
|
const maxReconnectAttempts = 5
|
|
11
13
|
const reconnectDelay = 2000
|
|
12
14
|
let pingInterval: ReturnType<typeof setInterval> | null = null
|
|
13
15
|
let isInitialized = false
|
|
16
|
+
let preferencesLoaded = false
|
|
17
|
+
|
|
18
|
+
const actionIcons: Record<NotificationAction, string> = {
|
|
19
|
+
create: 'i-lucide-plus-circle',
|
|
20
|
+
edit: 'i-lucide-pencil',
|
|
21
|
+
delete: 'i-lucide-trash-2',
|
|
22
|
+
restore: 'i-lucide-undo-2',
|
|
23
|
+
run: 'i-lucide-play',
|
|
24
|
+
cancel: 'i-lucide-x-circle',
|
|
25
|
+
complete: 'i-lucide-check-circle',
|
|
26
|
+
fail: 'i-lucide-alert-circle'
|
|
27
|
+
}
|
|
14
28
|
|
|
15
29
|
export function useNotificationBus() {
|
|
16
30
|
const toast = useToast()
|
|
@@ -21,43 +35,39 @@ export function useNotificationBus() {
|
|
|
21
35
|
return `${protocol}//${window.location.host}/notifications`
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
async function loadPreferences() {
|
|
39
|
+
if (preferencesLoaded) return
|
|
40
|
+
try {
|
|
41
|
+
const response = await $fetch<{ data: { notifications: NotificationPreferences } }>('/api/settings')
|
|
42
|
+
notificationPreferences.value = { ...defaultNotificationPreferences, ...response.data.notifications }
|
|
43
|
+
preferencesLoaded = true
|
|
44
|
+
} catch {
|
|
45
|
+
console.warn('[notification-bus] Failed to load preferences, using defaults')
|
|
30
46
|
}
|
|
47
|
+
}
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
})
|
|
40
|
-
} else if (payload.type === 'agent:completed') {
|
|
41
|
-
toast.add({
|
|
42
|
-
title: 'Agent Completed',
|
|
43
|
-
description: payload.agentName || 'Agent run completed successfully',
|
|
44
|
-
icon: 'i-lucide-check-circle',
|
|
45
|
-
color: 'success'
|
|
46
|
-
})
|
|
47
|
-
} else if (payload.type === 'agent:failed') {
|
|
48
|
-
toast.add({
|
|
49
|
-
title: 'Agent Failed',
|
|
50
|
-
description: payload.message || payload.agentName || 'Agent run failed',
|
|
51
|
-
icon: 'i-lucide-alert-circle',
|
|
52
|
-
color: 'error'
|
|
53
|
-
})
|
|
54
|
-
} else if (payload.type === 'toast' && payload.title) {
|
|
55
|
-
toast.add({
|
|
56
|
-
title: payload.title,
|
|
57
|
-
description: payload.message,
|
|
58
|
-
color: payload.color || 'info'
|
|
59
|
-
})
|
|
49
|
+
function handleNotification(payload: NotificationPayload) {
|
|
50
|
+
// Always track running agents regardless of preferences
|
|
51
|
+
if (payload.resource === 'agent' && payload.resourceId) {
|
|
52
|
+
if (payload.action === 'run')
|
|
53
|
+
runningAgentIds.value.add(payload.resourceId)
|
|
54
|
+
else if (payload.action === 'complete' || payload.action === 'fail' || payload.action === 'cancel')
|
|
55
|
+
runningAgentIds.value.delete(payload.resourceId)
|
|
60
56
|
}
|
|
57
|
+
|
|
58
|
+
// Check resource-level preference
|
|
59
|
+
const pref = notificationPreferences.value[payload.resource]
|
|
60
|
+
if (!pref?.enabled) return
|
|
61
|
+
|
|
62
|
+
// Check subtype preference (defaults to enabled if not explicitly set)
|
|
63
|
+
if (pref.subtypes && pref.subtypes[payload.action] === false) return
|
|
64
|
+
|
|
65
|
+
toast.add({
|
|
66
|
+
title: payload.title || 'Notification',
|
|
67
|
+
description: payload.message,
|
|
68
|
+
icon: actionIcons[payload.action] || 'i-lucide-bell',
|
|
69
|
+
color: payload.color || 'info'
|
|
70
|
+
})
|
|
61
71
|
}
|
|
62
72
|
|
|
63
73
|
function connect() {
|
|
@@ -75,6 +85,7 @@ export function useNotificationBus() {
|
|
|
75
85
|
status.value = 'connected'
|
|
76
86
|
reconnectAttempts.value = 0
|
|
77
87
|
console.log('[notification-bus] Connected')
|
|
88
|
+
loadPreferences()
|
|
78
89
|
startPingInterval()
|
|
79
90
|
}
|
|
80
91
|
|
|
@@ -83,13 +94,12 @@ export function useNotificationBus() {
|
|
|
83
94
|
const data = JSON.parse(event.data) as Record<string, unknown>
|
|
84
95
|
const msgType = data.type as string
|
|
85
96
|
|
|
86
|
-
// Skip
|
|
97
|
+
// Skip control messages
|
|
87
98
|
if (msgType === 'pong' || msgType === 'connected') return
|
|
88
99
|
|
|
89
|
-
//
|
|
90
|
-
if (msgType === '
|
|
100
|
+
// Handle resource change notifications
|
|
101
|
+
if (msgType === 'resource_change')
|
|
91
102
|
handleNotification(data as unknown as NotificationPayload)
|
|
92
|
-
}
|
|
93
103
|
} catch (e) {
|
|
94
104
|
console.error('[notification-bus] Failed to parse message:', e)
|
|
95
105
|
}
|
|
@@ -123,9 +133,8 @@ export function useNotificationBus() {
|
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
function sendPing() {
|
|
126
|
-
if (ws.value?.readyState === WebSocket.OPEN)
|
|
136
|
+
if (ws.value?.readyState === WebSocket.OPEN)
|
|
127
137
|
ws.value.send(JSON.stringify({ type: 'ping' }))
|
|
128
|
-
}
|
|
129
138
|
}
|
|
130
139
|
|
|
131
140
|
function startPingInterval() {
|
|
@@ -144,11 +153,18 @@ export function useNotificationBus() {
|
|
|
144
153
|
return runningAgentIds.value.has(agentId)
|
|
145
154
|
}
|
|
146
155
|
|
|
156
|
+
function updatePreferences(prefs: NotificationPreferences) {
|
|
157
|
+
notificationPreferences.value = prefs
|
|
158
|
+
}
|
|
159
|
+
|
|
147
160
|
return {
|
|
148
161
|
status: readonly(status),
|
|
149
162
|
runningAgentIds: readonly(runningAgentIds),
|
|
163
|
+
notificationPreferences: readonly(notificationPreferences),
|
|
150
164
|
isAgentRunning,
|
|
151
165
|
connect,
|
|
152
|
-
disconnect
|
|
166
|
+
disconnect,
|
|
167
|
+
loadPreferences,
|
|
168
|
+
updatePreferences
|
|
153
169
|
}
|
|
154
170
|
}
|
package/app/pages/settings.vue
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import type { NotificationPreferences, NotificationResource, NotificationAction } from '~~/shared/types'
|
|
3
|
+
import { defaultNotificationPreferences } from '~~/shared/utils/notification-defaults'
|
|
4
|
+
|
|
2
5
|
interface Secret {
|
|
3
6
|
id: string
|
|
4
7
|
key: string
|
|
@@ -14,6 +17,7 @@ definePageMeta({
|
|
|
14
17
|
|
|
15
18
|
const { user, updateProfile, changeEmail, changePassword } = useAuth()
|
|
16
19
|
const toast = useToast()
|
|
20
|
+
const { updatePreferences } = useNotificationBus()
|
|
17
21
|
|
|
18
22
|
// Profile form state
|
|
19
23
|
const profileState = reactive({
|
|
@@ -190,9 +194,73 @@ function formatDate(dateStr: string) {
|
|
|
190
194
|
})
|
|
191
195
|
}
|
|
192
196
|
|
|
193
|
-
//
|
|
197
|
+
// === Notification Preferences ===
|
|
198
|
+
const notifPrefs = ref<NotificationPreferences>({ ...defaultNotificationPreferences })
|
|
199
|
+
const notifLoading = ref(false)
|
|
200
|
+
const notifSaving = ref(false)
|
|
201
|
+
const expandedResources = ref<Set<string>>(new Set())
|
|
202
|
+
|
|
203
|
+
const resourceConfig: { key: NotificationResource, label: string, icon: string, subtypes: NotificationAction[] }[] = [
|
|
204
|
+
{ key: 'task', label: 'Tasks', icon: 'i-lucide-check-square', subtypes: ['create', 'edit', 'delete', 'restore'] },
|
|
205
|
+
{ key: 'project', label: 'Projects', icon: 'i-lucide-folder', subtypes: ['create', 'edit', 'delete'] },
|
|
206
|
+
{ key: 'agent', label: 'Agents', icon: 'i-lucide-bot', subtypes: ['create', 'edit', 'delete', 'run', 'complete', 'fail', 'cancel'] },
|
|
207
|
+
{ key: 'document', label: 'Documents', icon: 'i-lucide-file-text', subtypes: ['edit', 'delete', 'restore'] },
|
|
208
|
+
{ key: 'memory', label: 'Memories', icon: 'i-lucide-brain', subtypes: ['create', 'delete'] },
|
|
209
|
+
{ key: 'reminder', label: 'Reminders', icon: 'i-lucide-bell', subtypes: ['create'] },
|
|
210
|
+
{ key: 'secret', label: 'Secrets', icon: 'i-lucide-key-round', subtypes: ['create', 'edit', 'delete'] },
|
|
211
|
+
{ key: 'hook', label: 'Hooks', icon: 'i-lucide-webhook', subtypes: ['create'] },
|
|
212
|
+
{ key: 'conversation', label: 'Conversations', icon: 'i-lucide-message-square', subtypes: ['delete'] }
|
|
213
|
+
]
|
|
214
|
+
|
|
215
|
+
async function loadNotificationPrefs() {
|
|
216
|
+
notifLoading.value = true
|
|
217
|
+
try {
|
|
218
|
+
const { data } = await $fetch<{ data: { notifications: NotificationPreferences } }>('/api/settings')
|
|
219
|
+
notifPrefs.value = { ...defaultNotificationPreferences, ...data.notifications }
|
|
220
|
+
} catch {
|
|
221
|
+
notifPrefs.value = { ...defaultNotificationPreferences }
|
|
222
|
+
}
|
|
223
|
+
notifLoading.value = false
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function saveNotificationPrefs() {
|
|
227
|
+
notifSaving.value = true
|
|
228
|
+
try {
|
|
229
|
+
await $fetch('/api/settings', {
|
|
230
|
+
method: 'PUT',
|
|
231
|
+
body: { notifications: notifPrefs.value }
|
|
232
|
+
})
|
|
233
|
+
updatePreferences(notifPrefs.value)
|
|
234
|
+
toast.add({ title: 'Notification preferences saved', color: 'success' })
|
|
235
|
+
} catch {
|
|
236
|
+
toast.add({ title: 'Failed to save preferences', color: 'error' })
|
|
237
|
+
}
|
|
238
|
+
notifSaving.value = false
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function toggleResourceExpand(key: string) {
|
|
242
|
+
if (expandedResources.value.has(key))
|
|
243
|
+
expandedResources.value.delete(key)
|
|
244
|
+
else
|
|
245
|
+
expandedResources.value.add(key)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function setResourceEnabled(key: NotificationResource, enabled: boolean) {
|
|
249
|
+
notifPrefs.value[key] = { ...notifPrefs.value[key], enabled }
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function setSubtypeEnabled(key: NotificationResource, subtype: NotificationAction, enabled: boolean) {
|
|
253
|
+
const current = notifPrefs.value[key]
|
|
254
|
+
notifPrefs.value[key] = {
|
|
255
|
+
...current,
|
|
256
|
+
subtypes: { ...current?.subtypes, [subtype]: enabled }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Load secrets + notification prefs when component mounts
|
|
194
261
|
onMounted(() => {
|
|
195
262
|
fetchSecrets()
|
|
263
|
+
loadNotificationPrefs()
|
|
196
264
|
})
|
|
197
265
|
|
|
198
266
|
// Form handlers
|
|
@@ -498,17 +566,84 @@ async function handlePasswordSubmit() {
|
|
|
498
566
|
|
|
499
567
|
<!-- App Tab -->
|
|
500
568
|
<template #app>
|
|
501
|
-
<div class="
|
|
502
|
-
<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
569
|
+
<div class="max-w-2xl mx-auto py-6">
|
|
570
|
+
<div class="mb-6">
|
|
571
|
+
<h3 class="text-lg font-semibold mb-1">
|
|
572
|
+
Notification Preferences
|
|
573
|
+
</h3>
|
|
574
|
+
<p class="text-sm text-dimmed">
|
|
575
|
+
Choose which resource changes show toast notifications.
|
|
576
|
+
</p>
|
|
577
|
+
</div>
|
|
578
|
+
|
|
579
|
+
<div
|
|
580
|
+
v-if="notifLoading"
|
|
581
|
+
class="space-y-3"
|
|
582
|
+
>
|
|
583
|
+
<USkeleton
|
|
584
|
+
v-for="i in 5"
|
|
585
|
+
:key="i"
|
|
586
|
+
class="h-12 w-full"
|
|
587
|
+
/>
|
|
588
|
+
</div>
|
|
589
|
+
|
|
590
|
+
<div
|
|
591
|
+
v-else
|
|
592
|
+
class="space-y-2"
|
|
593
|
+
>
|
|
594
|
+
<div
|
|
595
|
+
v-for="rc in resourceConfig"
|
|
596
|
+
:key="rc.key"
|
|
597
|
+
class="border border-default rounded-lg"
|
|
598
|
+
>
|
|
599
|
+
<div class="flex items-center justify-between px-4 py-3">
|
|
600
|
+
<div class="flex items-center gap-3">
|
|
601
|
+
<UButton
|
|
602
|
+
variant="ghost"
|
|
603
|
+
size="xs"
|
|
604
|
+
:icon="expandedResources.has(rc.key) ? 'i-lucide-chevron-down' : 'i-lucide-chevron-right'"
|
|
605
|
+
@click="toggleResourceExpand(rc.key)"
|
|
606
|
+
/>
|
|
607
|
+
<UIcon
|
|
608
|
+
:name="rc.icon"
|
|
609
|
+
class="size-5 text-dimmed"
|
|
610
|
+
/>
|
|
611
|
+
<span class="font-medium">{{ rc.label }}</span>
|
|
612
|
+
</div>
|
|
613
|
+
<USwitch
|
|
614
|
+
:model-value="notifPrefs[rc.key]?.enabled ?? false"
|
|
615
|
+
@update:model-value="(v: boolean) => setResourceEnabled(rc.key, v)"
|
|
616
|
+
/>
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
<div
|
|
620
|
+
v-if="expandedResources.has(rc.key) && notifPrefs[rc.key]?.enabled"
|
|
621
|
+
class="border-t border-default px-4 py-3 space-y-2 bg-elevated/50"
|
|
622
|
+
>
|
|
623
|
+
<div
|
|
624
|
+
v-for="subtype in rc.subtypes"
|
|
625
|
+
:key="subtype"
|
|
626
|
+
class="flex items-center justify-between pl-11"
|
|
627
|
+
>
|
|
628
|
+
<span class="text-sm text-dimmed capitalize">{{ subtype }}</span>
|
|
629
|
+
<USwitch
|
|
630
|
+
:model-value="notifPrefs[rc.key]?.subtypes?.[subtype] !== false"
|
|
631
|
+
size="sm"
|
|
632
|
+
@update:model-value="(v: boolean) => setSubtypeEnabled(rc.key, subtype, v)"
|
|
633
|
+
/>
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
</div>
|
|
637
|
+
</div>
|
|
638
|
+
|
|
639
|
+
<div class="mt-6">
|
|
640
|
+
<UButton
|
|
641
|
+
:loading="notifSaving"
|
|
642
|
+
@click="saveNotificationPrefs"
|
|
643
|
+
>
|
|
644
|
+
Save Preferences
|
|
645
|
+
</UButton>
|
|
646
|
+
</div>
|
|
512
647
|
</div>
|
|
513
648
|
</template>
|
|
514
649
|
</UTabs>
|
package/package.json
CHANGED
|
@@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm'
|
|
|
2
2
|
import { getDb, schema } from '~~/server/db'
|
|
3
3
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
4
|
import { unscheduleAgent } from '~~/server/services/cron-scheduler'
|
|
5
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
5
6
|
|
|
6
7
|
export default defineEventHandler(async (event) => {
|
|
7
8
|
requireDb(event)
|
|
@@ -25,5 +26,7 @@ export default defineEventHandler(async (event) => {
|
|
|
25
26
|
throw createError({ statusCode: 404, message: 'Agent not found' })
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
notifyResourceChange({ resource: 'agent', action: 'delete', resourceId: id, resourceName: deleted.name })
|
|
30
|
+
|
|
28
31
|
return { data: deleted }
|
|
29
32
|
})
|
|
@@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm'
|
|
|
2
2
|
import { getDb, schema } from '~~/server/db'
|
|
3
3
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
4
|
import { scheduleAgent, unscheduleAgent } from '~~/server/services/cron-scheduler'
|
|
5
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
5
6
|
|
|
6
7
|
interface UpdateAgentBody {
|
|
7
8
|
name?: string
|
|
@@ -51,5 +52,7 @@ export default defineEventHandler(async (event) => {
|
|
|
51
52
|
}
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
notifyResourceChange({ resource: 'agent', action: 'edit', resourceId: id, resourceName: agent!.name })
|
|
56
|
+
|
|
54
57
|
return { data: agent }
|
|
55
58
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDb, schema } from '~~/server/db'
|
|
2
2
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
3
|
import { scheduleAgent } from '~~/server/services/cron-scheduler'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
|
|
5
6
|
interface CreateAgentBody {
|
|
6
7
|
name: string
|
|
@@ -44,5 +45,7 @@ export default defineEventHandler(async (event) => {
|
|
|
44
45
|
scheduleAgent(agent!)
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
notifyResourceChange({ resource: 'agent', action: 'create', resourceId: agent!.id, resourceName: agent!.name })
|
|
49
|
+
|
|
47
50
|
return { data: agent }
|
|
48
51
|
})
|
|
@@ -2,6 +2,7 @@ import { eq } from 'drizzle-orm'
|
|
|
2
2
|
import { getDb } from '~~/server/db'
|
|
3
3
|
import * as schema from '~~/server/db/schema'
|
|
4
4
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
5
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
5
6
|
|
|
6
7
|
export default defineEventHandler(async (event) => {
|
|
7
8
|
requireDb(event)
|
|
@@ -12,5 +13,7 @@ export default defineEventHandler(async (event) => {
|
|
|
12
13
|
await db.delete(schema.conversations)
|
|
13
14
|
.where(eq(schema.conversations.id, id))
|
|
14
15
|
|
|
16
|
+
notifyResourceChange({ resource: 'conversation', action: 'delete', resourceId: id })
|
|
17
|
+
|
|
15
18
|
return { data: { success: true } }
|
|
16
19
|
})
|
|
@@ -4,6 +4,7 @@ import { getDb } from '~~/server/db'
|
|
|
4
4
|
import * as schema from '~~/server/db/schema'
|
|
5
5
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
6
6
|
import { validatePath } from '~~/server/utils/path-validator'
|
|
7
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
7
8
|
|
|
8
9
|
export default defineEventHandler(async (event) => {
|
|
9
10
|
requireDb(event)
|
|
@@ -43,5 +44,7 @@ export default defineEventHandler(async (event) => {
|
|
|
43
44
|
modifiedBy: userId
|
|
44
45
|
}).where(eq(schema.documents.id, id))
|
|
45
46
|
|
|
47
|
+
notifyResourceChange({ resource: 'document', action: 'delete', resourceId: id, resourceName: document.title })
|
|
48
|
+
|
|
46
49
|
return { data: { id, deleted: true } }
|
|
47
50
|
})
|
|
@@ -6,6 +6,7 @@ import * as schema from '~~/server/db/schema'
|
|
|
6
6
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
7
7
|
import { validatePath } from '~~/server/utils/path-validator'
|
|
8
8
|
import { parseFrontmatter, stringifyFrontmatter, computeContentHash, extractTitle } from '~~/server/utils/frontmatter'
|
|
9
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
9
10
|
|
|
10
11
|
export default defineEventHandler(async (event) => {
|
|
11
12
|
requireDb(event)
|
|
@@ -85,6 +86,8 @@ export default defineEventHandler(async (event) => {
|
|
|
85
86
|
with: { project: true }
|
|
86
87
|
})
|
|
87
88
|
|
|
89
|
+
notifyResourceChange({ resource: 'document', action: 'edit', resourceId: id, resourceName: updated?.title })
|
|
90
|
+
|
|
88
91
|
return {
|
|
89
92
|
data: {
|
|
90
93
|
document: updated,
|
|
@@ -6,6 +6,7 @@ import * as schema from '~~/server/db/schema'
|
|
|
6
6
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
7
7
|
import { validatePath } from '~~/server/utils/path-validator'
|
|
8
8
|
import { stringifyFrontmatter } from '~~/server/utils/frontmatter'
|
|
9
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
9
10
|
|
|
10
11
|
export default defineEventHandler(async (event) => {
|
|
11
12
|
requireDb(event)
|
|
@@ -61,5 +62,7 @@ export default defineEventHandler(async (event) => {
|
|
|
61
62
|
with: { project: true }
|
|
62
63
|
})
|
|
63
64
|
|
|
65
|
+
notifyResourceChange({ resource: 'document', action: 'restore', resourceId: id, resourceName: restored?.title })
|
|
66
|
+
|
|
64
67
|
return { data: restored }
|
|
65
68
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDb, schema } from '~~/server/db'
|
|
2
2
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
3
4
|
import type { CreateHookEventInput } from '~~/shared/types'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
@@ -32,5 +33,7 @@ export default defineEventHandler(async (event) => {
|
|
|
32
33
|
if (!hookEvent)
|
|
33
34
|
throw createError({ statusCode: 500, message: 'Failed to create hook event' })
|
|
34
35
|
|
|
36
|
+
notifyResourceChange({ resource: 'hook', action: 'create', resourceId: hookEvent!.id, resourceName: body.eventType })
|
|
37
|
+
|
|
35
38
|
return { data: hookEvent }
|
|
36
39
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm'
|
|
2
2
|
import { getDb, schema } from '~~/server/db'
|
|
3
3
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
6
7
|
requireDb(event)
|
|
@@ -22,5 +23,7 @@ export default defineEventHandler(async (event) => {
|
|
|
22
23
|
|
|
23
24
|
await db.delete(schema.memoryChunks).where(eq(schema.memoryChunks.id, id))
|
|
24
25
|
|
|
26
|
+
notifyResourceChange({ resource: 'memory', action: 'delete', resourceId: id })
|
|
27
|
+
|
|
25
28
|
return { data: { success: true } }
|
|
26
29
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDb, schema } from '~~/server/db'
|
|
2
2
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
3
4
|
import type { CreateMemoryInput, MemoryChunk } from '~~/shared/types'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
@@ -27,5 +28,7 @@ export default defineEventHandler(async (event) => {
|
|
|
27
28
|
|
|
28
29
|
console.log(`[memory] Stored memory: ${body.chunkType} - ${body.content.slice(0, 50)}...`)
|
|
29
30
|
|
|
31
|
+
notifyResourceChange({ resource: 'memory', action: 'create', resourceId: inserted.id, resourceName: body.chunkType })
|
|
32
|
+
|
|
30
33
|
return { data: inserted as MemoryChunk }
|
|
31
34
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm'
|
|
2
2
|
import { getDb, schema } from '~~/server/db'
|
|
3
3
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
6
7
|
requireDb(event)
|
|
@@ -36,5 +37,7 @@ export default defineEventHandler(async (event) => {
|
|
|
36
37
|
.where(eq(schema.projects.id, id))
|
|
37
38
|
.returning()
|
|
38
39
|
|
|
40
|
+
notifyResourceChange({ resource: 'project', action: 'delete', resourceId: id, resourceName: project.name })
|
|
41
|
+
|
|
39
42
|
return { data: project }
|
|
40
43
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { eq } from 'drizzle-orm'
|
|
2
2
|
import { getDb, schema } from '~~/server/db'
|
|
3
3
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
import type { UpdateProjectInput } from '~~/shared/types'
|
|
5
6
|
|
|
6
7
|
export default defineEventHandler(async (event) => {
|
|
@@ -46,5 +47,7 @@ export default defineEventHandler(async (event) => {
|
|
|
46
47
|
.where(eq(schema.projects.id, id))
|
|
47
48
|
.returning()
|
|
48
49
|
|
|
50
|
+
notifyResourceChange({ resource: 'project', action: 'edit', resourceId: id, resourceName: project.name })
|
|
51
|
+
|
|
49
52
|
return { data: project }
|
|
50
53
|
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDb, schema } from '~~/server/db'
|
|
2
2
|
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
3
4
|
import type { CreateProjectInput } from '~~/shared/types'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
@@ -30,5 +31,7 @@ export default defineEventHandler(async (event) => {
|
|
|
30
31
|
})
|
|
31
32
|
.returning()
|
|
32
33
|
|
|
34
|
+
notifyResourceChange({ resource: 'project', action: 'create', resourceId: project!.id, resourceName: project!.name })
|
|
35
|
+
|
|
33
36
|
return { data: project }
|
|
34
37
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDb } from '~~/server/db'
|
|
2
2
|
import { secrets } from '~~/server/db/schema'
|
|
3
3
|
import { eq } from 'drizzle-orm'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
|
|
5
6
|
export default defineEventHandler(async (event) => {
|
|
6
7
|
const key = getRouterParam(event, 'key')
|
|
@@ -27,5 +28,7 @@ export default defineEventHandler(async (event) => {
|
|
|
27
28
|
|
|
28
29
|
await db.delete(secrets).where(eq(secrets.key, key))
|
|
29
30
|
|
|
31
|
+
notifyResourceChange({ resource: 'secret', action: 'delete', resourceName: key })
|
|
32
|
+
|
|
30
33
|
return { data: { deleted: true } }
|
|
31
34
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDb } from '~~/server/db'
|
|
2
2
|
import { secrets } from '~~/server/db/schema'
|
|
3
3
|
import { encryptSecret } from '~~/server/utils/crypto'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
import { eq } from 'drizzle-orm'
|
|
5
6
|
|
|
6
7
|
interface UpdateSecretInput {
|
|
@@ -48,5 +49,7 @@ export default defineEventHandler(async (event) => {
|
|
|
48
49
|
updatedAt: secrets.updatedAt
|
|
49
50
|
})
|
|
50
51
|
|
|
52
|
+
notifyResourceChange({ resource: 'secret', action: 'edit', resourceId: result!.id, resourceName: result!.key })
|
|
53
|
+
|
|
51
54
|
return { data: result }
|
|
52
55
|
})
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getDb } from '~~/server/db'
|
|
2
2
|
import { secrets } from '~~/server/db/schema'
|
|
3
3
|
import { encryptSecret } from '~~/server/utils/crypto'
|
|
4
|
+
import { notifyResourceChange } from '~~/server/utils/notify-resource'
|
|
4
5
|
|
|
5
6
|
interface CreateSecretInput {
|
|
6
7
|
key: string
|
|
@@ -54,5 +55,7 @@ export default defineEventHandler(async (event) => {
|
|
|
54
55
|
createdAt: secrets.createdAt
|
|
55
56
|
})
|
|
56
57
|
|
|
58
|
+
notifyResourceChange({ resource: 'secret', action: 'create', resourceId: result!.id, resourceName: result!.key })
|
|
59
|
+
|
|
57
60
|
return { data: result }
|
|
58
61
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import { defaultNotificationPreferences } from '~~/shared/utils/notification-defaults'
|
|
5
|
+
import type { UserSettings } from '~~/shared/types'
|
|
6
|
+
|
|
7
|
+
export default defineEventHandler(async (event) => {
|
|
8
|
+
requireDb(event)
|
|
9
|
+
|
|
10
|
+
const userId = event.context.user?.id
|
|
11
|
+
if (!userId)
|
|
12
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
13
|
+
|
|
14
|
+
const db = getDb()
|
|
15
|
+
|
|
16
|
+
const existing = await db.query.userSettings.findFirst({
|
|
17
|
+
where: eq(schema.userSettings.userId, userId)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (!existing)
|
|
21
|
+
return { data: { notifications: defaultNotificationPreferences } as UserSettings }
|
|
22
|
+
|
|
23
|
+
const settings = JSON.parse(existing.settings) as Partial<UserSettings>
|
|
24
|
+
|
|
25
|
+
// Merge with defaults so new resource types are always present
|
|
26
|
+
return {
|
|
27
|
+
data: {
|
|
28
|
+
notifications: {
|
|
29
|
+
...defaultNotificationPreferences,
|
|
30
|
+
...settings.notifications
|
|
31
|
+
}
|
|
32
|
+
} as UserSettings
|
|
33
|
+
}
|
|
34
|
+
})
|