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; } @Injectable() export class AuthService { constructor(@Inject(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; } }