Created new routes. Fixed small bug in the tests.

This commit is contained in:
Hackntosh 2023-08-22 13:52:50 -03:00
parent bb9532c265
commit 5057f58a12
20 changed files with 172 additions and 75 deletions

View file

@ -1,3 +0,0 @@
{
"json.schemaDownload.enable": true
}

View file

@ -10,6 +10,9 @@ import router from './routes'
const app = express() const app = express()
// TODO: test socket io, emit notifications when create one. // TODO: test socket io, emit notifications when create one.
// TODO: test update name, email and password routes.
// TODO: start to create the client, or a barebone to test socket io
// TODO: see how we create authentication with socket io.
app.use(express.json()) app.use(express.json())
app.use(express.urlencoded({ extended: true })) app.use(express.urlencoded({ extended: true }))

View file

@ -29,6 +29,8 @@ usersRouter.put(
uploadFile, uploadFile,
user.uploadPicture user.uploadPicture
) )
usersRouter.put('/update', authenticated, user.update) usersRouter.put('/update-email', authenticated, user.updateEmail)
usersRouter.put('/update-name', authenticated, user.updateName)
usersRouter.put('/update-password', authenticated, user.updatePassword)
export default usersRouter export default usersRouter

View file

@ -7,7 +7,9 @@ import userLikeCommentController from './like-comment'
import userLikePostController from './like-post' import userLikePostController from './like-post'
import userSearchController from './search-user' import userSearchController from './search-user'
import userSignupController from './signup' import userSignupController from './signup'
import userUpdateController from './update' import userUpdateEmailController from './update-email'
import userUpdateNameController from './update-name'
import userUpdatePasswordController from './update-password'
import userUploadPictureController from './upload-picture' import userUploadPictureController from './upload-picture'
const user = { const user = {
@ -20,7 +22,9 @@ const user = {
likePost: userLikePostController, likePost: userLikePostController,
searchUser: userSearchController, searchUser: userSearchController,
signup: userSignupController, signup: userSignupController,
update: userUpdateController, updateEmail: userUpdateEmailController,
updateName: userUpdateNameController,
updatePassword: userUpdatePasswordController,
uploadPicture: userUploadPictureController uploadPicture: userUploadPictureController
} as const } as const

View file

@ -2,16 +2,16 @@ import user from 'services/users'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import handleResponse from 'helpers/handle-response' import handleResponse from 'helpers/handle-response'
async function userUpdateController ( async function userUpdateEmailController (
req: Request, req: Request,
res: Response res: Response
): Promise<void> { ): Promise<void> {
const { email, displayName, username } = req.body const { email } = req.body
const id = req.user?.id ?? '' const id = req.user?.id ?? ''
const result = await user.update({ id, email, displayName, username }) const result = await user.updateEmail({ id, email })
handleResponse(res, result) handleResponse(res, result)
} }
export default userUpdateController export default userUpdateEmailController

View file

@ -0,0 +1,17 @@
import user from 'services/users'
import type { Request, Response } from 'express'
import handleResponse from 'helpers/handle-response'
async function userUpdateNameController (
req: Request,
res: Response
): Promise<void> {
const { displayName, username } = req.body
const id = req.user?.id ?? ''
const result = await user.updateName({ id, displayName, username })
handleResponse(res, result)
}
export default userUpdateNameController

View file

@ -0,0 +1,17 @@
import user from 'services/users'
import type { Request, Response } from 'express'
import handleResponse from 'helpers/handle-response'
async function userUpdatePasswordController (
req: Request,
res: Response
): Promise<void> {
const { currentPassword, newPassword } = req.body
const id = req.user?.id ?? ''
const result = await user.updatePassword(id, currentPassword, newPassword)
handleResponse(res, result)
}
export default userUpdatePasswordController

View file

@ -7,7 +7,9 @@ import userLikeCommentService from './like-comment'
import userLikePostService from './like-post' import userLikePostService from './like-post'
import userSearchService from './search-user' import userSearchService from './search-user'
import userSignupService from './signup' import userSignupService from './signup'
import userUpdateService from './update' import userUpdateEmailService from './update-email'
import userUpdateNameService from './update-name'
import userUpdatePasswordService from './update-password'
import userUploadPictureService from './upload-picture' import userUploadPictureService from './upload-picture'
const user = { const user = {
@ -20,7 +22,9 @@ const user = {
likePost: userLikePostService, likePost: userLikePostService,
searchUser: userSearchService, searchUser: userSearchService,
signup: userSignupService, signup: userSignupService,
update: userUpdateService, updateEmail: userUpdateEmailService,
updateName: userUpdateNameService,
updatePassword: userUpdatePasswordService,
uploadPicture: userUploadPictureService uploadPicture: userUploadPictureService
} as const } as const

View file

@ -21,10 +21,6 @@ async function userSignupService ({
) )
} }
if (password.trim().length < 8) {
return new Error('Password too short')
}
if (!usernameRegex.test(username)) { if (!usernameRegex.test(username)) {
return new Error( return new Error(
'Username not allowed. Only alphanumerics characters (uppercase and lowercase words), underscore, dots and it must be between 5 and 15 characters' 'Username not allowed. Only alphanumerics characters (uppercase and lowercase words), underscore, dots and it must be between 5 and 15 characters'

View file

@ -0,0 +1,42 @@
import type User from 'interfaces/user'
import prisma from 'clients/prisma-client'
async function userUpdateEmailService ({
id,
email
}: User): Promise<Record<string, unknown> | Error> {
const user = await prisma.user.findFirst({ where: { id } })
if (user === null) {
return new Error('User not found')
}
if (user.id !== id) {
return new Error('Forbidden')
}
if (email !== undefined && email.trim() !== user.email) {
const existingUser = await prisma.user.findFirst({ where: { email } })
if (existingUser != null && existingUser.email !== user.email) {
return new Error('Email already in use')
}
}
await prisma.user.update({
where: {
id
},
data: {
email: email ?? user.email
},
select: {
displayName: true,
username: true,
createdAt: true
}
})
return { message: 'Successfully updated user email' }
}
export default userUpdateEmailService

View file

@ -1,9 +1,8 @@
import type User from 'interfaces/user' import type User from 'interfaces/user'
import prisma from 'clients/prisma-client' import prisma from 'clients/prisma-client'
async function userUpdateService ({ async function userUpdateNameService ({
id, id,
email,
displayName, displayName,
username username
}: User): Promise<Record<string, unknown> | Error> { }: User): Promise<Record<string, unknown> | Error> {
@ -13,15 +12,8 @@ async function userUpdateService ({
return new Error('User not found') return new Error('User not found')
} }
// Check if the provided email or username is different from the current user's data // Check if the provided username is different from the current user's data
// if they are different, additional queries are made to check if the new email or username is already in use. // if different, queries are made to check if the new username is already in use.
if (email !== undefined && email.trim() !== user.email) {
const existingUser = await prisma.user.findFirst({ where: { email } })
if (existingUser != null && existingUser.email !== user.email) {
return new Error('Email already in use')
}
}
if (username !== undefined && username.trim() !== user.username) { if (username !== undefined && username.trim() !== user.username) {
const existingUser = await prisma.user.findFirst({ where: { username } }) const existingUser = await prisma.user.findFirst({ where: { username } })
@ -34,14 +26,11 @@ async function userUpdateService ({
return new Error('Forbidden') return new Error('Forbidden')
} }
// TODO: /user/change-password | /user/change-email
const updatedUser = await prisma.user.update({ const updatedUser = await prisma.user.update({
where: { where: {
id id
}, },
data: { data: {
email: email ?? user.email,
displayName: displayName ?? user.displayName, displayName: displayName ?? user.displayName,
username: username ?? user.username username: username ?? user.username
}, },
@ -55,4 +44,4 @@ async function userUpdateService ({
return updatedUser return updatedUser
} }
export default userUpdateService export default userUpdateNameService

View file

@ -0,0 +1,56 @@
import * as bcrypt from 'bcrypt'
import prisma from 'clients/prisma-client'
const passwordRegex = /^(?=.*[0-9])(?=.*[!@#$%^&*_])[a-zA-Z0-9!@#$%^&*_]{8,}$/
async function userUpdatePasswordService (
id: string,
currentPassword: string,
newPassword: string
): Promise<Record<string, unknown> | Error> {
if (!passwordRegex.test(newPassword)) {
return new Error(
'New password must have at least 8 characters, one number and one special character.'
)
}
const user = await prisma.user.findFirst({ where: { id } })
if (user === null) {
return new Error('User not found')
}
if (user.id !== id) {
return new Error('Forbidden')
}
const validPassword = await bcrypt.compare(
currentPassword.replace(/ /g, ''),
user.password
)
if (!validPassword) {
return new Error('Invalid password')
}
const salt = await bcrypt.genSalt(15)
const hashedPassword = await bcrypt.hash(newPassword.replace(/ /g, ''), salt)
await prisma.user.update({
where: {
id
},
data: {
password: hashedPassword
},
select: {
displayName: true,
username: true,
createdAt: true
}
})
return { message: 'Successfully updated user password' }
}
export default userUpdatePasswordService

View file

@ -12,7 +12,8 @@ describe('POST /post/create', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should respond with 200 status code if the user send the token and the content', async () => { it('should respond with 200 status code if the user send the token and the content', async () => {

View file

@ -12,7 +12,8 @@ describe('DELETE /post/delete', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should delete the post successfully', async () => { it('should delete the post successfully', async () => {

View file

@ -26,7 +26,8 @@ describe('POST /post/info', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should respond with 200 status code and return some info about the post', async () => { it('should respond with 200 status code and return some info about the post', async () => {

View file

@ -12,7 +12,8 @@ describe('PUT /post/update', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should create a new post and update the content of it', async () => { it('should create a new post and update the content of it', async () => {

View file

@ -12,7 +12,8 @@ describe('POST /user/auth', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should respond with a error if the user does not exists', async () => { it('should respond with a error if the user does not exists', async () => {

View file

@ -12,7 +12,8 @@ describe('POST /user/info', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should respond with 200 status code and return the user data', async () => { it('should respond with 200 status code and return the user data', async () => {

View file

@ -13,7 +13,8 @@ describe('POST /user/signup', () => {
}) })
afterAll(async () => { afterAll(async () => {
await deleteUser(user.username ?? '') // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await deleteUser(user.username!)
}) })
it('should respond with a 400 status code if sent any invalid data', async () => { it('should respond with a 400 status code if sent any invalid data', async () => {

View file

@ -1,37 +0,0 @@
import request from 'supertest'
import app from '../../app'
import signUpNewUser from '../utils/create-user'
import deleteUser from '../utils/delete-user'
import type User from 'interfaces/user'
let user: User
describe('PUT /user/update', () => {
beforeAll(async () => {
user = await signUpNewUser()
})
afterAll(async () => {
await deleteUser(user.username ?? '')
})
it('should update the user successfully', async () => {
const fieldsToUpdate = {
displayName: 'Cookie'
}
const response = await request(app)
.put('/user/update')
.send(fieldsToUpdate)
.set('Authorization', `Bearer ${user.token ?? ''}`)
.expect(200)
expect(response.body).toEqual(
expect.objectContaining({
displayName: expect.any(String),
username: expect.any(String),
createdAt: expect.any(String)
})
)
})
})