)
}
\ No newline at end of file
diff --git a/app/chat/page.tsx b/app/chat/page.tsx
index f6a55907..9b3bd954 100644
--- a/app/chat/page.tsx
+++ b/app/chat/page.tsx
@@ -10,7 +10,7 @@ export default function ChatPage() {
useEffect(() => {
// Generate a new unique chat ID and redirect
- const chatId = uuidv4().substring(0, 8)
+ const chatId = uuidv4()
router.push(`/chat/${chatId}`)
}, [router])
diff --git a/app/example/page.tsx b/app/example/page.tsx
new file mode 100644
index 00000000..51fbe108
--- /dev/null
+++ b/app/example/page.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import { Authenticated, Unauthenticated, AuthLoading } from "convex/react";
+import { SignInButton, UserButton } from "@clerk/nextjs";
+import { useQuery } from "convex/react";
+import { api } from "../../convex/_generated/api";
+
+export default function ExamplePage() {
+ return (
+
+
Convex + Clerk Example
+
+
+
Loading authentication state...
+
+
+
+
+
+ You are signed in:
+
+
+
+
+
+
+
+
+
You are not signed in.
+
+
+
+
+
+
+ );
+}
+
+function Content() {
+ // This will only be called if the user is authenticated
+ const messages = useQuery(api.messages.list);
+
+ return (
+
diff --git a/convex/README.md b/convex/README.md
new file mode 100644
index 00000000..4d82e136
--- /dev/null
+++ b/convex/README.md
@@ -0,0 +1,90 @@
+# Welcome to your Convex functions directory!
+
+Write your Convex functions here.
+See https://docs.convex.dev/functions for more.
+
+A query function that takes two arguments looks like:
+
+```ts
+// functions.js
+import { query } from "./_generated/server";
+import { v } from "convex/values";
+
+export const myQueryFunction = query({
+ // Validators for arguments.
+ args: {
+ first: v.number(),
+ second: v.string(),
+ },
+
+ // Function implementation.
+ handler: async (ctx, args) => {
+ // Read the database as many times as you need here.
+ // See https://docs.convex.dev/database/reading-data.
+ const documents = await ctx.db.query("tablename").collect();
+
+ // Arguments passed from the client are properties of the args object.
+ console.log(args.first, args.second);
+
+ // Write arbitrary JavaScript here: filter, aggregate, build derived data,
+ // remove non-public properties, or create new objects.
+ return documents;
+ },
+});
+```
+
+Using this query function in a React component looks like:
+
+```ts
+const data = useQuery(api.functions.myQueryFunction, {
+ first: 10,
+ second: "hello",
+});
+```
+
+A mutation function looks like:
+
+```ts
+// functions.js
+import { mutation } from "./_generated/server";
+import { v } from "convex/values";
+
+export const myMutationFunction = mutation({
+ // Validators for arguments.
+ args: {
+ first: v.string(),
+ second: v.string(),
+ },
+
+ // Function implementation.
+ handler: async (ctx, args) => {
+ // Insert or modify documents in the database here.
+ // Mutations can also read from the database like queries.
+ // See https://docs.convex.dev/database/writing-data.
+ const message = { body: args.first, author: args.second };
+ const id = await ctx.db.insert("messages", message);
+
+ // Optionally, return a value from your mutation.
+ return await ctx.db.get(id);
+ },
+});
+```
+
+Using this mutation function in a React component looks like:
+
+```ts
+const mutation = useMutation(api.functions.myMutationFunction);
+function handleButtonPress() {
+ // fire and forget, the most common way to use mutations
+ mutation({ first: "Hello!", second: "me" });
+ // OR
+ // use the result once the mutation has completed
+ mutation({ first: "Hello!", second: "me" }).then((result) =>
+ console.log(result),
+ );
+}
+```
+
+Use the Convex CLI to push your functions to a deployment. See everything
+the Convex CLI can do by running `npx convex -h` in your project root
+directory. To learn more, launch the docs with `npx convex docs`.
diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts
new file mode 100644
index 00000000..47aa3189
--- /dev/null
+++ b/convex/_generated/api.d.ts
@@ -0,0 +1,44 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type {
+ ApiFromModules,
+ FilterApi,
+ FunctionReference,
+} from "convex/server";
+import type * as chats from "../chats.js";
+import type * as clerk from "../clerk.js";
+import type * as http from "../http.js";
+import type * as messages from "../messages.js";
+import type * as users from "../users.js";
+
+/**
+ * A utility for referencing Convex functions in your app's API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+declare const fullApi: ApiFromModules<{
+ chats: typeof chats;
+ clerk: typeof clerk;
+ http: typeof http;
+ messages: typeof messages;
+ users: typeof users;
+}>;
+export declare const api: FilterApi<
+ typeof fullApi,
+ FunctionReference
+>;
+export declare const internal: FilterApi<
+ typeof fullApi,
+ FunctionReference
+>;
diff --git a/convex/_generated/api.js b/convex/_generated/api.js
new file mode 100644
index 00000000..3f9c482d
--- /dev/null
+++ b/convex/_generated/api.js
@@ -0,0 +1,22 @@
+/* eslint-disable */
+/**
+ * Generated `api` utility.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import { anyApi } from "convex/server";
+
+/**
+ * A utility for referencing Convex functions in your app's API.
+ *
+ * Usage:
+ * ```js
+ * const myFunctionReference = api.myModule.myFunction;
+ * ```
+ */
+export const api = anyApi;
+export const internal = anyApi;
diff --git a/convex/_generated/dataModel.d.ts b/convex/_generated/dataModel.d.ts
new file mode 100644
index 00000000..8541f319
--- /dev/null
+++ b/convex/_generated/dataModel.d.ts
@@ -0,0 +1,60 @@
+/* eslint-disable */
+/**
+ * Generated data model types.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import type {
+ DataModelFromSchemaDefinition,
+ DocumentByName,
+ TableNamesInDataModel,
+ SystemTableNames,
+} from "convex/server";
+import type { GenericId } from "convex/values";
+import schema from "../schema.js";
+
+/**
+ * The names of all of your Convex tables.
+ */
+export type TableNames = TableNamesInDataModel;
+
+/**
+ * The type of a document stored in Convex.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Doc = DocumentByName<
+ DataModel,
+ TableName
+>;
+
+/**
+ * An identifier for a document in Convex.
+ *
+ * Convex documents are uniquely identified by their `Id`, which is accessible
+ * on the `_id` field. To learn more, see [Document IDs](https://docs.convex.dev/using/document-ids).
+ *
+ * Documents can be loaded using `db.get(id)` in query and mutation functions.
+ *
+ * IDs are just strings at runtime, but this type can be used to distinguish them from other
+ * strings when type checking.
+ *
+ * @typeParam TableName - A string literal type of the table name (like "users").
+ */
+export type Id =
+ GenericId;
+
+/**
+ * A type describing your Convex data model.
+ *
+ * This type includes information about what tables you have, the type of
+ * documents stored in those tables, and the indexes defined on them.
+ *
+ * This type is used to parameterize methods like `queryGeneric` and
+ * `mutationGeneric` to make them type-safe.
+ */
+export type DataModel = DataModelFromSchemaDefinition;
diff --git a/convex/_generated/server.d.ts b/convex/_generated/server.d.ts
new file mode 100644
index 00000000..7f337a43
--- /dev/null
+++ b/convex/_generated/server.d.ts
@@ -0,0 +1,142 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ ActionBuilder,
+ HttpActionBuilder,
+ MutationBuilder,
+ QueryBuilder,
+ GenericActionCtx,
+ GenericMutationCtx,
+ GenericQueryCtx,
+ GenericDatabaseReader,
+ GenericDatabaseWriter,
+} from "convex/server";
+import type { DataModel } from "./dataModel.js";
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const query: QueryBuilder;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalQuery: QueryBuilder;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const mutation: MutationBuilder;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalMutation: MutationBuilder;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export declare const action: ActionBuilder;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export declare const internalAction: ActionBuilder;
+
+/**
+ * Define an HTTP action.
+ *
+ * This function will be used to respond to HTTP requests received by a Convex
+ * deployment if the requests matches the path and method where this action
+ * is routed. Be sure to route your action in `convex/http.js`.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up.
+ */
+export declare const httpAction: HttpActionBuilder;
+
+/**
+ * A set of services for use within Convex query functions.
+ *
+ * The query context is passed as the first argument to any Convex query
+ * function run on the server.
+ *
+ * This differs from the {@link MutationCtx} because all of the services are
+ * read-only.
+ */
+export type QueryCtx = GenericQueryCtx;
+
+/**
+ * A set of services for use within Convex mutation functions.
+ *
+ * The mutation context is passed as the first argument to any Convex mutation
+ * function run on the server.
+ */
+export type MutationCtx = GenericMutationCtx;
+
+/**
+ * A set of services for use within Convex action functions.
+ *
+ * The action context is passed as the first argument to any Convex action
+ * function run on the server.
+ */
+export type ActionCtx = GenericActionCtx;
+
+/**
+ * An interface to read from the database within Convex query functions.
+ *
+ * The two entry points are {@link DatabaseReader.get}, which fetches a single
+ * document by its {@link Id}, or {@link DatabaseReader.query}, which starts
+ * building a query.
+ */
+export type DatabaseReader = GenericDatabaseReader;
+
+/**
+ * An interface to read from and write to the database within Convex mutation
+ * functions.
+ *
+ * Convex guarantees that all writes within a single mutation are
+ * executed atomically, so you never have to worry about partial writes leaving
+ * your data in an inconsistent state. See [the Convex Guide](https://docs.convex.dev/understanding/convex-fundamentals/functions#atomicity-and-optimistic-concurrency-control)
+ * for the guarantees Convex provides your functions.
+ */
+export type DatabaseWriter = GenericDatabaseWriter;
diff --git a/convex/_generated/server.js b/convex/_generated/server.js
new file mode 100644
index 00000000..566d4858
--- /dev/null
+++ b/convex/_generated/server.js
@@ -0,0 +1,89 @@
+/* eslint-disable */
+/**
+ * Generated utilities for implementing server-side Convex query and mutation functions.
+ *
+ * THIS CODE IS AUTOMATICALLY GENERATED.
+ *
+ * To regenerate, run `npx convex dev`.
+ * @module
+ */
+
+import {
+ actionGeneric,
+ httpActionGeneric,
+ queryGeneric,
+ mutationGeneric,
+ internalActionGeneric,
+ internalMutationGeneric,
+ internalQueryGeneric,
+} from "convex/server";
+
+/**
+ * Define a query in this Convex app's public API.
+ *
+ * This function will be allowed to read your Convex database and will be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const query = queryGeneric;
+
+/**
+ * Define a query that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to read from your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The query function. It receives a {@link QueryCtx} as its first argument.
+ * @returns The wrapped query. Include this as an `export` to name it and make it accessible.
+ */
+export const internalQuery = internalQueryGeneric;
+
+/**
+ * Define a mutation in this Convex app's public API.
+ *
+ * This function will be allowed to modify your Convex database and will be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const mutation = mutationGeneric;
+
+/**
+ * Define a mutation that is only accessible from other Convex functions (but not from the client).
+ *
+ * This function will be allowed to modify your Convex database. It will not be accessible from the client.
+ *
+ * @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
+ * @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
+ */
+export const internalMutation = internalMutationGeneric;
+
+/**
+ * Define an action in this Convex app's public API.
+ *
+ * An action is a function which can execute any JavaScript code, including non-deterministic
+ * code and code with side-effects, like calling third-party services.
+ * They can be run in Convex's JavaScript environment or in Node.js using the "use node" directive.
+ * They can interact with the database indirectly by calling queries and mutations using the {@link ActionCtx}.
+ *
+ * @param func - The action. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped action. Include this as an `export` to name it and make it accessible.
+ */
+export const action = actionGeneric;
+
+/**
+ * Define an action that is only accessible from other Convex functions (but not from the client).
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument.
+ * @returns The wrapped function. Include this as an `export` to name it and make it accessible.
+ */
+export const internalAction = internalActionGeneric;
+
+/**
+ * Define a Convex HTTP action.
+ *
+ * @param func - The function. It receives an {@link ActionCtx} as its first argument, and a `Request` object
+ * as its second.
+ * @returns The wrapped endpoint function. Route a URL path to this function in `convex/http.js`.
+ */
+export const httpAction = httpActionGeneric;
diff --git a/convex/auth.config.ts b/convex/auth.config.ts
new file mode 100644
index 00000000..efd4db48
--- /dev/null
+++ b/convex/auth.config.ts
@@ -0,0 +1,8 @@
+export default {
+ providers: [
+ {
+ domain: "https://funky-humpback-59.clerk.accounts.dev",
+ applicationID: "convex",
+ },
+ ]
+};
\ No newline at end of file
diff --git a/convex/chats.ts b/convex/chats.ts
new file mode 100644
index 00000000..8b550df8
--- /dev/null
+++ b/convex/chats.ts
@@ -0,0 +1,115 @@
+import { mutation, query } from "./_generated/server";
+import { v } from "convex/values";
+import { Doc, Id } from "./_generated/dataModel";
+
+// Get all chats for a user
+export const getChatsByUser = query({
+ args: { userId: v.id("users") },
+ handler: async (ctx, args) => {
+ return await ctx.db
+ .query("chats")
+ .withIndex("by_user", (q) => q.eq("userId", args.userId))
+ .order("desc")
+ .collect();
+ },
+});
+
+// Get a specific chat by ID
+export const getChatById = query({
+ args: { chatId: v.id("chats") },
+ handler: async (ctx, args) => {
+ return await ctx.db.get(args.chatId);
+ },
+});
+
+// Get messages for a chat
+export const getMessagesByChatId = query({
+ args: { chatId: v.id("chats") },
+ handler: async (ctx, args) => {
+ return await ctx.db
+ .query("messages")
+ .withIndex("by_chat", (q) => q.eq("chatId", args.chatId))
+ .order("asc")
+ .collect();
+ },
+});
+
+// Create a new chat
+export const createChat = mutation({
+ args: {
+ userId: v.id("users"),
+ title: v.string(),
+ },
+ handler: async (ctx, args) => {
+ const { userId, title } = args;
+ const now = Date.now();
+
+ return await ctx.db.insert("chats", {
+ userId,
+ title,
+ createdAt: now,
+ updatedAt: now,
+ });
+ },
+});
+
+// Update a chat
+export const updateChat = mutation({
+ args: {
+ chatId: v.id("chats"),
+ title: v.string(),
+ },
+ handler: async (ctx, args) => {
+ const { chatId, title } = args;
+ const now = Date.now();
+
+ return await ctx.db.patch(chatId, {
+ title,
+ updatedAt: now,
+ });
+ },
+});
+
+// Delete a chat and its messages
+export const deleteChat = mutation({
+ args: {
+ chatId: v.id("chats"),
+ },
+ handler: async (ctx, args) => {
+ const { chatId } = args;
+
+ // Get all messages for this chat
+ const messages = await ctx.db
+ .query("messages")
+ .withIndex("by_chat", (q) => q.eq("chatId", chatId))
+ .collect();
+
+ // Delete all messages
+ for (const message of messages) {
+ await ctx.db.delete(message._id);
+ }
+
+ // Delete the chat
+ return await ctx.db.delete(chatId);
+ },
+});
+
+// Add a message to a chat
+export const addMessage = mutation({
+ args: {
+ chatId: v.id("chats"),
+ content: v.string(),
+ role: v.string(),
+ },
+ handler: async (ctx, args) => {
+ const { chatId, content, role } = args;
+ const now = Date.now();
+
+ return await ctx.db.insert("messages", {
+ chatId,
+ content,
+ role,
+ createdAt: now,
+ });
+ },
+});
\ No newline at end of file
diff --git a/convex/clerk.ts b/convex/clerk.ts
new file mode 100644
index 00000000..50b897c1
--- /dev/null
+++ b/convex/clerk.ts
@@ -0,0 +1,88 @@
+import { v } from "convex/values";
+import { internalMutation } from "./_generated/server";
+
+// This internal mutation will be called to sync user data
+export const syncUser = internalMutation({
+ args: {
+ clerkId: v.string(),
+ email: v.optional(v.string()),
+ firstName: v.optional(v.string()),
+ lastName: v.optional(v.string()),
+ avatarUrl: v.optional(v.string()),
+ },
+ handler: async (ctx, args) => {
+ const { clerkId, email, firstName, lastName, avatarUrl } = args;
+
+ // Check if user already exists
+ const existingUser = await ctx.db
+ .query("users")
+ .withIndex("by_clerk_id", (q) => q.eq("clerkId", clerkId))
+ .first();
+
+ const now = Date.now();
+
+ if (existingUser) {
+ // Update existing user
+ return await ctx.db.patch(existingUser._id, {
+ email,
+ firstName,
+ lastName,
+ avatarUrl,
+ updatedAt: now,
+ });
+ } else {
+ // Create new user
+ return await ctx.db.insert("users", {
+ clerkId,
+ email,
+ firstName,
+ lastName,
+ avatarUrl,
+ createdAt: now,
+ updatedAt: now,
+ });
+ }
+ },
+});
+
+// This internal mutation will delete a user
+export const deleteUser = internalMutation({
+ args: { clerkId: v.string() },
+ handler: async (ctx, args) => {
+ const { clerkId } = args;
+
+ // Find user by clerkId
+ const user = await ctx.db
+ .query("users")
+ .withIndex("by_clerk_id", (q) => q.eq("clerkId", clerkId))
+ .first();
+
+ if (!user) {
+ return null;
+ }
+
+ // Find all chats for this user
+ const chats = await ctx.db
+ .query("chats")
+ .withIndex("by_user", (q) => q.eq("userId", user._id))
+ .collect();
+
+ // Delete all messages in each chat
+ for (const chat of chats) {
+ const messages = await ctx.db
+ .query("messages")
+ .withIndex("by_chat", (q) => q.eq("chatId", chat._id))
+ .collect();
+
+ for (const message of messages) {
+ await ctx.db.delete(message._id);
+ }
+
+ // Delete the chat
+ await ctx.db.delete(chat._id);
+ }
+
+ // Delete the user
+ return await ctx.db.delete(user._id);
+ },
+});
\ No newline at end of file
diff --git a/convex/http.ts b/convex/http.ts
new file mode 100644
index 00000000..31daabcf
--- /dev/null
+++ b/convex/http.ts
@@ -0,0 +1,19 @@
+import { httpRouter } from "convex/server";
+import { httpAction } from "./_generated/server";
+
+const http = httpRouter();
+
+// Add HTTP routes here as needed
+// Example: health check endpoint
+http.route({
+ path: "/health",
+ method: "GET",
+ handler: httpAction(async () => {
+ return new Response(JSON.stringify({ status: "ok" }), {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ });
+ }),
+});
+
+export default http;
\ No newline at end of file
diff --git a/convex/messages.ts b/convex/messages.ts
new file mode 100644
index 00000000..0effbf83
--- /dev/null
+++ b/convex/messages.ts
@@ -0,0 +1,47 @@
+import { mutation, query } from "./_generated/server";
+import { v } from "convex/values";
+
+// List messages for the current authenticated user
+export const list = query({
+ args: {},
+ handler: async (ctx) => {
+ const identity = await ctx.auth.getUserIdentity();
+ if (identity === null) {
+ throw new Error("Not authenticated");
+ }
+
+ // Use the user's tokenIdentifier to filter messages
+ // This is typically in the format of "clerk:user_id"
+ const tokenIdentifier = identity.tokenIdentifier;
+
+ return await ctx.db
+ .query("authMessages")
+ .filter((q) => q.eq(q.field("author"), tokenIdentifier))
+ .order("desc")
+ .collect();
+ },
+});
+
+// Create a new message
+export const create = mutation({
+ args: {
+ content: v.string(),
+ },
+ handler: async (ctx, args) => {
+ const identity = await ctx.auth.getUserIdentity();
+ if (identity === null) {
+ throw new Error("Not authenticated");
+ }
+
+ const { content } = args;
+ const tokenIdentifier = identity.tokenIdentifier;
+
+ return await ctx.db.insert("authMessages", {
+ content,
+ author: tokenIdentifier,
+ authorName: identity.name || "Anonymous",
+ authorEmail: identity.email || undefined,
+ createdAt: Date.now(),
+ });
+ },
+});
\ No newline at end of file
diff --git a/convex/schema.ts b/convex/schema.ts
new file mode 100644
index 00000000..9ba6edd7
--- /dev/null
+++ b/convex/schema.ts
@@ -0,0 +1,40 @@
+import { v } from "convex/values";
+import { defineSchema, defineTable } from "convex/server";
+
+export default defineSchema({
+ // Users table to store user information synced from Clerk
+ users: defineTable({
+ clerkId: v.string(),
+ email: v.optional(v.string()),
+ firstName: v.optional(v.string()),
+ lastName: v.optional(v.string()),
+ avatarUrl: v.optional(v.string()),
+ createdAt: v.number(), // Unix timestamp
+ updatedAt: v.number(), // Unix timestamp
+ }).index("by_clerk_id", ["clerkId"]),
+
+ // Chats table to store chat information
+ chats: defineTable({
+ userId: v.id("users"), // Reference to users table
+ title: v.string(),
+ createdAt: v.number(), // Unix timestamp
+ updatedAt: v.number(), // Unix timestamp
+ }).index("by_user", ["userId"]),
+
+ // Messages table to store chat messages
+ messages: defineTable({
+ chatId: v.id("chats"), // Reference to chats table
+ content: v.string(),
+ role: v.string(), // "user" or "assistant"
+ createdAt: v.number(), // Unix timestamp
+ }).index("by_chat", ["chatId"]),
+
+ // Auth example messages table
+ authMessages: defineTable({
+ content: v.string(),
+ author: v.string(), // Store the tokenIdentifier from auth
+ authorName: v.optional(v.string()),
+ authorEmail: v.optional(v.string()),
+ createdAt: v.number(), // Unix timestamp
+ }).index("by_author", ["author"]),
+});
\ No newline at end of file
diff --git a/convex/tsconfig.json b/convex/tsconfig.json
new file mode 100644
index 00000000..73741270
--- /dev/null
+++ b/convex/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ /* This TypeScript project config describes the environment that
+ * Convex functions run in and is used to typecheck them.
+ * You can modify it, but some settings are required to use Convex.
+ */
+ "compilerOptions": {
+ /* These settings are not required by Convex and can be modified. */
+ "allowJs": true,
+ "strict": true,
+ "moduleResolution": "Bundler",
+ "jsx": "react-jsx",
+ "skipLibCheck": true,
+ "allowSyntheticDefaultImports": true,
+
+ /* These compiler options are required by Convex */
+ "target": "ESNext",
+ "lib": ["ES2021", "dom"],
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "isolatedModules": true,
+ "noEmit": true
+ },
+ "include": ["./**/*"],
+ "exclude": ["./_generated"]
+}
diff --git a/convex/users.ts b/convex/users.ts
new file mode 100644
index 00000000..e6183741
--- /dev/null
+++ b/convex/users.ts
@@ -0,0 +1,65 @@
+import { mutation, query } from "./_generated/server";
+import { v } from "convex/values";
+import { Doc, Id } from "./_generated/dataModel";
+
+// Get user by Clerk ID
+export const getUserByClerkId = query({
+ args: { clerkId: v.string() },
+ handler: async (ctx, args) => {
+ return await ctx.db
+ .query("users")
+ .withIndex("by_clerk_id", (q) => q.eq("clerkId", args.clerkId))
+ .first();
+ },
+});
+
+// Get all users (for admin purposes)
+export const getAllUsers = query({
+ handler: async (ctx) => {
+ return await ctx.db.query("users").collect();
+ },
+});
+
+// Sync a Clerk user with Convex
+export const syncClerkUser = mutation({
+ args: {
+ clerkId: v.string(),
+ email: v.optional(v.string()),
+ firstName: v.optional(v.string()),
+ lastName: v.optional(v.string()),
+ avatarUrl: v.optional(v.string()),
+ },
+ handler: async (ctx, args) => {
+ const { clerkId, email, firstName, lastName, avatarUrl } = args;
+
+ // Check if user already exists
+ const existingUser = await ctx.db
+ .query("users")
+ .withIndex("by_clerk_id", (q) => q.eq("clerkId", clerkId))
+ .first();
+
+ const now = Date.now();
+
+ if (existingUser) {
+ // Update existing user
+ return await ctx.db.patch(existingUser._id, {
+ email,
+ firstName,
+ lastName,
+ avatarUrl,
+ updatedAt: now,
+ });
+ } else {
+ // Create new user
+ return await ctx.db.insert("users", {
+ clerkId,
+ email,
+ firstName,
+ lastName,
+ avatarUrl,
+ createdAt: now,
+ updatedAt: now,
+ });
+ }
+ },
+});
\ No newline at end of file
diff --git a/lib/actions.ts b/lib/actions.ts
new file mode 100644
index 00000000..1a24b498
--- /dev/null
+++ b/lib/actions.ts
@@ -0,0 +1,49 @@
+import { api } from "../convex/_generated/api";
+import { Id } from "../convex/_generated/dataModel";
+import { useUser } from "@clerk/nextjs";
+import { useMutation, useQuery } from "convex/react";
+import { useEffect } from "react";
+
+/**
+ * Hook to sync the current Clerk user with Convex.
+ * Call this in components where you need the user's Convex ID.
+ *
+ * @returns The user's Convex ID or null if not found/authenticated
+ */
+export function useConvexUser() {
+ const { user, isSignedIn } = useUser();
+ const syncUser = useMutation(api.users.syncClerkUser);
+ const convexUser = useQuery(
+ api.users.getUserByClerkId,
+ isSignedIn ? { clerkId: user?.id || "" } : "skip"
+ );
+
+ useEffect(() => {
+ // If user is signed in and we have their data, sync with Convex
+ if (isSignedIn && user?.id) {
+ const primaryEmail = user.emailAddresses[0]?.emailAddress;
+
+ syncUser({
+ clerkId: user.id,
+ email: primaryEmail,
+ firstName: user.firstName || undefined,
+ lastName: user.lastName || undefined,
+ avatarUrl: user.imageUrl || undefined,
+ });
+ }
+ }, [isSignedIn, user?.id, syncUser, user]);
+
+ return convexUser ? convexUser._id : null;
+}
+
+/**
+ * Function to get the current user in an API route
+ */
+export async function getCurrentUser(clerkUserId: string, db: any) {
+ if (!clerkUserId) return null;
+
+ return await db
+ .query("users")
+ .withIndex("by_clerk_id", (q: any) => q.eq("clerkId", clerkUserId))
+ .first();
+}
\ No newline at end of file
diff --git a/lib/convex.ts b/lib/convex.ts
new file mode 100644
index 00000000..deb3ef1e
--- /dev/null
+++ b/lib/convex.ts
@@ -0,0 +1,11 @@
+import { ConvexReactClient } from "convex/react";
+import { ConvexProviderWithClerk } from "convex/react-clerk";
+import { ClerkProvider, useAuth } from "@clerk/nextjs";
+
+// Create a Convex client
+export const convex = new ConvexReactClient(
+ process.env.NEXT_PUBLIC_CONVEX_URL as string
+);
+
+// Convenience exports
+export { ConvexProviderWithClerk, useAuth };
\ No newline at end of file
diff --git a/lib/posthog.ts b/lib/posthog.ts
index 083c056f..58142708 100644
--- a/lib/posthog.ts
+++ b/lib/posthog.ts
@@ -5,8 +5,19 @@ import { PostHog } from "posthog-node"
* You can call this in server-side code to capture events.
*/
export default function PostHogClient() {
- const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY!, {
- host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
+ // Return null if required environment variables are missing
+ if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
+ if (process.env.NODE_ENV === 'development') {
+ console.warn('PostHog API key not found. Server-side analytics tracking is disabled.');
+ }
+ return null;
+ }
+
+ // Default host if not provided
+ const host = process.env.NEXT_PUBLIC_POSTHOG_HOST || 'https://us.i.posthog.com';
+
+ const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
+ host,
// Adjust flush settings as needed
flushAt: 1,
flushInterval: 0,
diff --git a/middleware.ts b/middleware.ts
index befe7e50..2c548177 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -1,31 +1,12 @@
-import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
-import { NextResponse } from 'next/server';
+import { clerkMiddleware } from '@clerk/nextjs/server';
-// Define routes that require authentication
-const isProtectedRoute = createRouteMatcher([
- '/chat(.*)', // Protect the /chat route and its sub-paths
- // Add any other routes you want to protect here
-]);
-
-const publicRoutes = [
- '/',
- '/sign-in(.*)',
- '/sign-up(.*)',
-];
-
-export default clerkMiddleware(async (auth, req) => {
- // For routes that require authentication, protect them
- if (isProtectedRoute(req)) {
- await auth.protect();
- }
-
- // No automatic redirection from home page to chat
-});
+// Using basic clerkMiddleware with no custom configuration
+export default clerkMiddleware();
export const config = {
matcher: [
// Skip Next.js internals and all static files, unless found in search params
- '/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
+ '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
// Always run for API routes
'/(api|trpc)(.*)',
],
diff --git a/next.config.mjs b/next.config.mjs
index fab1d0e5..ff94392c 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -18,14 +18,26 @@ const nextConfig = {
source: '/ingest/static/:path*',
destination: 'https://us-assets.i.posthog.com/static/:path*',
},
- {
- source: '/ingest/:path*',
- destination: 'https://us.i.posthog.com/:path*',
- },
{
source: '/ingest/decide',
destination: 'https://us.i.posthog.com/decide',
},
+ {
+ source: '/ingest/decide/',
+ destination: 'https://us.i.posthog.com/decide/',
+ },
+ {
+ source: '/ingest/e',
+ destination: 'https://us.i.posthog.com/e',
+ },
+ {
+ source: '/ingest/e/',
+ destination: 'https://us.i.posthog.com/e/',
+ },
+ {
+ source: '/ingest/:path*',
+ destination: 'https://us.i.posthog.com/:path*',
+ },
];
},
// This is required to support PostHog trailing slash API requests
diff --git a/package.json b/package.json
index 4bcb0a55..8f8f5a3f 100644
--- a/package.json
+++ b/package.json
@@ -51,6 +51,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
+ "convex": "^1.24.8",
"date-fns": "4.1.0",
"embla-carousel-react": "8.5.1",
"framer-motion": "latest",
@@ -67,6 +68,7 @@
"react-resizable-panels": "^2.1.7",
"recharts": "2.15.0",
"sonner": "^1.7.1",
+ "svix": "^1.66.0",
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7",
"uuid": "^11.1.0",