mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +00:00
Created new routes. Fixed small bug in the tests.
This commit is contained in:
parent
bb9532c265
commit
5057f58a12
20 changed files with 172 additions and 75 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,3 +0,0 @@
|
||||||
{
|
|
||||||
"json.schemaDownload.enable": true
|
|
||||||
}
|
|
|
@ -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 }))
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
17
src/controllers/users/update-name.ts
Normal file
17
src/controllers/users/update-name.ts
Normal 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
|
17
src/controllers/users/update-password.ts
Normal file
17
src/controllers/users/update-password.ts
Normal 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
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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'
|
||||||
|
|
42
src/services/users/update-email.ts
Normal file
42
src/services/users/update-email.ts
Normal 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
|
|
@ -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
|
56
src/services/users/update-password.ts
Normal file
56
src/services/users/update-password.ts
Normal 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
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in a new issue