cognova 0.1.0
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/.env.example +58 -0
- package/Claude/CLAUDE.md +92 -0
- package/Claude/hooks/lib/__init__.py +1 -0
- package/Claude/hooks/lib/hook_client.py +207 -0
- package/Claude/hooks/log-event.py +97 -0
- package/Claude/hooks/pre-compact.py +46 -0
- package/Claude/hooks/session-end.py +26 -0
- package/Claude/hooks/session-start.py +35 -0
- package/Claude/hooks/stop-extract.py +40 -0
- package/Claude/rules/frontmatter.md +54 -0
- package/Claude/rules/markdown.md +43 -0
- package/Claude/rules/note-organization.md +33 -0
- package/Claude/settings.json +54 -0
- package/Claude/skills/README.md +136 -0
- package/Claude/skills/_lib/__init__.py +1 -0
- package/Claude/skills/_lib/api.py +164 -0
- package/Claude/skills/_lib/output.py +95 -0
- package/Claude/skills/environment/SKILL.md +73 -0
- package/Claude/skills/environment/environment.py +239 -0
- package/Claude/skills/memory/SKILL.md +153 -0
- package/Claude/skills/memory/memory.py +270 -0
- package/Claude/skills/project/SKILL.md +105 -0
- package/Claude/skills/project/project.py +203 -0
- package/Claude/skills/skill-creator/SKILL.md +261 -0
- package/Claude/skills/task/SKILL.md +135 -0
- package/Claude/skills/task/task.py +310 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +39 -0
- package/app/assets/css/main.css +10 -0
- package/app/components/AppLogo.vue +40 -0
- package/app/components/AssistantPanel.client.vue +518 -0
- package/app/components/ConfirmModal.vue +84 -0
- package/app/components/TemplateMenu.vue +49 -0
- package/app/components/agents/AgentActivityChart.client.vue +105 -0
- package/app/components/agents/AgentActivityChart.server.vue +25 -0
- package/app/components/agents/AgentForm.vue +304 -0
- package/app/components/agents/AgentRunModal.vue +154 -0
- package/app/components/agents/AgentStatsCards.vue +98 -0
- package/app/components/chat/ChatInput.vue +85 -0
- package/app/components/chat/ConversationList.vue +78 -0
- package/app/components/chat/MessageBubble.vue +81 -0
- package/app/components/chat/StreamingMessage.vue +36 -0
- package/app/components/chat/ToolCallBlock.vue +77 -0
- package/app/components/editor/CodeEditor.client.vue +212 -0
- package/app/components/editor/CodeEditorFallback.vue +12 -0
- package/app/components/editor/DocumentEditor.vue +326 -0
- package/app/components/editor/DocumentMetadata.vue +140 -0
- package/app/components/editor/MarkdownEditor.vue +146 -0
- package/app/components/files/FileTree.vue +436 -0
- package/app/components/hooks/HookActivityChart.client.vue +117 -0
- package/app/components/hooks/HookActivityChart.server.vue +25 -0
- package/app/components/hooks/HookStatsCards.vue +63 -0
- package/app/components/hooks/RecentEventsTable.vue +123 -0
- package/app/components/hooks/ToolBreakdownTable.vue +72 -0
- package/app/components/search/DashboardSearch.vue +122 -0
- package/app/components/tasks/ProjectSelect.vue +35 -0
- package/app/components/tasks/TaskCard.vue +182 -0
- package/app/components/tasks/TaskDetail.vue +160 -0
- package/app/components/tasks/TaskForm.vue +280 -0
- package/app/components/tasks/TaskList.vue +69 -0
- package/app/components/view/ViewToc.vue +85 -0
- package/app/composables/useAgents.ts +153 -0
- package/app/composables/useAuth.ts +73 -0
- package/app/composables/useChat.ts +298 -0
- package/app/composables/useDocument.ts +141 -0
- package/app/composables/useEditor.ts +100 -0
- package/app/composables/useFileTree.ts +220 -0
- package/app/composables/useHookEvents.ts +68 -0
- package/app/composables/useMemories.ts +83 -0
- package/app/composables/useNotificationBus.ts +154 -0
- package/app/composables/usePreferences.ts +131 -0
- package/app/composables/useProjects.ts +97 -0
- package/app/composables/useSearch.ts +52 -0
- package/app/composables/useTasks.ts +201 -0
- package/app/composables/useTerminal.ts +135 -0
- package/app/layouts/auth.vue +20 -0
- package/app/layouts/dashboard.vue +186 -0
- package/app/layouts/view.vue +60 -0
- package/app/middleware/auth.ts +9 -0
- package/app/pages/agents/[id].vue +602 -0
- package/app/pages/agents/index.vue +412 -0
- package/app/pages/chat.vue +146 -0
- package/app/pages/dashboard.vue +80 -0
- package/app/pages/docs.vue +131 -0
- package/app/pages/hooks.vue +163 -0
- package/app/pages/index.vue +249 -0
- package/app/pages/login.vue +60 -0
- package/app/pages/memories.vue +282 -0
- package/app/pages/settings.vue +625 -0
- package/app/pages/tasks.vue +312 -0
- package/app/pages/view/[uuid].vue +376 -0
- package/dist/cli/index.js +2711 -0
- package/drizzle.config.ts +10 -0
- package/nuxt.config.ts +98 -0
- package/package.json +107 -0
- package/server/api/agents/[id]/cancel.post.ts +27 -0
- package/server/api/agents/[id]/run.post.ts +34 -0
- package/server/api/agents/[id]/runs.get.ts +45 -0
- package/server/api/agents/[id]/stats.get.ts +94 -0
- package/server/api/agents/[id].delete.ts +29 -0
- package/server/api/agents/[id].get.ts +25 -0
- package/server/api/agents/[id].patch.ts +55 -0
- package/server/api/agents/index.get.ts +15 -0
- package/server/api/agents/index.post.ts +48 -0
- package/server/api/agents/stats.get.ts +86 -0
- package/server/api/auth/[...all].ts +5 -0
- package/server/api/conversations/[id].delete.ts +16 -0
- package/server/api/conversations/[id].get.ts +34 -0
- package/server/api/conversations/index.get.ts +17 -0
- package/server/api/documents/[id]/index.delete.ts +47 -0
- package/server/api/documents/[id]/index.put.ts +102 -0
- package/server/api/documents/[id]/public.get.ts +60 -0
- package/server/api/documents/[id]/restore.post.ts +65 -0
- package/server/api/documents/by-path.post.ts +168 -0
- package/server/api/documents/index.get.ts +48 -0
- package/server/api/fs/delete.post.ts +41 -0
- package/server/api/fs/list.get.ts +99 -0
- package/server/api/fs/mkdir.post.ts +44 -0
- package/server/api/fs/move.post.ts +68 -0
- package/server/api/fs/read.post.ts +48 -0
- package/server/api/fs/rename.post.ts +55 -0
- package/server/api/fs/write.post.ts +51 -0
- package/server/api/health.get.ts +40 -0
- package/server/api/home.get.ts +26 -0
- package/server/api/hooks/events/index.get.ts +56 -0
- package/server/api/hooks/events/index.post.ts +36 -0
- package/server/api/hooks/stats.get.ts +99 -0
- package/server/api/memory/[id].delete.ts +26 -0
- package/server/api/memory/context.get.ts +83 -0
- package/server/api/memory/extract.post.ts +42 -0
- package/server/api/memory/search.get.ts +70 -0
- package/server/api/memory/store.post.ts +31 -0
- package/server/api/projects/[id]/index.delete.ts +40 -0
- package/server/api/projects/[id]/index.get.ts +25 -0
- package/server/api/projects/[id]/index.put.ts +50 -0
- package/server/api/projects/index.get.ts +20 -0
- package/server/api/projects/index.post.ts +34 -0
- package/server/api/secrets/[key].delete.ts +31 -0
- package/server/api/secrets/[key].get.ts +30 -0
- package/server/api/secrets/[key].put.ts +52 -0
- package/server/api/secrets/index.get.ts +20 -0
- package/server/api/secrets/index.post.ts +58 -0
- package/server/api/tasks/[id]/index.delete.ts +46 -0
- package/server/api/tasks/[id]/index.get.ts +24 -0
- package/server/api/tasks/[id]/index.put.ts +70 -0
- package/server/api/tasks/[id]/restore.post.ts +49 -0
- package/server/api/tasks/index.get.ts +53 -0
- package/server/api/tasks/index.post.ts +47 -0
- package/server/api/tasks/tags.get.ts +21 -0
- package/server/api/user/email.patch.ts +56 -0
- package/server/db/index.ts +76 -0
- package/server/db/migrate.ts +41 -0
- package/server/db/schema.ts +345 -0
- package/server/db/seed.ts +46 -0
- package/server/db/types.ts +28 -0
- package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
- package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
- package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
- package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
- package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
- package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
- package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
- package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
- package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
- package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
- package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
- package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
- package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
- package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
- package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
- package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
- package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
- package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
- package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
- package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
- package/server/drizzle/migrations/meta/_journal.json +76 -0
- package/server/middleware/auth.ts +79 -0
- package/server/plugins/00.env-validate.ts +38 -0
- package/server/plugins/01.api-token.ts +31 -0
- package/server/plugins/02.database.ts +54 -0
- package/server/plugins/03.file-watcher.ts +65 -0
- package/server/plugins/04.cron-agents.ts +26 -0
- package/server/routes/_ws/chat.ts +252 -0
- package/server/routes/notifications.ts +47 -0
- package/server/routes/terminal.ts +98 -0
- package/server/services/agent-executor.ts +218 -0
- package/server/services/cron-scheduler.ts +78 -0
- package/server/services/memory-extractor.ts +120 -0
- package/server/utils/agent-cleanup.ts +91 -0
- package/server/utils/agent-registry.ts +95 -0
- package/server/utils/auth.ts +33 -0
- package/server/utils/chat-session-manager.ts +59 -0
- package/server/utils/crypto.ts +40 -0
- package/server/utils/db-guard.ts +12 -0
- package/server/utils/db-state.ts +63 -0
- package/server/utils/document-sync.ts +207 -0
- package/server/utils/frontmatter.ts +84 -0
- package/server/utils/notification-bus.ts +60 -0
- package/server/utils/path-validator.ts +55 -0
- package/server/utils/pty-manager.ts +130 -0
- package/shared/types/index.ts +604 -0
- package/shared/utils/language-detection.ts +87 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { desc, eq, sql } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import type { MemoryChunk, MemoryContextResponse } from '~~/shared/types'
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
requireDb(event)
|
|
8
|
+
|
|
9
|
+
const query = getQuery(event)
|
|
10
|
+
const db = getDb()
|
|
11
|
+
|
|
12
|
+
const projectPath = query.project as string | undefined
|
|
13
|
+
const limit = Math.min(parseInt(query.limit as string) || 5, 20)
|
|
14
|
+
|
|
15
|
+
// Get recent, high-relevance memories for this project
|
|
16
|
+
let dbQuery = db.select()
|
|
17
|
+
.from(schema.memoryChunks)
|
|
18
|
+
|
|
19
|
+
if (projectPath)
|
|
20
|
+
dbQuery = dbQuery.where(eq(schema.memoryChunks.projectPath, projectPath)) as typeof dbQuery
|
|
21
|
+
|
|
22
|
+
const memories = await dbQuery
|
|
23
|
+
.orderBy(
|
|
24
|
+
desc(schema.memoryChunks.relevanceScore),
|
|
25
|
+
desc(schema.memoryChunks.createdAt)
|
|
26
|
+
)
|
|
27
|
+
.limit(limit)
|
|
28
|
+
|
|
29
|
+
// Update access count for retrieved memories
|
|
30
|
+
if (memories.length > 0) {
|
|
31
|
+
const ids = memories.map(m => m.id)
|
|
32
|
+
await db.execute(sql`
|
|
33
|
+
UPDATE memory_chunks
|
|
34
|
+
SET access_count = access_count + 1,
|
|
35
|
+
last_accessed_at = NOW()
|
|
36
|
+
WHERE id = ANY(${ids})
|
|
37
|
+
`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Format for Claude context injection
|
|
41
|
+
const formatted = formatForContext(memories as MemoryChunk[])
|
|
42
|
+
|
|
43
|
+
const response: MemoryContextResponse = {
|
|
44
|
+
memories: memories as MemoryChunk[],
|
|
45
|
+
formatted
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { data: response }
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
function formatForContext(memories: MemoryChunk[]): string {
|
|
52
|
+
if (memories.length === 0)
|
|
53
|
+
return ''
|
|
54
|
+
|
|
55
|
+
const lines = ['## Previous Context\n']
|
|
56
|
+
|
|
57
|
+
// Group by type
|
|
58
|
+
const byType = memories.reduce((acc, m) => {
|
|
59
|
+
if (!acc[m.chunkType])
|
|
60
|
+
acc[m.chunkType] = []
|
|
61
|
+
acc[m.chunkType]!.push(m)
|
|
62
|
+
return acc
|
|
63
|
+
}, {} as Record<string, MemoryChunk[]>)
|
|
64
|
+
|
|
65
|
+
const typeLabels: Record<string, string> = {
|
|
66
|
+
decision: 'Decisions',
|
|
67
|
+
fact: 'Key Facts',
|
|
68
|
+
solution: 'Solutions',
|
|
69
|
+
pattern: 'Patterns',
|
|
70
|
+
preference: 'Preferences',
|
|
71
|
+
summary: 'Summaries'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
for (const [type, items] of Object.entries(byType)) {
|
|
75
|
+
const label = typeLabels[type] || type
|
|
76
|
+
lines.push(`### ${label}`)
|
|
77
|
+
for (const item of items)
|
|
78
|
+
lines.push(`- ${item.content}`)
|
|
79
|
+
lines.push('')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return lines.join('\n')
|
|
83
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getDb, schema } from '~~/server/db'
|
|
2
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import { extractMemories, extractMemoriesFromTranscriptFile } from '~~/server/services/memory-extractor'
|
|
4
|
+
import type { ExtractMemoryInput, MemoryChunk } from '~~/shared/types'
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
requireDb(event)
|
|
8
|
+
|
|
9
|
+
const body = await readBody<ExtractMemoryInput & { transcriptPath?: string }>(event)
|
|
10
|
+
const db = getDb()
|
|
11
|
+
|
|
12
|
+
let memories: Awaited<ReturnType<typeof extractMemories>> = []
|
|
13
|
+
|
|
14
|
+
// Extract from transcript file path or raw transcript
|
|
15
|
+
if (body.transcriptPath)
|
|
16
|
+
memories = await extractMemoriesFromTranscriptFile(body.transcriptPath)
|
|
17
|
+
else if (body.transcript)
|
|
18
|
+
memories = await extractMemories(body.transcript)
|
|
19
|
+
else
|
|
20
|
+
throw createError({ statusCode: 400, message: 'Either transcript or transcriptPath is required' })
|
|
21
|
+
|
|
22
|
+
if (memories.length === 0)
|
|
23
|
+
return { data: [], message: 'No memories worth extracting' }
|
|
24
|
+
|
|
25
|
+
// Store extracted memories
|
|
26
|
+
const inserted = await db.insert(schema.memoryChunks)
|
|
27
|
+
.values(memories.map(m => ({
|
|
28
|
+
sessionId: body.sessionId,
|
|
29
|
+
projectPath: body.projectPath,
|
|
30
|
+
chunkType: m.type,
|
|
31
|
+
content: m.content,
|
|
32
|
+
relevanceScore: m.relevance
|
|
33
|
+
})))
|
|
34
|
+
.returning()
|
|
35
|
+
|
|
36
|
+
console.log(`[memory] Extracted and stored ${inserted.length} memories`)
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
data: inserted as MemoryChunk[],
|
|
40
|
+
message: `Extracted ${inserted.length} memories`
|
|
41
|
+
}
|
|
42
|
+
})
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { sql, desc, eq, and, gte, ilike, or } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import type { MemoryChunk, MemoryChunkType } from '~~/shared/types'
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
requireDb(event)
|
|
8
|
+
|
|
9
|
+
const query = getQuery(event)
|
|
10
|
+
const db = getDb()
|
|
11
|
+
|
|
12
|
+
const searchQuery = query.query as string | undefined
|
|
13
|
+
const projectPath = query.projectPath as string | undefined
|
|
14
|
+
const chunkType = query.chunkType as MemoryChunkType | undefined
|
|
15
|
+
const minRelevance = query.minRelevance ? parseFloat(query.minRelevance as string) : undefined
|
|
16
|
+
const limit = Math.min(parseInt(query.limit as string) || 20, 100)
|
|
17
|
+
|
|
18
|
+
const conditions = []
|
|
19
|
+
|
|
20
|
+
// Project filter
|
|
21
|
+
if (projectPath)
|
|
22
|
+
conditions.push(eq(schema.memoryChunks.projectPath, projectPath))
|
|
23
|
+
|
|
24
|
+
// Type filter
|
|
25
|
+
if (chunkType)
|
|
26
|
+
conditions.push(eq(schema.memoryChunks.chunkType, chunkType))
|
|
27
|
+
|
|
28
|
+
// Relevance filter
|
|
29
|
+
if (minRelevance !== undefined)
|
|
30
|
+
conditions.push(gte(schema.memoryChunks.relevanceScore, minRelevance))
|
|
31
|
+
|
|
32
|
+
// Text search (simple ILIKE for now, will upgrade to tsvector)
|
|
33
|
+
if (searchQuery) {
|
|
34
|
+
conditions.push(or(
|
|
35
|
+
ilike(schema.memoryChunks.content, `%${searchQuery}%`),
|
|
36
|
+
ilike(schema.memoryChunks.sourceExcerpt, `%${searchQuery}%`)
|
|
37
|
+
))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let dbQuery = db.select()
|
|
41
|
+
.from(schema.memoryChunks)
|
|
42
|
+
|
|
43
|
+
if (conditions.length > 0)
|
|
44
|
+
dbQuery = dbQuery.where(and(...conditions)) as typeof dbQuery
|
|
45
|
+
|
|
46
|
+
const memories = await dbQuery
|
|
47
|
+
.orderBy(
|
|
48
|
+
desc(schema.memoryChunks.relevanceScore),
|
|
49
|
+
desc(schema.memoryChunks.createdAt)
|
|
50
|
+
)
|
|
51
|
+
.limit(limit)
|
|
52
|
+
|
|
53
|
+
// Update access count and timestamp for retrieved memories
|
|
54
|
+
if (memories.length > 0) {
|
|
55
|
+
try {
|
|
56
|
+
const ids = memories.map(m => m.id)
|
|
57
|
+
await db.execute(sql`
|
|
58
|
+
UPDATE memory_chunks
|
|
59
|
+
SET access_count = access_count + 1,
|
|
60
|
+
last_accessed_at = NOW()
|
|
61
|
+
WHERE id = ANY(ARRAY[${sql.join(ids.map(id => sql`${id}`), sql`, `)}]::uuid[])
|
|
62
|
+
`)
|
|
63
|
+
} catch (e) {
|
|
64
|
+
// Don't fail the request if access count update fails
|
|
65
|
+
console.error('[memory/search] Failed to update access count:', e)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return { data: memories as MemoryChunk[] }
|
|
70
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getDb, schema } from '~~/server/db'
|
|
2
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import type { CreateMemoryInput, MemoryChunk } from '~~/shared/types'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const body = await readBody<CreateMemoryInput>(event)
|
|
9
|
+
const db = getDb()
|
|
10
|
+
|
|
11
|
+
if (!body.content)
|
|
12
|
+
throw createError({ statusCode: 400, message: 'content is required' })
|
|
13
|
+
|
|
14
|
+
if (!body.chunkType)
|
|
15
|
+
throw createError({ statusCode: 400, message: 'chunkType is required' })
|
|
16
|
+
|
|
17
|
+
const [inserted] = await db.insert(schema.memoryChunks)
|
|
18
|
+
.values({
|
|
19
|
+
sessionId: body.sessionId,
|
|
20
|
+
projectPath: body.projectPath,
|
|
21
|
+
chunkType: body.chunkType,
|
|
22
|
+
content: body.content,
|
|
23
|
+
sourceExcerpt: body.sourceExcerpt,
|
|
24
|
+
relevanceScore: body.relevanceScore ?? 1.0
|
|
25
|
+
})
|
|
26
|
+
.returning()
|
|
27
|
+
|
|
28
|
+
console.log(`[memory] Stored memory: ${body.chunkType} - ${body.content.slice(0, 50)}...`)
|
|
29
|
+
|
|
30
|
+
return { data: inserted as MemoryChunk }
|
|
31
|
+
})
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const id = getRouterParam(event, 'id')
|
|
9
|
+
|
|
10
|
+
if (!id)
|
|
11
|
+
throw createError({ statusCode: 400, message: 'Project ID is required' })
|
|
12
|
+
|
|
13
|
+
const db = getDb()
|
|
14
|
+
|
|
15
|
+
// Check project exists
|
|
16
|
+
const [existing] = await db
|
|
17
|
+
.select({ id: schema.projects.id })
|
|
18
|
+
.from(schema.projects)
|
|
19
|
+
.where(eq(schema.projects.id, id))
|
|
20
|
+
.limit(1)
|
|
21
|
+
|
|
22
|
+
if (!existing)
|
|
23
|
+
throw createError({ statusCode: 404, message: 'Project not found' })
|
|
24
|
+
|
|
25
|
+
const userId = event.context.user?.id
|
|
26
|
+
|
|
27
|
+
// Soft delete
|
|
28
|
+
const [project] = await db
|
|
29
|
+
.update(schema.projects)
|
|
30
|
+
.set({
|
|
31
|
+
deletedAt: new Date(),
|
|
32
|
+
deletedBy: userId,
|
|
33
|
+
modifiedAt: new Date(),
|
|
34
|
+
modifiedBy: userId
|
|
35
|
+
})
|
|
36
|
+
.where(eq(schema.projects.id, id))
|
|
37
|
+
.returning()
|
|
38
|
+
|
|
39
|
+
return { data: project }
|
|
40
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const id = getRouterParam(event, 'id')
|
|
9
|
+
|
|
10
|
+
if (!id)
|
|
11
|
+
throw createError({ statusCode: 400, message: 'Project ID is required' })
|
|
12
|
+
|
|
13
|
+
const db = getDb()
|
|
14
|
+
|
|
15
|
+
const [project] = await db
|
|
16
|
+
.select()
|
|
17
|
+
.from(schema.projects)
|
|
18
|
+
.where(eq(schema.projects.id, id))
|
|
19
|
+
.limit(1)
|
|
20
|
+
|
|
21
|
+
if (!project)
|
|
22
|
+
throw createError({ statusCode: 404, message: 'Project not found' })
|
|
23
|
+
|
|
24
|
+
return { data: project }
|
|
25
|
+
})
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
import type { UpdateProjectInput } from '~~/shared/types'
|
|
5
|
+
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
requireDb(event)
|
|
8
|
+
|
|
9
|
+
const id = getRouterParam(event, 'id')
|
|
10
|
+
|
|
11
|
+
if (!id)
|
|
12
|
+
throw createError({ statusCode: 400, message: 'Project ID is required' })
|
|
13
|
+
|
|
14
|
+
const body = await readBody<UpdateProjectInput>(event)
|
|
15
|
+
|
|
16
|
+
// Validate color if provided
|
|
17
|
+
if (body.color && !/^#[0-9A-Fa-f]{6}$/.test(body.color))
|
|
18
|
+
throw createError({ statusCode: 400, message: 'Color must be a valid hex color (e.g., #3b82f6)' })
|
|
19
|
+
|
|
20
|
+
const db = getDb()
|
|
21
|
+
|
|
22
|
+
// Check project exists
|
|
23
|
+
const [existing] = await db
|
|
24
|
+
.select({ id: schema.projects.id })
|
|
25
|
+
.from(schema.projects)
|
|
26
|
+
.where(eq(schema.projects.id, id))
|
|
27
|
+
.limit(1)
|
|
28
|
+
|
|
29
|
+
if (!existing)
|
|
30
|
+
throw createError({ statusCode: 404, message: 'Project not found' })
|
|
31
|
+
|
|
32
|
+
const userId = event.context.user?.id
|
|
33
|
+
|
|
34
|
+
const updates: Record<string, unknown> = {
|
|
35
|
+
modifiedAt: new Date(),
|
|
36
|
+
modifiedBy: userId
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (body.name !== undefined) updates.name = body.name.trim()
|
|
40
|
+
if (body.color !== undefined) updates.color = body.color
|
|
41
|
+
if (body.description !== undefined) updates.description = body.description?.trim() || null
|
|
42
|
+
|
|
43
|
+
const [project] = await db
|
|
44
|
+
.update(schema.projects)
|
|
45
|
+
.set(updates)
|
|
46
|
+
.where(eq(schema.projects.id, id))
|
|
47
|
+
.returning()
|
|
48
|
+
|
|
49
|
+
return { data: project }
|
|
50
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isNull } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const query = getQuery(event)
|
|
9
|
+
const includeDeleted = query.includeDeleted === 'true'
|
|
10
|
+
|
|
11
|
+
const db = getDb()
|
|
12
|
+
|
|
13
|
+
const projects = await db
|
|
14
|
+
.select()
|
|
15
|
+
.from(schema.projects)
|
|
16
|
+
.where(includeDeleted ? undefined : isNull(schema.projects.deletedAt))
|
|
17
|
+
.orderBy(schema.projects.name)
|
|
18
|
+
|
|
19
|
+
return { data: projects }
|
|
20
|
+
})
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { getDb, schema } from '~~/server/db'
|
|
2
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
import type { CreateProjectInput } from '~~/shared/types'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const body = await readBody<CreateProjectInput>(event)
|
|
9
|
+
|
|
10
|
+
if (!body.name?.trim())
|
|
11
|
+
throw createError({ statusCode: 400, message: 'Project name is required' })
|
|
12
|
+
|
|
13
|
+
if (!body.color?.trim())
|
|
14
|
+
throw createError({ statusCode: 400, message: 'Project color is required' })
|
|
15
|
+
|
|
16
|
+
// Validate hex color format
|
|
17
|
+
if (!/^#[0-9A-Fa-f]{6}$/.test(body.color))
|
|
18
|
+
throw createError({ statusCode: 400, message: 'Color must be a valid hex color (e.g., #3b82f6)' })
|
|
19
|
+
|
|
20
|
+
const db = getDb()
|
|
21
|
+
const userId = event.context.user?.id
|
|
22
|
+
|
|
23
|
+
const [project] = await db
|
|
24
|
+
.insert(schema.projects)
|
|
25
|
+
.values({
|
|
26
|
+
name: body.name.trim(),
|
|
27
|
+
color: body.color,
|
|
28
|
+
description: body.description?.trim() || null,
|
|
29
|
+
createdBy: userId
|
|
30
|
+
})
|
|
31
|
+
.returning()
|
|
32
|
+
|
|
33
|
+
return { data: project }
|
|
34
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { secrets } from '~~/server/db/schema'
|
|
3
|
+
import { eq } from 'drizzle-orm'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
const key = getRouterParam(event, 'key')
|
|
7
|
+
|
|
8
|
+
if (!key) {
|
|
9
|
+
throw createError({
|
|
10
|
+
statusCode: 400,
|
|
11
|
+
message: 'Key is required'
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const db = getDb()
|
|
16
|
+
|
|
17
|
+
const existing = await db.query.secrets.findFirst({
|
|
18
|
+
where: (s, { eq }) => eq(s.key, key)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
if (!existing) {
|
|
22
|
+
throw createError({
|
|
23
|
+
statusCode: 404,
|
|
24
|
+
message: `Secret with key "${key}" not found`
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
await db.delete(secrets).where(eq(secrets.key, key))
|
|
29
|
+
|
|
30
|
+
return { data: { deleted: true } }
|
|
31
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { decryptSecret } from '~~/server/utils/crypto'
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
const key = getRouterParam(event, 'key')
|
|
6
|
+
|
|
7
|
+
if (!key) {
|
|
8
|
+
throw createError({
|
|
9
|
+
statusCode: 400,
|
|
10
|
+
message: 'Key is required'
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const db = getDb()
|
|
15
|
+
|
|
16
|
+
const secret = await db.query.secrets.findFirst({
|
|
17
|
+
where: (s, { eq }) => eq(s.key, key)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (!secret) {
|
|
21
|
+
throw createError({
|
|
22
|
+
statusCode: 404,
|
|
23
|
+
message: `Secret with key "${key}" not found`
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const value = decryptSecret(secret.encryptedValue, secret.iv)
|
|
28
|
+
|
|
29
|
+
return { data: { key: secret.key, value } }
|
|
30
|
+
})
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { secrets } from '~~/server/db/schema'
|
|
3
|
+
import { encryptSecret } from '~~/server/utils/crypto'
|
|
4
|
+
import { eq } from 'drizzle-orm'
|
|
5
|
+
|
|
6
|
+
interface UpdateSecretInput {
|
|
7
|
+
value?: string
|
|
8
|
+
description?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineEventHandler(async (event) => {
|
|
12
|
+
const key = getRouterParam(event, 'key')
|
|
13
|
+
|
|
14
|
+
if (!key)
|
|
15
|
+
throw createError({ statusCode: 400, message: 'Key is required' })
|
|
16
|
+
|
|
17
|
+
const body = await readBody<UpdateSecretInput>(event)
|
|
18
|
+
const db = getDb()
|
|
19
|
+
|
|
20
|
+
const existing = await db.query.secrets.findFirst({
|
|
21
|
+
where: (s, { eq }) => eq(s.key, key)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (!existing)
|
|
25
|
+
throw createError({ statusCode: 404, message: `Secret with key "${key}" not found` })
|
|
26
|
+
|
|
27
|
+
const updateData: Record<string, unknown> = {
|
|
28
|
+
updatedAt: new Date()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (body.value) {
|
|
32
|
+
const { encrypted, iv } = encryptSecret(body.value)
|
|
33
|
+
updateData.encryptedValue = encrypted
|
|
34
|
+
updateData.iv = iv
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (body.description !== undefined) {
|
|
38
|
+
updateData.description = body.description?.trim() || null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const [result] = await db.update(secrets)
|
|
42
|
+
.set(updateData)
|
|
43
|
+
.where(eq(secrets.key, key))
|
|
44
|
+
.returning({
|
|
45
|
+
id: secrets.id,
|
|
46
|
+
key: secrets.key,
|
|
47
|
+
description: secrets.description,
|
|
48
|
+
updatedAt: secrets.updatedAt
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return { data: result }
|
|
52
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { secrets } from '~~/server/db/schema'
|
|
3
|
+
import { desc } from 'drizzle-orm'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async () => {
|
|
6
|
+
const db = getDb()
|
|
7
|
+
|
|
8
|
+
const results = await db.query.secrets.findMany({
|
|
9
|
+
columns: {
|
|
10
|
+
id: true,
|
|
11
|
+
key: true,
|
|
12
|
+
description: true,
|
|
13
|
+
createdAt: true,
|
|
14
|
+
updatedAt: true
|
|
15
|
+
},
|
|
16
|
+
orderBy: [desc(secrets.createdAt)]
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
return { data: results }
|
|
20
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { secrets } from '~~/server/db/schema'
|
|
3
|
+
import { encryptSecret } from '~~/server/utils/crypto'
|
|
4
|
+
|
|
5
|
+
interface CreateSecretInput {
|
|
6
|
+
key: string
|
|
7
|
+
value: string
|
|
8
|
+
description?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineEventHandler(async (event) => {
|
|
12
|
+
const body = await readBody<CreateSecretInput>(event)
|
|
13
|
+
|
|
14
|
+
if (!body.key?.trim())
|
|
15
|
+
throw createError({ statusCode: 400, message: 'Key is required' })
|
|
16
|
+
|
|
17
|
+
if (!body.value)
|
|
18
|
+
throw createError({ statusCode: 400, message: 'Value is required' })
|
|
19
|
+
|
|
20
|
+
// Validate key format: SCREAMING_SNAKE_CASE
|
|
21
|
+
if (!/^[A-Z][A-Z0-9_]*$/.test(body.key))
|
|
22
|
+
throw createError({ statusCode: 400, message: 'Key must be SCREAMING_SNAKE_CASE (e.g., MY_API_KEY)' })
|
|
23
|
+
|
|
24
|
+
if (body.key.length > 255)
|
|
25
|
+
throw createError({ statusCode: 400, message: 'Key must be 255 characters or less' })
|
|
26
|
+
|
|
27
|
+
const db = getDb()
|
|
28
|
+
const userId = event.context.user?.id
|
|
29
|
+
|
|
30
|
+
// Check for duplicate key
|
|
31
|
+
const existing = await db.query.secrets.findFirst({
|
|
32
|
+
where: (s, { eq }) => eq(s.key, body.key)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (existing) {
|
|
36
|
+
throw createError({
|
|
37
|
+
statusCode: 409,
|
|
38
|
+
message: `Secret with key "${body.key}" already exists`
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const { encrypted, iv } = encryptSecret(body.value)
|
|
43
|
+
|
|
44
|
+
const [result] = await db.insert(secrets).values({
|
|
45
|
+
key: body.key,
|
|
46
|
+
encryptedValue: encrypted,
|
|
47
|
+
iv,
|
|
48
|
+
description: body.description?.trim() || null,
|
|
49
|
+
createdBy: userId
|
|
50
|
+
}).returning({
|
|
51
|
+
id: secrets.id,
|
|
52
|
+
key: secrets.key,
|
|
53
|
+
description: secrets.description,
|
|
54
|
+
createdAt: secrets.createdAt
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
return { data: result }
|
|
58
|
+
})
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
import { getDb, schema } from '~~/server/db'
|
|
3
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
4
|
+
|
|
5
|
+
export default defineEventHandler(async (event) => {
|
|
6
|
+
requireDb(event)
|
|
7
|
+
|
|
8
|
+
const id = getRouterParam(event, 'id')
|
|
9
|
+
|
|
10
|
+
if (!id)
|
|
11
|
+
throw createError({ statusCode: 400, message: 'Task ID is required' })
|
|
12
|
+
|
|
13
|
+
const db = getDb()
|
|
14
|
+
|
|
15
|
+
// Check task exists
|
|
16
|
+
const [existing] = await db
|
|
17
|
+
.select({ id: schema.tasks.id })
|
|
18
|
+
.from(schema.tasks)
|
|
19
|
+
.where(eq(schema.tasks.id, id))
|
|
20
|
+
.limit(1)
|
|
21
|
+
|
|
22
|
+
if (!existing)
|
|
23
|
+
throw createError({ statusCode: 404, message: 'Task not found' })
|
|
24
|
+
|
|
25
|
+
const userId = event.context.user?.id
|
|
26
|
+
|
|
27
|
+
// Soft delete
|
|
28
|
+
await db
|
|
29
|
+
.update(schema.tasks)
|
|
30
|
+
.set({
|
|
31
|
+
deletedAt: new Date(),
|
|
32
|
+
deletedBy: userId,
|
|
33
|
+
modifiedAt: new Date(),
|
|
34
|
+
modifiedBy: userId
|
|
35
|
+
})
|
|
36
|
+
.where(eq(schema.tasks.id, id))
|
|
37
|
+
|
|
38
|
+
// Fetch updated task with project relation
|
|
39
|
+
const [task] = await db.query.tasks.findMany({
|
|
40
|
+
where: (tasks, { eq }) => eq(tasks.id, id),
|
|
41
|
+
with: { project: true },
|
|
42
|
+
limit: 1
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return { data: task }
|
|
46
|
+
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getDb } from '~~/server/db'
|
|
2
|
+
import { requireDb } from '~~/server/utils/db-guard'
|
|
3
|
+
|
|
4
|
+
export default defineEventHandler(async (event) => {
|
|
5
|
+
requireDb(event)
|
|
6
|
+
|
|
7
|
+
const id = getRouterParam(event, 'id')
|
|
8
|
+
|
|
9
|
+
if (!id)
|
|
10
|
+
throw createError({ statusCode: 400, message: 'Task ID is required' })
|
|
11
|
+
|
|
12
|
+
const db = getDb()
|
|
13
|
+
|
|
14
|
+
const [task] = await db.query.tasks.findMany({
|
|
15
|
+
where: (tasks, { eq }) => eq(tasks.id, id),
|
|
16
|
+
with: { project: true, creator: true },
|
|
17
|
+
limit: 1
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
if (!task)
|
|
21
|
+
throw createError({ statusCode: 404, message: 'Task not found' })
|
|
22
|
+
|
|
23
|
+
return { data: task }
|
|
24
|
+
})
|