Spaces:
Starting
Starting
| import { Injectable, Inject, UnauthorizedException } from "@nestjs/common"; | |
| import { eq, and } from "drizzle-orm"; | |
| import { users, userIdentities, type Database } from "@courtmitra/db"; | |
| import { DB } from "../db/db.module.js"; | |
| export interface ClerkUser { | |
| id: string; | |
| emailAddresses: { emailAddress: string }[]; | |
| firstName: string | null; | |
| lastName: string | null; | |
| imageUrl: string | null; | |
| } | |
| () | |
| export class AuthService { | |
| constructor((DB) private readonly db: Database) {} | |
| /** | |
| * Find or create a user from Clerk OAuth data. | |
| * Maps Clerk userId -> our users table via userIdentities. | |
| */ | |
| async findOrCreateUser(clerkUser: ClerkUser): Promise<{ id: string; email: string | null }> { | |
| const providerSubject = clerkUser.id; | |
| // Try to find existing identity by provider + providerSubject (CRITICAL: must match the specific Clerk user) | |
| const existingIdentity = await this.db | |
| .select({ userId: userIdentities.userId }) | |
| .from(userIdentities) | |
| .where( | |
| and( | |
| eq(userIdentities.provider, "google"), | |
| eq(userIdentities.providerSubject, providerSubject), | |
| ), | |
| ) | |
| .limit(1); | |
| if (existingIdentity[0]) { | |
| const user = await this.db | |
| .select() | |
| .from(users) | |
| .where(eq(users.id, existingIdentity[0].userId)) | |
| .limit(1); | |
| if (user[0]) { | |
| return { id: user[0].id, email: user[0].email ?? null }; | |
| } | |
| } | |
| // Create new user | |
| const email = clerkUser.emailAddresses[0]?.emailAddress ?? null; | |
| const fullName = [clerkUser.firstName, clerkUser.lastName].filter(Boolean).join(" ") || null; | |
| const [userRow] = await this.db | |
| .insert(users) | |
| .values({ | |
| displayName: fullName ?? email ?? "User", | |
| email, | |
| avatarUrl: clerkUser.imageUrl, | |
| fullName, | |
| }) | |
| .returning(); | |
| if (!userRow) { | |
| throw new UnauthorizedException("Failed to create user"); | |
| } | |
| // Create identity link | |
| await this.db.insert(userIdentities).values({ | |
| userId: userRow.id, | |
| provider: "google", | |
| providerSubject, | |
| isVerified: true, | |
| metadata: { source: "clerk_oauth" }, | |
| }); | |
| return { id: userRow.id, email }; | |
| } | |
| /** Look up a user by their internal DB id. */ | |
| async findUserById(id: string) { | |
| const rows = await this.db.select().from(users).where(eq(users.id, id)).limit(1); | |
| return rows[0] ?? null; | |
| } | |
| } | |