mirror of
https://github.com/ershisan99/flashcards-api.git
synced 2026-01-05 12:34:26 +00:00
add error handling for db calls
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user