From e527c94752f8f00255cc7753184a16718206787b Mon Sep 17 00:00:00 2001 From: MaxymBeregovoi <108676512+maksberegovoi@users.noreply.github.com> Date: Sat, 7 Mar 2026 17:47:20 +0200 Subject: [PATCH] feat: add prisma exception helper - update prisma exceptions logic in user, auth modules --- .../src/modules/auth/auth.controller.ts | 3 +- apps/backend/src/modules/auth/auth.service.ts | 25 ++++++----- .../modules/auth/strategies/jwt.strategy.ts | 9 +++- apps/backend/src/modules/user/user.service.ts | 38 ++++++++--------- .../helpers/handle-prisma-error.helper.ts | 41 +++++++++++++++++++ 5 files changed, 82 insertions(+), 34 deletions(-) create mode 100644 apps/backend/src/shared/helpers/handle-prisma-error.helper.ts diff --git a/apps/backend/src/modules/auth/auth.controller.ts b/apps/backend/src/modules/auth/auth.controller.ts index d916fea..240a65e 100644 --- a/apps/backend/src/modules/auth/auth.controller.ts +++ b/apps/backend/src/modules/auth/auth.controller.ts @@ -51,7 +51,8 @@ export class AuthController { @UseGuards(JwtAuthGuard) @Get('me') async me(@Req() req: Request & { user: JwtPayload }): Promise> { - const user = await this.authService.me(req.user); + const { email } = req.user; + const user = await this.authService.me(email); return { data: user }; } diff --git a/apps/backend/src/modules/auth/auth.service.ts b/apps/backend/src/modules/auth/auth.service.ts index ae599a2..0a0dcf5 100644 --- a/apps/backend/src/modules/auth/auth.service.ts +++ b/apps/backend/src/modules/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { ConflictException, Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { UserDto } from '../user/dto/user.dto'; import bcrypt from 'bcrypt'; import { CreateUserDto } from './dto/create-user.dto'; @@ -15,7 +15,7 @@ export class AuthService { ) {} async validateUser(email: string, password: string): Promise { - const user = await this.userService.findByEmail(email); + const user = await this.userService.findByEmailOrNull(email); if (!user) return null; @@ -32,13 +32,7 @@ export class AuthService { } async registration(createUserDto: CreateUserDto) { - const existing = await this.userService.findByEmail(createUserDto.email); - if (existing) { - throw new ConflictException('Email in use'); - } - const hashedPassword = await hashPassword(createUserDto.password); - const user = await this.userService.create({ ...createUserDto, password: hashedPassword, @@ -59,8 +53,17 @@ export class AuthService { }; } - async me(user: JwtPayload): Promise { - const { sub: id } = user; - return await this.userService.findById(id); + async me(email: string): Promise { + const user = await this.userService.findByEmailOrNull(email); + if (!user) { + throw new UnauthorizedException('Not authorized'); + } + + return { + id: user.id, + email: user.email, + name: user.name, + role: user.role, + }; } } diff --git a/apps/backend/src/modules/auth/strategies/jwt.strategy.ts b/apps/backend/src/modules/auth/strategies/jwt.strategy.ts index bb39c96..37d95a5 100644 --- a/apps/backend/src/modules/auth/strategies/jwt.strategy.ts +++ b/apps/backend/src/modules/auth/strategies/jwt.strategy.ts @@ -1,12 +1,13 @@ import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { env } from '../../../env'; -import { Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { JwtPayload } from '../types/jwt-payload.type'; +import { UserService } from '../../user/user.service'; @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor() { + constructor(private readonly userService: UserService) { super({ jwtFromRequest: ExtractJwt.fromExtractors([(req) => req?.cookies?.access_token]), secretOrKey: env.JWT_SECRET, @@ -14,6 +15,10 @@ export class JwtStrategy extends PassportStrategy(Strategy) { } async validate(payload: JwtPayload): Promise { + const user = await this.userService.findByEmailOrNull(payload.email); + if (!user) { + throw new UnauthorizedException('Not authorized'); + } return payload; } } diff --git a/apps/backend/src/modules/user/user.service.ts b/apps/backend/src/modules/user/user.service.ts index 02d74ca..2a12da6 100644 --- a/apps/backend/src/modules/user/user.service.ts +++ b/apps/backend/src/modules/user/user.service.ts @@ -2,9 +2,9 @@ import { Injectable, NotFoundException } from '@nestjs/common'; import { UpdateUserDto } from './dto/update-user.dto'; import { PrismaService } from '../../prisma/prisma.service'; import { UserDto } from './dto/user.dto'; -import { Prisma } from '@prisma/client'; import { CreateUserDto } from '../auth/dto/create-user.dto'; import { User } from '@prisma/client'; +import { handlePrismaError } from '../../shared/helpers/handle-prisma-error.helper'; @Injectable() export class UserService { @@ -39,23 +39,27 @@ export class UserService { }; } - async findByEmail(email: string): Promise { - return await this.prisma.user.findUnique({ + async findByEmailOrNull(email: string): Promise { + return this.prisma.user.findUnique({ where: { email }, }); } async create(createUserDto: CreateUserDto): Promise { - const user = await this.prisma.user.create({ - data: createUserDto, - }); + try { + const user = await this.prisma.user.create({ + data: createUserDto, + }); - return { - id: user.id, - name: user.name, - email: user.email, - role: user.role, - }; + return { + id: user.id, + name: user.name, + email: user.email, + role: user.role, + }; + } catch (error) { + handlePrismaError(error, { UNIQUE_CONSTRAINT: 'User with this email already exists' }); + } } async update(id: string, updateUserDto: UpdateUserDto): Promise { @@ -72,10 +76,7 @@ export class UserService { role: updatedUser.role, }; } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025') { - throw new NotFoundException('User not found'); - } - throw error; + handlePrismaError(error, { NOT_FOUND: 'User not found' }); } } @@ -85,10 +86,7 @@ export class UserService { where: { id }, }); } catch (error) { - if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025') { - throw new NotFoundException('User not found'); - } - throw error; + handlePrismaError(error, { NOT_FOUND: 'User not found' }); } } } diff --git a/apps/backend/src/shared/helpers/handle-prisma-error.helper.ts b/apps/backend/src/shared/helpers/handle-prisma-error.helper.ts new file mode 100644 index 0000000..24c466a --- /dev/null +++ b/apps/backend/src/shared/helpers/handle-prisma-error.helper.ts @@ -0,0 +1,41 @@ +import { Prisma } from '@prisma/client'; +import { BadRequestException, ConflictException, NotFoundException } from '@nestjs/common'; + +type PrismaErrorMessages = { + NOT_FOUND?: string; + UNIQUE_CONSTRAINT?: string; + FOREIGN_KEY?: string; + VALUE_TOO_LONG?: string; +}; + +export function handlePrismaError(error: unknown, messages: PrismaErrorMessages): never { + if (error instanceof Prisma.PrismaClientKnownRequestError) { + switch (error.code) { + case 'P2025': + if (messages.NOT_FOUND) { + throw new NotFoundException(messages.NOT_FOUND ?? 'Not found'); + } + break; + + case 'P2002': + if (messages.UNIQUE_CONSTRAINT) { + throw new ConflictException(messages.UNIQUE_CONSTRAINT ?? 'Unique constraint failed'); + } + break; + + case 'P2003': + if (messages.FOREIGN_KEY) { + throw new ConflictException(messages.FOREIGN_KEY ?? 'Foreign key constraint failed'); + } + break; + + case 'P2000': + if (messages.VALUE_TOO_LONG) { + throw new BadRequestException(messages.VALUE_TOO_LONG ?? 'Value too long'); + } + break; + } + } + + throw error; +}