feat: migrate from express to fastify

This commit is contained in:
Divlo
2021-10-24 04:18:18 +02:00
parent 714cc643ba
commit b77e602358
281 changed files with 19768 additions and 22895 deletions

View File

@ -1,141 +1,136 @@
import { Request, Response, Router } from 'express'
import fileUpload from 'express-fileupload'
import { body, query } from 'express-validator'
import { v4 as uuidv4 } from 'uuid'
import { randomUUID } from 'node:crypto'
import { authenticateUser } from '../../../tools/middlewares/authenticateUser'
import { validateRequest } from '../../../tools/middlewares/validateRequest'
import User from '../../../models/User'
import {
commonErrorsMessages,
imageFileUploadOptions,
usersLogoPath
} from '../../../tools/configurations/constants'
import { alreadyUsedValidation } from '../../../tools/validations/alreadyUsedValidation'
import { ForbiddenError } from '../../../tools/errors/ForbiddenError'
import { uploadImage } from '../../../tools/utils/uploadImage'
import { deleteEveryRefreshTokens } from '../__utils__/deleteEveryRefreshTokens'
import UserSetting from '../../../models/UserSetting'
import { sendEmail } from '../../../tools/email/sendEmail'
import { Static, Type } from '@sinclair/typebox'
import { FastifyPluginAsync, FastifySchema } from 'fastify'
export const errorsMessages = {
email: {
mustBeValid: 'Email must be valid',
alreadyConnected: 'You are already connected with this email address'
},
name: {
alreadyConnected: 'You are already connected with this name'
import prisma from '../../../tools/database/prisma.js'
import { fastifyErrors } from '../../../models/utils.js'
import authenticateUser from '../../../tools/plugins/authenticateUser.js'
import { userCurrentSchema, userSchema } from '../../../models/User.js'
import { sendEmail } from '../../../tools/email/sendEmail.js'
import { HOST, PORT } from '../../../tools/configurations/index.js'
import { Language, Theme } from '../../../models/UserSettings.js'
const bodyPutServiceSchema = Type.Object({
name: Type.Optional(userSchema.name),
email: Type.Optional(userSchema.email),
status: Type.Optional(userSchema.status),
biography: Type.Optional(userSchema.biography),
website: Type.Optional(userSchema.website)
})
type BodyPutServiceSchemaType = Static<typeof bodyPutServiceSchema>
const queryPutCurrentUserSchema = Type.Object({
redirectURI: Type.Optional(Type.String({ format: 'uri-reference' }))
})
type QueryPutCurrentUserSchemaType = Static<typeof queryPutCurrentUserSchema>
const putServiceSchema: FastifySchema = {
description: 'Edit the current connected user information',
tags: ['users'] as string[],
security: [
{
bearerAuth: []
}
] as Array<{ [key: string]: [] }>,
body: bodyPutServiceSchema,
querystring: queryPutCurrentUserSchema,
response: {
200: userCurrentSchema,
400: fastifyErrors[400],
401: fastifyErrors[401],
403: fastifyErrors[403],
500: fastifyErrors[500]
}
}
} as const
export const putCurrentRouter = Router()
export const putCurrentUser: FastifyPluginAsync = async (fastify) => {
await fastify.register(authenticateUser)
putCurrentRouter.put(
'/users/current',
authenticateUser,
fileUpload(imageFileUploadOptions),
[
body('email')
.optional({ nullable: true })
.trim()
.isEmail()
.withMessage(errorsMessages.email.mustBeValid)
.custom(async (email: string, meta) => {
if (email === meta.req.user?.current.email) {
return await Promise.reject(
new Error(errorsMessages.email.alreadyConnected)
)
}
return await alreadyUsedValidation(User, 'email', email)
}),
body('name')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: 30, min: 3 })
.withMessage(
commonErrorsMessages.charactersLength('name', { max: 30, min: 3 })
)
.custom(async (name: string, meta) => {
if (name === meta.req.user?.current.name) {
return await Promise.reject(
new Error(errorsMessages.name.alreadyConnected)
)
}
return await alreadyUsedValidation(User, 'name', name)
}),
body('biography')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: 160 })
.withMessage(
commonErrorsMessages.charactersLength('biography', { max: 160 })
),
body('status')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: 100 })
.withMessage(
commonErrorsMessages.charactersLength('status', { max: 100 })
),
query('redirectURI').optional({ nullable: true }).trim()
],
validateRequest,
async (req: Request, res: Response) => {
if (req.user == null) {
throw new ForbiddenError()
}
const { name, email, status, biography } = req.body as {
name?: string
email?: string
status?: string
biography?: string
}
const logo = req.files?.logo
const { redirectURI } = req.query as { redirectURI?: string }
const user = req.user.current
user.name = name ?? user.name
user.status = status ?? user.status
user.biography = biography ?? user.biography
const resultUpload = await uploadImage({
image: logo,
propertyName: 'logo',
oldImage: user.logo,
imagesPath: usersLogoPath.filePath
})
if (resultUpload != null) {
user.logo = `${usersLogoPath.name}/${resultUpload}`
}
if (email != null) {
user.email = email
if (req.user.currentStrategy === 'local') {
await deleteEveryRefreshTokens(user.id)
fastify.route<{
Body: BodyPutServiceSchemaType
Params: QueryPutCurrentUserSchemaType
}>({
method: 'PUT',
url: '/users/current',
schema: putServiceSchema,
handler: async (request, reply) => {
if (request.user == null) {
throw fastify.httpErrors.forbidden()
}
const tempToken = uuidv4()
user.tempToken = tempToken
user.isConfirmed = false
const userSettings = await UserSetting.findOne({
where: { userId: user.id }
const { name, email, status, biography, website } = request.body
const { redirectURI } = request.params
const userValidation = await prisma.user.findFirst({
where: {
OR: [{ email }, { name }],
AND: [{ id: { not: request.user.current.id } }]
}
})
const redirectQuery =
redirectURI != null ? `&redirectURI=${redirectURI}` : ''
await sendEmail({
type: 'confirm-email',
email,
url: `${process.env.API_BASE_URL}/users/confirmEmail?tempToken=${tempToken}${redirectQuery}`,
language: userSettings?.language,
theme: userSettings?.theme
if (userValidation != null) {
throw fastify.httpErrors.badRequest(
'body.email or body.name already taken.'
)
}
const settings = await prisma.userSetting.findFirst({
where: { userId: request.user.current.id }
})
if (settings == null) {
throw fastify.httpErrors.internalServerError()
}
const OAuths = await prisma.oAuth.findMany({
where: { userId: request.user.current.id }
})
const strategies = OAuths.map((oauth) => {
return oauth.provider
})
if (request.user.current.password != null) {
strategies.push('local')
}
if (email != null) {
await prisma.refreshToken.deleteMany({
where: {
userId: request.user.current.id
}
})
const temporaryToken = randomUUID()
const redirectQuery =
redirectURI != null ? `&redirectURI=${redirectURI}` : ''
await sendEmail({
type: 'confirm-email',
email,
url: `${request.protocol}://${HOST}:${PORT}/users/confirm-email?temporaryToken=${temporaryToken}${redirectQuery}`,
language: settings.language as Language,
theme: settings.theme as Theme
})
await prisma.user.update({
where: { id: request.user.current.id },
data: {
email,
temporaryToken,
isConfirmed: false
}
})
}
const user = await prisma.user.update({
where: { id: request.user.current.id },
data: {
name: name ?? request.user.current.name,
status: status ?? request.user.current.status,
biography: biography ?? request.user.current.biography,
website: website ?? request.user.current.website
}
})
reply.statusCode = 200
return {
user: {
...user,
settings,
currentStrategy: request.user.currentStrategy,
strategies
}
}
}
const userSaved = await user.save()
return res
.status(200)
.json({ user: userSaved, strategy: req.user.currentStrategy })
}
)
})
}