chore: initial commit

This commit is contained in:
Divlo
2021-10-24 04:06:16 +02:00
commit 714cc643ba
260 changed files with 40783 additions and 0 deletions

View File

@ -0,0 +1,25 @@
/invitations/{invitationId}:
delete:
security:
- bearerAuth: []
tags:
- 'invitations'
summary: 'DELETE an invitation with its id'
parameters:
- name: 'invitationId'
in: 'path'
required: true
responses:
allOf:
- $ref: '#/definitions/UnauthorizedError'
- $ref: '#/definitions/ForbiddenError'
- $ref: '#/definitions/BadRequestError'
- $ref: '#/definitions/NotFoundError'
- '200':
content:
application/json:
schema:
type: 'object'
properties:
deletedInvitationId:
type: 'number'

View File

@ -0,0 +1,40 @@
/invitations/{invitationId}:
put:
security:
- bearerAuth: []
tags:
- 'invitations'
summary: 'Update an invitation with its id'
parameters:
- name: 'invitationId'
in: 'path'
required: true
requestBody:
content:
application/json:
schema:
type: 'object'
properties:
value:
type: 'string'
minLength: 1
maxLength: 250
expiresIn:
type: 'integer'
isPublic:
type: 'boolean'
responses:
allOf:
- $ref: '#/definitions/UnauthorizedError'
- $ref: '#/definitions/ForbiddenError'
- $ref: '#/definitions/BadRequestError'
- $ref: '#/definitions/NotFoundError'
- '200':
content:
application/json:
schema:
type: 'object'
properties:
invitation:
allOf:
- $ref: '#/definitions/Invitation'

View File

@ -0,0 +1,48 @@
import request from 'supertest'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUser'
import { formatErrors } from '../../../../__test__/utils/formatErrors'
import application from '../../../../application'
import { createInvitation } from '../../__test__/utils/createInvitation'
import Invitation from '../../../../models/Invitation'
describe('DELETE /invitations/:invitationId', () => {
it('succeeds and delete the invitation', async () => {
const result = await createInvitation()
const response = await request(application)
.delete(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${result?.user.type as string} ${result?.user.accessToken as string}`)
.send()
.expect(200)
expect(response.body.deletedInvitationId).toEqual(result?.invitation.id)
const foundInvitation = await Invitation.findOne({
where: { id: result?.invitation.id }
})
expect(foundInvitation).toBeNull()
})
it("fails if the invitation doesn't exist", async () => {
const userToken = await authenticateUserTest()
const response = await request(application)
.delete('/invitations/23')
.set('Authorization', `${userToken.type as string} ${userToken.accessToken}`)
.send()
.expect(404)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining(['Not Found']))
})
it('fails if the user is not the owner', async () => {
const userToken = await authenticateUserTest()
const result = await createInvitation()
const response = await request(application)
.delete(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${userToken.type as string} ${userToken.accessToken}`)
.send()
.expect(404)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining(['Not Found']))
})
})

View File

@ -0,0 +1,93 @@
import request from 'supertest'
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUser'
import { formatErrors } from '../../../../__test__/utils/formatErrors'
import application from '../../../../application'
import { createInvitation } from '../../__test__/utils/createInvitation'
import { errorsMessages } from '../put'
describe('PUT /invitations/:invitationId', () => {
it('succeeds and edit the invitation', async () => {
let value = 'random'
let expiresIn = 0
let isPublic = false
const result = await createInvitation({ value, expiresIn, isPublic })
expect(result?.invitation.value).toEqual(value)
expect(result?.invitation.expiresIn).toEqual(expiresIn)
expect(result?.invitation.isPublic).toEqual(isPublic)
value = 'awesome'
expiresIn = 60
isPublic = true
const response = await request(application)
.put(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${result?.user.type as string} ${result?.user.accessToken as string}`)
.send({ value, expiresIn, isPublic })
.expect(200)
expect(response.body.invitation.value).toEqual(value)
expect(response.body.invitation.isPublic).toEqual(isPublic)
})
it('fails with invalid slug value', async () => {
const result = await createInvitation()
const response = await request(application)
.put(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${result?.user.type as string} ${result?.user.accessToken as string}`)
.send({ value: 'random invitation value' })
.expect(400)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining([errorsMessages.value.mustBeSlug]))
})
it('fails with negative expiresIn', async () => {
const result = await createInvitation()
const response = await request(application)
.put(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${result?.user.type as string} ${result?.user.accessToken as string}`)
.send({ expiresIn: -42 })
.expect(400)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining([errorsMessages.expiresIn.mustBeGreaterOrEqual]))
})
it("fails if the invitation doesn't exist", async () => {
const userToken = await authenticateUserTest()
const response = await request(application)
.put('/invitations/23')
.set('Authorization', `${userToken.type as string} ${userToken.accessToken}`)
.send()
.expect(404)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining(['Not Found']))
})
it('fails if the invitation slug value already exists', async () => {
const value = 'random'
const result = await createInvitation({ value })
const response = await request(application)
.put(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${result?.user.type as string} ${result?.user.accessToken as string}`)
.send({ value })
.expect(400)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(
expect.arrayContaining(['Value already used'])
)
})
it('fails if the user is not the owner', async () => {
const userToken = await authenticateUserTest()
const result = await createInvitation()
const response = await request(application)
.put(`/invitations/${result?.invitation.id as number}`)
.set('Authorization', `${userToken.type as string} ${userToken.accessToken}`)
.send({ value: 'somevalue' })
.expect(404)
const errors = formatErrors(response.body.errors)
expect(errors.length).toEqual(1)
expect(errors).toEqual(expect.arrayContaining(['Not Found']))
})
})

View File

@ -0,0 +1,40 @@
import { Request, Response, Router } from 'express'
import { authenticateUser } from '../../../tools/middlewares/authenticateUser'
import Guild from '../../../models/Guild'
import Invitation from '../../../models/Invitation'
import Member from '../../../models/Member'
import { BadRequestError } from '../../../tools/errors/BadRequestError'
import { ForbiddenError } from '../../../tools/errors/ForbiddenError'
import { NotFoundError } from '../../../tools/errors/NotFoundError'
export const deleteByIdInvitationsRouter = Router()
deleteByIdInvitationsRouter.delete(
'/invitations/:invitationId',
authenticateUser,
async (req: Request, res: Response) => {
if (req.user == null) {
throw new ForbiddenError()
}
const user = req.user.current
const { invitationId } = req.params as { invitationId: string }
const invitation = await Invitation.findOne({ where: { id: invitationId } })
if (invitation == null) {
throw new NotFoundError()
}
const member = await Member.findOne({
where: { userId: user.id, guildId: invitation.guildId, isOwner: true },
include: [Guild]
})
if (member == null) {
throw new NotFoundError()
}
if (member.guild.isPublic && invitation.isPublic) {
throw new BadRequestError("You can't delete the public invitation")
}
const deletedInvitationId = invitation.id
await invitation.destroy()
return res.status(200).json({ deletedInvitationId })
}
)

View File

@ -0,0 +1,9 @@
import { Router } from 'express'
import { deleteByIdInvitationsRouter } from './delete'
import { putInvitationsRouter } from './put'
export const invitationsGetByIdRouter = Router()
invitationsGetByIdRouter.use('/', deleteByIdInvitationsRouter)
invitationsGetByIdRouter.use('/', putInvitationsRouter)

View File

@ -0,0 +1,97 @@
import { Request, Response, Router } from 'express'
import { body } from 'express-validator'
import { authenticateUser } from '../../../tools/middlewares/authenticateUser'
import { validateRequest } from '../../../tools/middlewares/validateRequest'
import Invitation from '../../../models/Invitation'
import Member from '../../../models/Member'
import { commonErrorsMessages } from '../../../tools/configurations/constants'
import { alreadyUsedValidation } from '../../../tools/validations/alreadyUsedValidation'
import { BadRequestError } from '../../../tools/errors/BadRequestError'
import { ForbiddenError } from '../../../tools/errors/ForbiddenError'
import { NotFoundError } from '../../../tools/errors/NotFoundError'
import Guild from '../../../models/Guild'
export const errorsMessages = {
value: {
mustBeSlug: 'Value must be a slug'
},
expiresIn: {
mustBeGreaterOrEqual: 'ExpiresIn must be >= 0'
},
public: {
alreadyHasInvitation: 'There is already a public invitation for this guild'
}
}
export const putInvitationsRouter = Router()
putInvitationsRouter.put(
'/invitations/:invitationId',
authenticateUser,
[
body('value')
.optional({ nullable: true })
.trim()
.escape()
.isLength({ max: 250, min: 1 })
.withMessage(
commonErrorsMessages.charactersLength('value', { max: 250, min: 1 })
)
.isSlug()
.withMessage(errorsMessages.value.mustBeSlug)
.custom(async (value: string) => {
return await alreadyUsedValidation(Invitation, 'value', value)
}),
body('expiresIn')
.optional({ nullable: true })
.isInt({ min: 0 })
.withMessage(errorsMessages.expiresIn.mustBeGreaterOrEqual),
body('isPublic').optional({ nullable: true }).isBoolean()
],
validateRequest,
async (req: Request, res: Response) => {
if (req.user == null) {
throw new ForbiddenError()
}
const user = req.user.current
const { value, expiresIn, isPublic } = req.body as {
value?: string
expiresIn?: number
isPublic?: boolean
}
const { invitationId } = req.params as { invitationId: string }
const invitation = await Invitation.findOne({ where: { id: invitationId } })
if (invitation == null) {
throw new NotFoundError()
}
const member = await Member.findOne({
where: { userId: user.id, guildId: invitation.guildId, isOwner: true },
include: [Guild]
})
if (member == null) {
throw new NotFoundError()
}
if (invitation.isPublic && expiresIn != null) {
throw new BadRequestError('You can\'t edit "expiresIn" if it is a public invitation')
}
if (member.guild.isPublic && isPublic != null && !isPublic) {
throw new BadRequestError('You can\'t edit "isPublic" if the guild is still public')
}
let expiresInValue = expiresIn ?? invitation.expiresIn
if (expiresInValue > 0 && expiresIn != null) {
expiresInValue += Date.now()
}
invitation.value = value ?? invitation.value
invitation.expiresIn = expiresInValue
invitation.isPublic = isPublic != null ? isPublic : invitation.isPublic
const foundInvitation = await Invitation.findOne({
where: { isPublic: true, guildId: member.guildId }
})
if (isPublic != null && isPublic && foundInvitation != null) {
throw new BadRequestError(errorsMessages.public.alreadyHasInvitation)
}
await invitation.save()
return res.status(200).json({ invitation })
}
)