import { eq, and } from "drizzle-orm"; import { apiKeys, users } from "../db/schema.js"; import type { DatabaseContext } from "./database-context.js"; import { rowsAffected } from "./mutation-result.js"; import { deleteReturning, insertReturning } from "./returning.js"; export type ApiKeyRecord = typeof apiKeys.$inferSelect; export type NewApiKeyRecord = typeof apiKeys.$inferInsert; export interface ApiKeyListRecord { id: string; name: string; userId: string; username: string | null; tokenPrefix: string; createdAt: string; expiresAt: string | null; lastUsedAt: string | null; isActive: boolean; /** JSON list, or null for full access. */ scopes: string | null; } export class ApiKeyRepository { constructor( private readonly context: DatabaseContext, private readonly onWrite?: () => void | Promise, ) {} async create(apiKey: NewApiKeyRecord): Promise { const rows = await insertReturning(this.context, apiKeys, apiKey); await this.afterWrite(); return rows[0]; } async listAllWithUsers(): Promise { return this.context.drizzle .select({ id: apiKeys.id, name: apiKeys.name, userId: apiKeys.userId, username: users.username, tokenPrefix: apiKeys.tokenPrefix, createdAt: apiKeys.createdAt, expiresAt: apiKeys.expiresAt, lastUsedAt: apiKeys.lastUsedAt, isActive: apiKeys.isActive, scopes: apiKeys.scopes, }) .from(apiKeys) .leftJoin(users, eq(apiKeys.userId, users.id)) .orderBy(apiKeys.createdAt); } async findById(id: string): Promise { const rows = await this.context.drizzle .select() .from(apiKeys) .where(eq(apiKeys.id, id)) .limit(1); return rows[0] ?? null; } async listActiveByTokenPrefix(tokenPrefix: string): Promise { return this.context.drizzle .select() .from(apiKeys) .where( and(eq(apiKeys.tokenPrefix, tokenPrefix), eq(apiKeys.isActive, true)), ); } async updateLastUsedAt(id: string, lastUsedAt: string): Promise { await this.context.drizzle .update(apiKeys) .set({ lastUsedAt }) .where(eq(apiKeys.id, id)); await this.afterWrite(); } async delete(id: string): Promise { const rows = await deleteReturning( this.context, apiKeys, eq(apiKeys.id, id), ); await this.afterWrite(); return rows[0] ?? null; } async deleteByUserId(userId: string): Promise { const result = await this.context.drizzle .delete(apiKeys) .where(eq(apiKeys.userId, userId)); await this.afterWrite(); return rowsAffected(result); } private async afterWrite(): Promise { await this.onWrite?.(); } }