import { Injectable } from '@nestjs/common'; import { isPrismaErrorCode, PrismaErrorCode } from '#src/prisma/prisma-error-code'; import { PrismaService } from '#src/prisma/prisma.service'; const EXPECTED_ROTATED_SESSIONS = 1; @Injectable() export class AuthRepository { constructor(private readonly prisma: PrismaService) {} findUserByEmail(email: string) { return this.prisma.user.findUnique({ where: { email } }); } async createUser(data: { email: string; id: string; passwordHash: string }) { try { return await this.prisma.user.create({ data }); } catch (error) { if (isUniqueConstraintError(error)) return null; throw error; } } findUserById(id: string) { return this.prisma.user.findUnique({ where: { id } }); } findSessionWithUser(id: string) { return this.prisma.session.findUnique({ where: { id }, include: { user: true }, }); } findSession(id: string) { return this.prisma.session.findUnique({ where: { id } }); } deleteSession(id: string) { return this.prisma.session.delete({ where: { id } }); } revokeSession(id: string) { return this.prisma.session.update({ where: { id }, data: { revokedAt: new Date() }, }); } createSession(userId: string, id: string) { return this.prisma.session.create({ data: { id, userId, refreshTokenHash: '', expiresAt: new Date() }, }); } async rotateSession( id: string, data: { expiresAt: Date; refreshTokenHash: string }, expectedRefreshTokenHash?: string, ): Promise { if (expectedRefreshTokenHash) { const updated = await this.prisma.session.updateMany({ where: { id, refreshTokenHash: expectedRefreshTokenHash }, data, }); return updated.count === EXPECTED_ROTATED_SESSIONS; } await this.prisma.session.update({ where: { id }, data }); return true; } } function isUniqueConstraintError(error: unknown): boolean { return ( typeof error === 'object' && error !== null && 'code' in error && isPrismaErrorCode(error.code, PrismaErrorCode.UniqueConstraint) ); }