feat: hook syncUser to login callback
Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
@@ -1,39 +1,46 @@
|
||||
import type { UserIdentity } from "convex/server"
|
||||
import {
|
||||
customCtx,
|
||||
customMutation,
|
||||
customQuery,
|
||||
} from "convex-helpers/server/customFunctions"
|
||||
import type { Doc } from "./_generated/dataModel"
|
||||
import type { MutationCtx, QueryCtx } from "./_generated/server"
|
||||
import { mutation, query } from "./_generated/server"
|
||||
import { userIdentityOrThrow, userOrThrow } from "./model/user"
|
||||
|
||||
export type AuthenticatedQueryCtx = QueryCtx & {
|
||||
user: Doc<"users">
|
||||
identity: UserIdentity
|
||||
}
|
||||
|
||||
export type AuthenticatedMutationCtx = MutationCtx & {
|
||||
user: Doc<"users">
|
||||
identity: UserIdentity
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom query that automatically provides authenticated user context
|
||||
* Throws an error if the user is not authenticated
|
||||
*/
|
||||
export const authenticatedQuery = customQuery(query, {
|
||||
args: {},
|
||||
input: async (ctx, args) => {
|
||||
export const authenticatedQuery = customQuery(
|
||||
query,
|
||||
customCtx(async (ctx: QueryCtx) => {
|
||||
const user = await userOrThrow(ctx)
|
||||
const identity = await userIdentityOrThrow(ctx)
|
||||
return {
|
||||
ctx: { ...ctx, user, identity },
|
||||
args,
|
||||
}
|
||||
},
|
||||
})
|
||||
return { user, identity }
|
||||
}),
|
||||
)
|
||||
|
||||
/**
|
||||
* Custom mutation that automatically provides authenticated user context
|
||||
* Throws an error if the user is not authenticated
|
||||
*/
|
||||
export const authenticatedMutation = customMutation(mutation, {
|
||||
args: {},
|
||||
input: async (ctx, args) => {
|
||||
export const authenticatedMutation = customMutation(
|
||||
mutation,
|
||||
customCtx(async (ctx: MutationCtx) => {
|
||||
const user = await userOrThrow(ctx)
|
||||
const identity = await userIdentityOrThrow(ctx)
|
||||
|
||||
return {
|
||||
ctx: { ...ctx, user, identity },
|
||||
args,
|
||||
}
|
||||
},
|
||||
})
|
||||
return { user, identity }
|
||||
}),
|
||||
)
|
||||
|
@@ -10,14 +10,10 @@ export enum Code {
|
||||
export type ApplicationError = ConvexError<{ code: Code; message: string }>
|
||||
|
||||
export function isApplicationError(error: unknown): error is ApplicationError {
|
||||
return (
|
||||
error instanceof ConvexError &&
|
||||
"code" in error.data &&
|
||||
"message" in error.data
|
||||
)
|
||||
return error instanceof ConvexError && "code" in error.data
|
||||
}
|
||||
|
||||
export function create(code: Code, message: string = "unknown error") {
|
||||
export function create(code: Code, message?: string) {
|
||||
return new ConvexError({
|
||||
code,
|
||||
message,
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import type { Id } from "../_generated/dataModel"
|
||||
import type { MutationCtx, QueryCtx } from "../_generated/server"
|
||||
import type { AuthenticatedMutationCtx } from "../functions"
|
||||
import * as Err from "./error"
|
||||
|
||||
/**
|
||||
@@ -36,3 +38,9 @@ export async function userOrThrow(ctx: QueryCtx | MutationCtx) {
|
||||
|
||||
return user
|
||||
}
|
||||
|
||||
export async function register(ctx: AuthenticatedMutationCtx) {
|
||||
await ctx.db.insert("users", {
|
||||
jwtSubject: ctx.identity.subject,
|
||||
})
|
||||
}
|
||||
|
@@ -3,8 +3,8 @@ import { v } from "convex/values"
|
||||
|
||||
const schema = defineSchema({
|
||||
users: defineTable({
|
||||
jwtSubject: v.optional(v.string()), // JWT subject from identity provider (optional for migration)
|
||||
}).index("byJwtSubject", ["jwtSubject"]), // Unique index for JWT subject lookup
|
||||
jwtSubject: v.string(),
|
||||
}).index("byJwtSubject", ["jwtSubject"]),
|
||||
files: defineTable({
|
||||
storageId: v.id("_storage"),
|
||||
userId: v.id("users"),
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { mutation } from "./_generated/server"
|
||||
import { authenticatedQuery } from "./functions"
|
||||
import { getOrCreateUser, userIdentityOrThrow } from "./model/user"
|
||||
import * as Err from "./model/error"
|
||||
|
||||
export const getCurrentUser = authenticatedQuery({
|
||||
handler: async (ctx) => {
|
||||
@@ -11,15 +11,22 @@ export const getCurrentUser = authenticatedQuery({
|
||||
|
||||
export const syncUser = mutation({
|
||||
handler: async (ctx) => {
|
||||
// This function creates or updates the internal user from identity provider
|
||||
const userId = await getOrCreateUser(ctx)
|
||||
const identity = await userIdentityOrThrow(ctx)
|
||||
|
||||
return {
|
||||
userId,
|
||||
jwtSubject: identity.subject,
|
||||
name: identity.name,
|
||||
email: identity.email,
|
||||
const identity = await ctx.auth.getUserIdentity()
|
||||
if (!identity) {
|
||||
throw Err.create(Err.Code.Unauthenticated)
|
||||
}
|
||||
|
||||
const existingUser = await ctx.db
|
||||
.query("users")
|
||||
.withIndex("byJwtSubject", (q) =>
|
||||
q.eq("jwtSubject", identity.subject),
|
||||
)
|
||||
.first()
|
||||
|
||||
if (!existingUser) {
|
||||
await ctx.db.insert("users", {
|
||||
jwtSubject: identity.subject,
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
|
Reference in New Issue
Block a user