Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion components.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
"registries": {
"@ai-elements": "https://ai-sdk.dev/elements/api/registry/{name}.json"
}
}
4 changes: 4 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
*/

import type * as auth from "../auth.js";
import type * as conversations from "../conversations.js";
import type * as files from "../files.js";
import type * as projects from "../projects.js";
import type * as system from "../system.js";

import type {
ApiFromModules,
Expand All @@ -20,8 +22,10 @@ import type {

declare const fullApi: ApiFromModules<{
auth: typeof auth;
conversations: typeof conversations;
files: typeof files;
projects: typeof projects;
system: typeof system;
}>;

/**
Expand Down
117 changes: 117 additions & 0 deletions convex/conversations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { v } from "convex/values";
import { mutation, query } from "./_generated/server";

import { verifyAuth } from "./auth";

export const create = mutation({
args: {
projectId: v.id("projects"),
title: v.string(),
},
handler: async (ctx, args) => {
const identity = await verifyAuth(ctx);

const project = await ctx.db.get("projects", args.projectId);

if (!project) {
throw new Error("Project not found");
}

if (project.ownerId !== identity.subject) {
throw new Error("Unauthorized to access this project");
}

const conversationId = await ctx.db.insert("conversations", {
projectId: args.projectId,
title: args.title,
updatedAt: Date.now(),
});

return conversationId;
},
});

export const getById = query({
args: {
id: v.id("conversations"),
},
handler: async (ctx, args) => {
const identity = await verifyAuth(ctx);

const conversation = await ctx.db.get("conversations", args.id);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Same issue: Fix all ctx.db.get calls throughout the file.

Multiple occurrences of the incorrect two-argument ctx.db.get() pattern exist in this file.

🐛 Proposed fix for all occurrences
-    const conversation = await ctx.db.get("conversations", args.id);
+    const conversation = await ctx.db.get(args.id);
-    const project = await ctx.db.get("projects", conversation.projectId);
+    const project = await ctx.db.get(conversation.projectId);
-    const project = await ctx.db.get("projects", args.projectId);
+    const project = await ctx.db.get(args.projectId);
-    const conversation = await ctx.db.get("conversations", args.conversationId);
+    const conversation = await ctx.db.get(args.conversationId);
-    const project = await ctx.db.get("projects", conversation.projectId);
+    const project = await ctx.db.get(conversation.projectId);

Also applies to: 47-47, 68-68, 93-93, 99-99

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@convex/conversations.ts` at line 41, Several calls use the wrong two-argument
form ctx.db.get("conversations", args.id); change all ctx.db.get calls to the
single-argument form by removing the collection name and passing only the record
id (e.g., replace ctx.db.get("conversations", args.id) with await
ctx.db.get(args.id)); update every occurrence in this file (including the one
that assigns conversation and the other reported lines) and ensure any
surrounding code awaiting the result still uses await and handles null/undefined
as before.


if (!conversation) {
throw new Error("Conversation not found");
}

const project = await ctx.db.get("projects", conversation.projectId);

if (!project) {
throw new Error("Project not found");
}

if (project.ownerId !== identity.subject) {
throw new Error("Unauthorized to access this project");
}

return conversation;
},
});

export const getByProject = query({
args: {
projectId: v.id("projects"),
},
handler: async (ctx, args) => {
const identity = await verifyAuth(ctx);

const project = await ctx.db.get("projects", args.projectId);

if (!project) {
throw new Error("Project not found");
}

if (project.ownerId !== identity.subject) {
throw new Error("Unauthorized to access this project");
}

return await ctx.db
.query("conversations")
.withIndex("by_project", (q) => q.eq("projectId", args.projectId))
.order("desc")
.collect();
},
});

export const getMessages = query({
args: {
conversationId: v.id("conversations"),
},
handler: async (ctx, args) => {
const identity = await verifyAuth(ctx);

const conversation = await ctx.db.get("conversations", args.conversationId);

if (!conversation) {
throw new Error("Conversation not found");
}

const project = await ctx.db.get("projects", conversation.projectId);

if (!project) {
throw new Error("Project not found");
}

if (project.ownerId !== identity.subject) {
throw new Error("Unauthorized to access this project");
}

return await ctx.db
.query("messages")
.withIndex("by_conversation", (q) =>
q.eq("conversationId", args.conversationId)
)
.order("asc")
.collect();
},
});
22 changes: 22 additions & 0 deletions convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,26 @@ export default defineSchema({
.index("by_project", ["projectId"])
.index("by_parent", ["parentId"])
.index("by_project_parent", ["projectId", "parentId"]),

conversations: defineTable({
projectId: v.id("projects"),
title: v.string(),
updatedAt: v.number(),
}).index("by_project", ["projectId"]),

messages: defineTable({
conversationId: v.id("conversations"),
projectId: v.id("projects"),
role: v.union(v.literal("user"), v.literal("assistant")),
content: v.string(),
status: v.optional(
v.union(
v.literal("processing"),
v.literal("completed"),
v.literal("cancelled")
)
),
})
.index("by_conversation", ["conversationId"])
.index("by_project_status", ["projectId", "status"]),
});
78 changes: 78 additions & 0 deletions convex/system.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { v } from "convex/values";

import { mutation, query } from "./_generated/server";

const validateInternalKey = (key: string) => {
const internalKey = process.env.HEXSMITH_CONVEX_INTERNAL_KEY;

if (!internalKey) {
throw new Error("HEXSMITH_CONVEX_INTERNAL_KEY is not configured");
}

if (key !== internalKey) {
throw new Error("Invalid internal key");
}
};

export const getConversationById = query({
args: {
conversationId: v.id("conversations"),
internalKey: v.string(),
},
handler: async (ctx, args) => {
validateInternalKey(args.internalKey);

return await ctx.db.get(args.conversationId);
},
});

export const createMessage = mutation({
args: {
internalKey: v.string(),
conversationId: v.id("conversations"),
projectId: v.id("projects"),
role: v.union(v.literal("user"), v.literal("assistant")),
content: v.string(),
status: v.optional(
v.union(
v.literal("processing"),
v.literal("completed"),
v.literal("cancelled")
)
),
},
handler: async (ctx, args) => {
validateInternalKey(args.internalKey);

const messageId = await ctx.db.insert("messages", {
conversationId: args.conversationId,
projectId: args.projectId,
role: args.role,
content: args.content,
status: args.status,
});

// Update conversation's updatedAt
await ctx.db.patch(args.conversationId, {
updatedAt: Date.now(),
});

return messageId;
},
});

export const updateMessageContent = mutation({
args: {
internalKey: v.string(),
messageId: v.id("messages"),
content: v.string(),
},
handler: async (ctx, args) => {
validateInternalKey(args.internalKey);

await ctx.db.patch(args.messageId, {
content: args.content,
status: "completed" as const,
});
},
});
Loading