chore: initial commit
This commit is contained in:
48
src/services/users/signup/__docs__/post.yaml
Normal file
48
src/services/users/signup/__docs__/post.yaml
Normal file
@ -0,0 +1,48 @@
|
||||
/users/signup:
|
||||
post:
|
||||
tags:
|
||||
- 'users'
|
||||
summary: 'Signup the user'
|
||||
description: 'Allows a new user to signup, if success he would need to confirm his email.'
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: 'object'
|
||||
properties:
|
||||
email:
|
||||
type: 'string'
|
||||
format: 'email'
|
||||
name:
|
||||
type: 'string'
|
||||
minLength: 3
|
||||
maxLength: 30
|
||||
example: 'user'
|
||||
password:
|
||||
type: 'string'
|
||||
format: 'password'
|
||||
example: 'password'
|
||||
language:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Language'
|
||||
theme:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Theme'
|
||||
required:
|
||||
- 'email'
|
||||
- 'name'
|
||||
- 'password'
|
||||
parameters:
|
||||
- name: 'redirectURI'
|
||||
description: 'The redirect URI to redirect the user when he successfuly confirm his email (could be a signin page), if not provided it will redirect the user to a simple page with a message to tell the user he can now signin.'
|
||||
in: 'query'
|
||||
required: false
|
||||
responses:
|
||||
allOf:
|
||||
- $ref: '#/definitions/BadRequestError'
|
||||
- '201':
|
||||
description: 'User created and send an email to confirm the account'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/definitions/User'
|
124
src/services/users/signup/__test__/post.test.ts
Normal file
124
src/services/users/signup/__test__/post.test.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import request from 'supertest'
|
||||
|
||||
import { formatErrors } from '../../../../__test__/utils/formatErrors'
|
||||
import application from '../../../../application'
|
||||
import User from '../../../../models/User'
|
||||
import { commonErrorsMessages } from '../../../../tools/configurations/constants'
|
||||
import { errorsMessages } from '../post'
|
||||
|
||||
describe('POST /users/signup', () => {
|
||||
it('succeeds and create a new user', async () => {
|
||||
let users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
|
||||
await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name: 'John',
|
||||
email: 'contact@test.com',
|
||||
password: 'test'
|
||||
})
|
||||
.expect(201)
|
||||
|
||||
users = await User.findAll()
|
||||
expect(users.length).toEqual(1)
|
||||
})
|
||||
|
||||
it('fails with invalid email', async () => {
|
||||
let users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
|
||||
const response = await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name: 'Divlo',
|
||||
email: 'incorrect@email',
|
||||
password: 'test'
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(response.body.errors.length).toEqual(1)
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
errorsMessages.email.mustBeValid
|
||||
)
|
||||
|
||||
users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('fails with invalid name', async () => {
|
||||
let users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
|
||||
const response = await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name: 'jo',
|
||||
email: 'contact@email.com',
|
||||
password: 'test'
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
expect(response.body.errors.length).toEqual(1)
|
||||
expect(response.body.errors[0].message).toBe(
|
||||
commonErrorsMessages.charactersLength('name', { max: 30, min: 3 })
|
||||
)
|
||||
|
||||
users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('fails with invalid name and invalid email', async () => {
|
||||
let users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
|
||||
const response = await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name: 'jo',
|
||||
email: 'contact@email',
|
||||
password: 'test'
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
const errors = formatErrors(response.body.errors)
|
||||
expect(errors.length).toEqual(2)
|
||||
expect(errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
commonErrorsMessages.charactersLength('name', { max: 30, min: 3 }),
|
||||
errorsMessages.email.mustBeValid
|
||||
])
|
||||
)
|
||||
|
||||
users = await User.findAll()
|
||||
expect(users.length).toEqual(0)
|
||||
})
|
||||
|
||||
it('fails with name and email already used', async () => {
|
||||
const name = 'John'
|
||||
const email = 'contact@test.com'
|
||||
await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name,
|
||||
email,
|
||||
password: 'test'
|
||||
})
|
||||
.expect(201)
|
||||
|
||||
const response = await request(application)
|
||||
.post('/users/signup')
|
||||
.send({
|
||||
name,
|
||||
email,
|
||||
password: 'test'
|
||||
})
|
||||
.expect(400)
|
||||
|
||||
const errors = formatErrors(response.body.errors)
|
||||
expect(errors.length).toEqual(2)
|
||||
expect(errors).toEqual(
|
||||
expect.arrayContaining(['Name already used', 'Email already used'])
|
||||
)
|
||||
})
|
||||
})
|
102
src/services/users/signup/post.ts
Normal file
102
src/services/users/signup/post.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import bcrypt from 'bcryptjs'
|
||||
import { Request, Response, Router } from 'express'
|
||||
import { body, query } from 'express-validator'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
import { validateRequest } from '../../../tools/middlewares/validateRequest'
|
||||
import User from '../../../models/User'
|
||||
import UserSetting, {
|
||||
Language,
|
||||
languages,
|
||||
Theme,
|
||||
themes
|
||||
} from '../../../models/UserSetting'
|
||||
import { commonErrorsMessages } from '../../../tools/configurations/constants'
|
||||
import { sendEmail } from '../../../tools/email/sendEmail'
|
||||
import { alreadyUsedValidation } from '../../../tools/validations/alreadyUsedValidation'
|
||||
import { onlyPossibleValuesValidation } from '../../../tools/validations/onlyPossibleValuesValidation'
|
||||
|
||||
export const errorsMessages = {
|
||||
email: {
|
||||
mustBeValid: 'Email must be valid'
|
||||
}
|
||||
}
|
||||
|
||||
export const signupRouter = Router()
|
||||
|
||||
signupRouter.post(
|
||||
'/users/signup',
|
||||
[
|
||||
body('email')
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.isEmail()
|
||||
.withMessage(errorsMessages.email.mustBeValid)
|
||||
.custom(async (email: string) => {
|
||||
return await alreadyUsedValidation(User, 'email', email)
|
||||
}),
|
||||
body('name')
|
||||
.trim()
|
||||
.notEmpty()
|
||||
.isLength({ max: 30, min: 3 })
|
||||
.withMessage(
|
||||
commonErrorsMessages.charactersLength('name', { max: 30, min: 3 })
|
||||
)
|
||||
.custom(async (name: string) => {
|
||||
return await alreadyUsedValidation(User, 'name', name)
|
||||
}),
|
||||
body('password').trim().notEmpty().isString(),
|
||||
body('theme')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.isString()
|
||||
.custom(async (theme: Theme) => {
|
||||
return await onlyPossibleValuesValidation([...themes], 'theme', theme)
|
||||
}),
|
||||
body('language')
|
||||
.optional({ nullable: true })
|
||||
.trim()
|
||||
.isString()
|
||||
.custom(async (language: Language) => {
|
||||
return await onlyPossibleValuesValidation(
|
||||
languages,
|
||||
'language',
|
||||
language
|
||||
)
|
||||
}),
|
||||
query('redirectURI').optional({ nullable: true }).trim()
|
||||
],
|
||||
validateRequest,
|
||||
async (req: Request, res: Response) => {
|
||||
const { name, email, password, theme, language } = req.body as {
|
||||
name: string
|
||||
email: string
|
||||
password: string
|
||||
theme?: Theme
|
||||
language?: Language
|
||||
}
|
||||
const { redirectURI } = req.query as { redirectURI?: string }
|
||||
const hashedPassword = await bcrypt.hash(password, 12)
|
||||
const tempToken = uuidv4()
|
||||
const user = await User.create({
|
||||
email,
|
||||
name,
|
||||
password: hashedPassword,
|
||||
tempToken
|
||||
})
|
||||
const userSettings = await UserSetting.create({
|
||||
userId: user.id,
|
||||
theme: theme ?? 'dark',
|
||||
language: language ?? 'en'
|
||||
})
|
||||
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
|
||||
})
|
||||
return res.status(201).json({ user })
|
||||
}
|
||||
)
|
Reference in New Issue
Block a user