Skip to content

Commit 403f3b8

Browse files
committed
feat: migrate backend queries and mutations to Better Auth
- Use authComponent.safeGetAuthUser(ctx) for safe authentication checks - Update auth helper functions to work with Better Auth user object structure - Migrate all queries/mutations in chats, messages, files, users, api_keys - Update subscription and connector functions for new auth system - Preserve existing data relationships and user ID mappings This completes the backend migration while maintaining all existing functionality and data integrity through Better Auth triggers.
1 parent 8870dc8 commit 403f3b8

File tree

9 files changed

+179
-167
lines changed

9 files changed

+179
-167
lines changed

convex/api_keys.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { getAuthUserId } from "@convex-dev/auth/server";
21
import { ConvexError, v } from "convex/values";
32
import { ERROR_CODES } from "../lib/error-codes";
3+
import type { Id } from "./_generated/dataModel";
44
import { mutation, query } from "./_generated/server";
5+
import { authComponent } from "./auth";
56

67
const API_KEY_SECRET = process.env.API_KEY_SECRET;
78
if (!API_KEY_SECRET) {
@@ -173,11 +174,12 @@ export const getApiKeys = query({
173174
})
174175
),
175176
handler: async (ctx) => {
176-
const userId = await getAuthUserId(ctx);
177-
if (!userId) {
177+
const authUser = await authComponent.safeGetAuthUser(ctx);
178+
if (!authUser?.userId) {
178179
// Return empty array for unauthenticated users
179180
return [];
180181
}
182+
const userId = authUser.userId as Id<"users">;
181183
const keys = await ctx.db
182184
.query("user_api_keys")
183185
.withIndex("by_user_provider", (q) => q.eq("userId", userId))
@@ -211,10 +213,11 @@ export const saveApiKey = mutation({
211213
},
212214
returns: v.null(),
213215
handler: async (ctx, { provider, key, mode }) => {
214-
const userId = await getAuthUserId(ctx);
215-
if (!userId) {
216+
const authUser = await authComponent.safeGetAuthUser(ctx);
217+
if (!authUser?.userId) {
216218
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
217219
}
220+
const userId = authUser.userId as Id<"users">;
218221
const [encrypted, existing] = await Promise.all([
219222
encrypt(key, userId),
220223
ctx.db
@@ -262,10 +265,11 @@ export const deleteApiKey = mutation({
262265
},
263266
returns: v.null(),
264267
handler: async (ctx, { provider }) => {
265-
const userId = await getAuthUserId(ctx);
266-
if (!userId) {
268+
const authUser = await authComponent.safeGetAuthUser(ctx);
269+
if (!authUser?.userId) {
267270
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
268271
}
272+
const userId = authUser.userId as Id<"users">;
269273
const existing = await ctx.db
270274
.query("user_api_keys")
271275
.withIndex("by_user_provider", (q) =>
@@ -294,10 +298,11 @@ export const updateApiKeyMode = mutation({
294298
},
295299
returns: v.null(),
296300
handler: async (ctx, { provider, mode }) => {
297-
const userId = await getAuthUserId(ctx);
298-
if (!userId) {
301+
const authUser = await authComponent.safeGetAuthUser(ctx);
302+
if (!authUser?.userId) {
299303
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
300304
}
305+
const userId = authUser.userId as Id<"users">;
301306
const existing = await ctx.db
302307
.query("user_api_keys")
303308
.withIndex("by_user_provider", (q) =>
@@ -324,10 +329,11 @@ export const getDecryptedKey = query({
324329
),
325330
},
326331
handler: async (ctx, { provider }) => {
327-
const userId = await getAuthUserId(ctx);
328-
if (!userId) {
332+
const authUser = await authComponent.safeGetAuthUser(ctx);
333+
if (!authUser?.userId) {
329334
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
330335
}
336+
const userId = authUser.userId as Id<"users">;
331337
const existing = await ctx.db
332338
.query("user_api_keys")
333339
.withIndex("by_user_provider", (q) =>
@@ -349,10 +355,11 @@ export const incrementUserApiKeyUsage = mutation({
349355
args: { provider: v.string() },
350356
returns: v.null(),
351357
handler: async (ctx, { provider }) => {
352-
const userId = await getAuthUserId(ctx);
353-
if (!userId) {
358+
const authUser = await authComponent.safeGetAuthUser(ctx);
359+
if (!authUser?.userId) {
354360
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
355361
}
362+
const userId = authUser.userId as Id<"users">;
356363
const existing = await ctx.db
357364
.query("user_api_keys")
358365
.withIndex("by_user_provider", (q) =>

convex/chats.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { getAuthUserId } from "@convex-dev/auth/server";
21
import { ConvexError, v } from "convex/values";
32
import { ERROR_CODES } from "../lib/error-codes";
43
import { detectRedactedContent } from "../lib/redacted-content-detector";
54
import type { Id } from "./_generated/dataModel";
65
import { internalMutation, mutation, query } from "./_generated/server";
6+
import { authComponent } from "./auth";
77
// Import helper functions
88
import {
99
ensureAuthenticated,
@@ -92,10 +92,11 @@ export const forkFromShared = mutation({
9292
args: { sourceChatId: v.id("chats") },
9393
returns: v.object({ chatId: v.id("chats") }),
9494
handler: async (ctx, { sourceChatId }) => {
95-
const userId = await getAuthUserId(ctx);
96-
if (!userId) {
95+
const authUser = await authComponent.safeGetAuthUser(ctx);
96+
if (!authUser?.userId) {
9797
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
9898
}
99+
const userId = authUser.userId as Id<"users">;
99100

100101
const source = await ctx.db.get(sourceChatId);
101102
if (!(source && (source.public ?? false))) {
@@ -201,10 +202,11 @@ export const listChatsForUser = query({
201202
})
202203
),
203204
handler: async (ctx) => {
204-
const userId = await getAuthUserId(ctx);
205-
if (!userId) {
205+
const authUser = await authComponent.safeGetAuthUser(ctx);
206+
if (!authUser?.userId) {
206207
return [];
207208
}
209+
const userId = authUser.userId as Id<"users">;
208210
return await ctx.db
209211
.query("chats")
210212
.withIndex("by_user", (q) => q.eq("userId", userId))
@@ -273,10 +275,11 @@ export const deleteAllChatsForUser = mutation({
273275
args: {},
274276
returns: v.null(),
275277
handler: async (ctx) => {
276-
const userId = await getAuthUserId(ctx);
277-
if (!userId) {
278+
const authUser = await authComponent.safeGetAuthUser(ctx);
279+
if (!authUser?.userId) {
278280
return null;
279281
}
282+
const userId = authUser.userId as Id<"users">;
280283

281284
const chats = await ctx.db
282285
.query("chats")

convex/connectors.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { getAuthUserId } from "@convex-dev/auth/server";
21
import { ConvexError, v } from "convex/values";
32
import { ERROR_CODES } from "../lib/error-codes";
3+
import type { Id } from "./_generated/dataModel";
44
import {
55
internalMutation,
66
internalQuery,
77
mutation,
88
query,
99
} from "./_generated/server";
10+
import { authComponent } from "./auth";
1011
import { ensureAuthenticated } from "./lib/auth_helper";
1112

1213
// Type for connector types
@@ -41,10 +42,11 @@ export const listUserConnectors = query({
4142
})
4243
),
4344
handler: async (ctx) => {
44-
const userId = await getAuthUserId(ctx);
45-
if (!userId) {
45+
const authUser = await authComponent.safeGetAuthUser(ctx);
46+
if (!authUser?.userId) {
4647
return [];
4748
}
49+
const userId = authUser.userId as Id<"users">;
4850
const connectors = await ctx.db
4951
.query("connectors")
5052
.withIndex("by_user", (q) => q.eq("userId", userId))
@@ -75,10 +77,11 @@ export const getConnectorByType = query({
7577
v.null()
7678
),
7779
handler: async (ctx, args) => {
78-
const userId = await getAuthUserId(ctx);
79-
if (!userId) {
80+
const authUser = await authComponent.safeGetAuthUser(ctx);
81+
if (!authUser?.userId) {
8082
return null;
8183
}
84+
const userId = authUser.userId as Id<"users">;
8285
const connector = await ctx.db
8386
.query("connectors")
8487
.withIndex("by_user_and_type", (q) =>

convex/files.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getAuthUserId } from "@convex-dev/auth/server";
21
import { R2, type R2Callbacks } from "@convex-dev/r2";
32
import { ConvexError, v } from "convex/values";
43
import { UPLOAD_ALLOWED_MIME, UPLOAD_MAX_BYTES } from "@/lib/config/upload";
@@ -7,6 +6,7 @@ import { ERROR_CODES } from "../lib/error-codes";
76
import { api, components, internal } from "./_generated/api";
87
import type { Id } from "./_generated/dataModel";
98
import { action, internalMutation, mutation, query } from "./_generated/server";
9+
import { authComponent } from "./auth";
1010

1111
// R2 client and client API exports (used by React upload hook and server routes)
1212
const r2 = new R2(components.r2);
@@ -19,17 +19,18 @@ export const { generateUploadUrl, syncMetadata, onSyncMetadata } = r2.clientApi(
1919
// Provide callbacks reference so the component can invoke onSyncMetadata
2020
callbacks,
2121
checkUpload: async (ctx) => {
22-
const userId = await getAuthUserId(ctx);
23-
if (!userId) {
22+
const authUser = await ctx.auth.getUserIdentity();
23+
if (authUser === null) {
2424
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
2525
}
2626
},
2727
// Create a pending attachment row on upload start
2828
onUpload: async (ctx, _bucket, key) => {
29-
const userId = await getAuthUserId(ctx);
30-
if (!userId) {
29+
const authUser = await authComponent.safeGetAuthUser(ctx);
30+
if (!authUser?.userId) {
3131
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
3232
}
33+
const userId = authUser.userId as Id<"users">;
3334

3435
// If a row for this key already exists for this user, skip insert
3536
const existing = await ctx.db
@@ -146,10 +147,11 @@ export const saveFileAttachment = action({
146147
fileName: v.string(),
147148
},
148149
handler: async (ctx, args): Promise<SavedAttachment> => {
149-
const userId = await getAuthUserId(ctx);
150-
if (!userId) {
150+
const authUser = await authComponent.safeGetAuthUser(ctx);
151+
if (!authUser?.userId) {
151152
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
152153
}
154+
const userId = authUser.userId as Id<"users">;
153155
// Ensure we have fresh metadata from R2
154156
// await ctx.runMutation(api.files.syncMetadata, { key: args.key });
155157

@@ -216,8 +218,8 @@ export const saveGeneratedImage = action({
216218
fileName: v.optional(v.string()),
217219
},
218220
handler: async (ctx, args): Promise<SavedAttachment> => {
219-
const userId = await getAuthUserId(ctx);
220-
if (!userId) {
221+
const authUser = await ctx.auth.getUserIdentity();
222+
if (authUser === null) {
221223
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
222224
}
223225

@@ -270,10 +272,11 @@ export const internalSave = internalMutation({
270272
url: v.optional(v.string()),
271273
},
272274
handler: async (ctx, args) => {
273-
const userId = await getAuthUserId(ctx);
274-
if (!userId) {
275+
const authUser = await authComponent.safeGetAuthUser(ctx);
276+
if (!authUser?.userId) {
275277
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
276278
}
279+
const userId = authUser.userId as Id<"users">;
277280

278281
// Verify that the key belongs to the current user via the pending row.
279282
const existing = await ctx.db
@@ -343,10 +346,11 @@ export const internalSaveGenerated = internalMutation({
343346
url: v.optional(v.string()),
344347
},
345348
handler: async (ctx, args) => {
346-
const userId = await getAuthUserId(ctx);
347-
if (!userId) {
349+
const authUser = await authComponent.safeGetAuthUser(ctx);
350+
if (!authUser?.userId) {
348351
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
349352
}
353+
const userId = authUser.userId as Id<"users">;
350354

351355
// Verify ownership of the key to this user
352356
const existing = await ctx.db
@@ -389,10 +393,11 @@ export const internalSaveGenerated = internalMutation({
389393
export const getAttachment = query({
390394
args: { attachmentId: v.id("chat_attachments") },
391395
handler: async (ctx, args) => {
392-
const userId = await getAuthUserId(ctx);
393-
if (!userId) {
396+
const authUser = await authComponent.safeGetAuthUser(ctx);
397+
if (!authUser?.userId) {
394398
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
395399
}
400+
const userId = authUser.userId as Id<"users">;
396401

397402
const attachment = await ctx.db.get(args.attachmentId);
398403
if (!attachment || attachment.userId !== userId) {
@@ -406,10 +411,11 @@ export const getAttachment = query({
406411
export const findAttachmentByKey = query({
407412
args: { key: v.string() },
408413
handler: async (ctx, args) => {
409-
const userId = await getAuthUserId(ctx);
410-
if (!userId) {
414+
const authUser = await authComponent.safeGetAuthUser(ctx);
415+
if (!authUser?.userId) {
411416
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
412417
}
418+
const userId = authUser.userId as Id<"users">;
413419
const row = await ctx.db
414420
.query("chat_attachments")
415421
.withIndex("by_key", (q) => q.eq("key", args.key))
@@ -428,8 +434,8 @@ export const getStorageUrl = query({
428434
args: { key: v.string() },
429435
returns: v.union(v.string(), v.null()),
430436
handler: async (ctx, args) => {
431-
const userId = await getAuthUserId(ctx);
432-
if (!userId) {
437+
const authUser = await ctx.auth.getUserIdentity();
438+
if (authUser === null) {
433439
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
434440
}
435441

@@ -452,10 +458,11 @@ export const getStorageUrl = query({
452458
export const getAttachmentsForUser = query({
453459
args: {},
454460
handler: async (ctx) => {
455-
const userId = await getAuthUserId(ctx);
456-
if (!userId) {
457-
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
461+
const authUser = await authComponent.safeGetAuthUser(ctx);
462+
if (!authUser?.userId) {
463+
return [];
458464
}
465+
const userId = authUser.userId as Id<"users">;
459466

460467
const attachments = await ctx.db
461468
.query("chat_attachments")
@@ -475,10 +482,11 @@ export const getAttachmentsForUser = query({
475482
export const deleteAttachments = mutation({
476483
args: { attachmentIds: v.array(v.id("chat_attachments")) },
477484
handler: async (ctx, { attachmentIds }) => {
478-
const userId = await getAuthUserId(ctx);
479-
if (!userId) {
485+
const authUser = await authComponent.safeGetAuthUser(ctx);
486+
if (!authUser?.userId) {
480487
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
481488
}
489+
const userId = authUser.userId as Id<"users">;
482490

483491
// Create a Set for O(1) lookup of attachment IDs to delete
484492
const attachmentIdsToDelete = new Set(attachmentIds);
@@ -508,10 +516,11 @@ export const deleteAttachments = mutation({
508516
export const getAttachmentsForChat = query({
509517
args: { chatId: v.id("chats") },
510518
handler: async (ctx, args) => {
511-
const userId = await getAuthUserId(ctx);
512-
if (!userId) {
519+
const authUser = await authComponent.safeGetAuthUser(ctx);
520+
if (!authUser?.userId) {
513521
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
514522
}
523+
const userId = authUser.userId as Id<"users">;
515524

516525
// Verify ownership of the chat
517526
const chat = await ctx.db.get(args.chatId);

convex/import_export.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { getAuthUserId } from "@convex-dev/auth/server";
21
import { ConvexError, v } from "convex/values";
32
import { ERROR_CODES } from "../lib/error-codes";
43
import type { Id } from "./_generated/dataModel";
54
import { mutation } from "./_generated/server";
5+
import { authComponent } from "./auth";
66

77
export const bulkImportChat = mutation({
88
args: {
@@ -44,10 +44,11 @@ export const bulkImportChat = mutation({
4444
messageCount: v.number(),
4545
}),
4646
handler: async (ctx, args) => {
47-
const userId = await getAuthUserId(ctx);
48-
if (!userId) {
47+
const authUser = await authComponent.safeGetAuthUser(ctx);
48+
if (!authUser?.userId) {
4949
throw new ConvexError(ERROR_CODES.NOT_AUTHENTICATED);
5050
}
51+
const userId = authUser.userId as Id<"users">;
5152

5253
// Create chat
5354
const now = Date.now();

0 commit comments

Comments
 (0)