chore: initial commit
This commit is contained in:
25
src/services/invitations/[invitationId]/__docs__/delete.yaml
Normal file
25
src/services/invitations/[invitationId]/__docs__/delete.yaml
Normal 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'
|
40
src/services/invitations/[invitationId]/__docs__/put.yaml
Normal file
40
src/services/invitations/[invitationId]/__docs__/put.yaml
Normal 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'
|
@ -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']))
|
||||
})
|
||||
})
|
93
src/services/invitations/[invitationId]/__test__/put.test.ts
Normal file
93
src/services/invitations/[invitationId]/__test__/put.test.ts
Normal 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']))
|
||||
})
|
||||
})
|
40
src/services/invitations/[invitationId]/delete.ts
Normal file
40
src/services/invitations/[invitationId]/delete.ts
Normal 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 })
|
||||
}
|
||||
)
|
9
src/services/invitations/[invitationId]/index.ts
Normal file
9
src/services/invitations/[invitationId]/index.ts
Normal 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)
|
97
src/services/invitations/[invitationId]/put.ts
Normal file
97
src/services/invitations/[invitationId]/put.ts
Normal 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 })
|
||||
}
|
||||
)
|
Reference in New Issue
Block a user