feat: migrate from express to fastify
This commit is contained in:
@ -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 })
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user