add error handling for db calls

This commit is contained in:
andres
2023-06-16 12:01:14 +02:00
parent 121fceb97a
commit 9cd6595ae2
11 changed files with 590 additions and 114 deletions

View File

@@ -5,7 +5,12 @@ import {
UserViewType,
VerificationWithUser,
} from '../../../types/types'
import { Injectable } from '@nestjs/common'
import {
BadRequestException,
Injectable,
InternalServerErrorException,
Logger,
} from '@nestjs/common'
import { addHours } from 'date-fns'
import { v4 as uuidv4 } from 'uuid'
import { PrismaService } from '../../../prisma.service'
@@ -16,157 +21,329 @@ import { Prisma } from '@prisma/client'
export class UsersRepository {
constructor(private prisma: PrismaService) {}
private readonly logger = new Logger(UsersRepository.name)
async getUsers(
currentPage: number,
itemsPerPage: number,
searchNameTerm: string,
searchEmailTerm: string
): Promise<EntityWithPaginationType<UserViewType>> {
const where = {
name: {
search: searchNameTerm || undefined,
},
email: {
search: searchEmailTerm || undefined,
},
}
const [totalItems, users] = await this.prisma.$transaction([
this.prisma.user.count({ where }),
this.prisma.user.findMany({
where,
skip: (currentPage - 1) * itemsPerPage,
take: itemsPerPage,
}),
])
const totalPages = Math.ceil(totalItems / itemsPerPage)
const usersView = users.map(u => pick(u, ['id', 'name', 'email', 'isEmailVerified']))
return {
totalPages,
currentPage,
itemsPerPage,
totalItems,
items: usersView,
try {
const where = {
name: {
search: searchNameTerm || undefined,
},
email: {
search: searchEmailTerm || undefined,
},
}
const [totalItems, users] = await this.prisma.$transaction([
this.prisma.user.count({ where }),
this.prisma.user.findMany({
where,
skip: (currentPage - 1) * itemsPerPage,
take: itemsPerPage,
}),
])
const totalPages = Math.ceil(totalItems / itemsPerPage)
const usersView = users.map(u => pick(u, ['id', 'name', 'email', 'isEmailVerified']))
return {
totalPages,
currentPage,
itemsPerPage,
totalItems,
items: usersView,
}
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2025') {
throw new BadRequestException({ message: 'Invalid page number', field: 'page' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async createUser(newUser: CreateUserInput): Promise<User | null> {
return await this.prisma.user.create({
data: {
email: newUser.email,
password: newUser.password,
name: newUser.name,
verification: {
create: {
verificationToken: newUser.verificationToken,
verificationTokenExpiry: newUser.verificationTokenExpiry,
try {
return await this.prisma.user.create({
data: {
email: newUser.email,
password: newUser.password,
name: newUser.name,
verification: {
create: {
verificationToken: newUser.verificationToken,
verificationTokenExpiry: newUser.verificationTokenExpiry,
},
},
},
},
include: {
verification: true,
},
})
include: {
verification: true,
},
})
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2002') {
throw new BadRequestException({ message: 'Email already exists', field: 'email' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async deleteUserById(id: string): Promise<boolean> {
const result = await this.prisma.user.delete({
where: {
id,
},
})
return result.isDeleted
try {
const result = await this.prisma.user.delete({
where: {
id,
},
})
return result.isDeleted
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2015') {
throw new BadRequestException({ message: 'User not found', field: 'id' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async deleteAllUsers(): Promise<boolean> {
const result = await this.prisma.user.deleteMany()
return result.count > 0
async deleteAllUsers(): Promise<number> {
try {
const result = await this.prisma.user.deleteMany()
return result.count
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async findUserById(id: string, include?: Prisma.UserInclude) {
const user = await this.prisma.user.findUnique({
where: { id },
include,
})
if (!user) {
return null
}
try {
const user = await this.prisma.user.findUnique({
where: { id },
include,
})
if (!user) {
return null
}
return user as Prisma.UserGetPayload<{ include: typeof include }>
return user as Prisma.UserGetPayload<{ include: typeof include }>
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2015') {
throw new BadRequestException({ message: 'User not found', field: 'id' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async findUserByEmail(email: string): Promise<User | null> {
const user = await this.prisma.user.findUnique({
where: { email },
include: { verification: true },
})
try {
const user = await this.prisma.user.findUnique({
where: { email },
include: { verification: true },
})
if (!user) {
return null
if (!user) {
return null
}
return user
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2015') {
throw new BadRequestException({ message: 'User not found', field: 'email' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
return user
}
async findUserByVerificationToken(token: string): Promise<VerificationWithUser | null> {
const verification = await this.prisma.verification.findUnique({
where: {
verificationToken: token,
},
include: {
user: true,
},
})
if (!verification) {
return null
try {
const verification = await this.prisma.verification.findUnique({
where: {
verificationToken: token,
},
include: {
user: true,
},
})
if (!verification) {
return null
}
return verification
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2015') {
throw new BadRequestException({ message: 'Verification not found', field: 'token' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
return verification
}
async updateEmailVerification(id: string) {
const result = await this.prisma.verification.update({
where: {
userId: id,
},
data: {
isEmailVerified: true,
user: {
update: {
isEmailVerified: true,
try {
const result = await this.prisma.verification.update({
where: {
userId: id,
},
data: {
isEmailVerified: true,
user: {
update: {
isEmailVerified: true,
},
},
},
},
})
return result.isEmailVerified
})
return result.isEmailVerified
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async updateVerificationToken(id: string) {
return await this.prisma.verification.update({
where: {
userId: id,
},
data: {
verificationToken: uuidv4(),
verificationTokenExpiry: addHours(new Date(), 24),
},
include: {
user: true,
},
})
try {
return await this.prisma.verification.update({
where: {
userId: id,
},
data: {
verificationToken: uuidv4(),
verificationTokenExpiry: addHours(new Date(), 24),
},
include: {
user: true,
},
})
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async createPasswordResetToken(id: string, token: string) {
try {
return await this.prisma.resetPassword.upsert({
where: {
userId: id,
},
update: {
resetPasswordToken: token,
resetPasswordTokenExpiry: addHours(new Date(), 1),
resetPasswordEmailsSent: {
increment: 1,
},
},
create: {
resetPasswordToken: token,
resetPasswordTokenExpiry: addHours(new Date(), 1),
resetPasswordEmailsSent: 1,
user: {
connect: {
id,
},
},
},
include: {
user: true,
},
})
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async findUserByPasswordResetToken(token: string): Promise<User | null> {
try {
const resetPassword = await this.prisma.resetPassword.findUnique({
where: {
resetPasswordToken: token,
},
include: {
user: true,
},
})
if (!resetPassword) {
return null
}
return resetPassword.user
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
if (e.code === 'P2015') {
throw new BadRequestException({ message: 'Invalid token', field: 'token' })
}
}
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async resetPasswordAndDeleteToken(userId: string, password: string) {
try {
return await this.prisma.$transaction([
this.prisma.resetPassword.update({
where: {
userId: userId,
},
data: {
resetPasswordToken: null,
resetPasswordTokenExpiry: null,
resetPasswordEmailsSent: {
increment: 1,
},
},
include: {
user: true,
},
}),
this.prisma.user.update({
where: {
id: userId,
},
data: {
password,
},
}),
])
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
}
async revokeToken(id: string, token: string): Promise<User | null> {
const revokedToken = await this.prisma.revokedToken.create({
data: {
token: token,
userId: id,
},
include: {
user: true,
},
})
if (!revokedToken.user) {
return null
try {
const revokedToken = await this.prisma.revokedToken.create({
data: {
token: token,
userId: id,
},
include: {
user: true,
},
})
if (!revokedToken.user) {
return null
}
return revokedToken.user
} catch (e) {
this.logger.error(e?.message || e)
throw new InternalServerErrorException(e)
}
return revokedToken.user
}
}

View File

@@ -21,8 +21,9 @@ export class UsersService {
return await this.usersRepository.deleteUserById(id)
}
async deleteAllUsers(): Promise<boolean> {
return await this.usersRepository.deleteAllUsers()
async deleteAllUsers(): Promise<{ deleted: number }> {
const deleted = await this.usersRepository.deleteAllUsers()
return { deleted }
}
public async sendConfirmationEmail({
@@ -42,6 +43,27 @@ export class UsersService {
html: `<b>Hello ${name}!</b><br/>Please confirm your email by clicking on the link below:<br/><a href="http://localhost:3000/confirm-email/${verificationToken}">Confirm email</a>`,
subject: 'E-mail confirmation',
})
} catch (e) {
this.logger.error(e?.message || e)
}
}
public async sendPasswordRecoveryEmail({
email,
name,
passwordRecoveryToken,
}: {
email: string
name: string
passwordRecoveryToken: string
}) {
try {
await this.emailService.sendMail({
from: 'Andrii <andrii@andrii.es>',
to: email,
html: `<b>Hello ${name}!</b><br/>To recover your password follow this link:<br/><a href="http://localhost:3000/confirm-email/${passwordRecoveryToken}">Confirm email</a>. If it doesn't work, copy and paste the following link in your browser:<br/>http://localhost:3000/confirm-email/${passwordRecoveryToken} `,
subject: 'Password recovery',
})
} catch (e) {
this.logger.error(e)
}