chore: initial commit
This commit is contained in:
24
src/services/messages/[messageId]/__docs__/delete.yaml
Normal file
24
src/services/messages/[messageId]/__docs__/delete.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
/messages/{messageId}:
|
||||
delete:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
tags:
|
||||
- 'messages'
|
||||
summary: 'DELETE a message with its id'
|
||||
parameters:
|
||||
- name: 'messageId'
|
||||
in: 'path'
|
||||
required: true
|
||||
responses:
|
||||
allOf:
|
||||
- $ref: '#/definitions/UnauthorizedError'
|
||||
- $ref: '#/definitions/ForbiddenError'
|
||||
- $ref: '#/definitions/NotFoundError'
|
||||
- '200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: 'object'
|
||||
properties:
|
||||
deletedMessageId:
|
||||
type: 'number'
|
37
src/services/messages/[messageId]/__docs__/put.yaml
Normal file
37
src/services/messages/[messageId]/__docs__/put.yaml
Normal file
@ -0,0 +1,37 @@
|
||||
/messages/{messageId}:
|
||||
put:
|
||||
security:
|
||||
- bearerAuth: []
|
||||
tags:
|
||||
- 'messages'
|
||||
summary: 'Update a message with its id'
|
||||
parameters:
|
||||
- name: 'messageId'
|
||||
in: 'path'
|
||||
required: true
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: 'object'
|
||||
properties:
|
||||
value:
|
||||
type: 'string'
|
||||
minLength: 1
|
||||
maxLength: 50_000
|
||||
responses:
|
||||
allOf:
|
||||
- $ref: '#/definitions/UnauthorizedError'
|
||||
- $ref: '#/definitions/ForbiddenError'
|
||||
- $ref: '#/definitions/BadRequestError'
|
||||
- $ref: '#/definitions/NotFoundError'
|
||||
- '200':
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: 'object'
|
||||
properties:
|
||||
message:
|
||||
allOf:
|
||||
- $ref: '#/definitions/Message'
|
||||
- $ref: '#/definitions/User'
|
36
src/services/messages/[messageId]/__test__/delete.test.ts
Normal file
36
src/services/messages/[messageId]/__test__/delete.test.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import request from 'supertest'
|
||||
|
||||
import application from '../../../../application'
|
||||
import { createMessages } from '../../__test__/utils/createMessages'
|
||||
import Message from '../../../../models/Message'
|
||||
import { authenticateUserTest } from '../../../../__test__/utils/authenticateUser'
|
||||
import { formatErrors } from '../../../../__test__/utils/formatErrors'
|
||||
|
||||
describe('DELETE /messages/:messageId', () => {
|
||||
it('succeeds and delete the message', async () => {
|
||||
const result = await createMessages(['awesome'])
|
||||
const messageToDelete = result.messages[0]
|
||||
const response = await request(application)
|
||||
.delete(`/messages/${messageToDelete.id as number}`)
|
||||
.set('Authorization', `${result.user.type} ${result.user.accessToken}`)
|
||||
.send()
|
||||
.expect(200)
|
||||
expect(response.body.deletedMessageId).toEqual(messageToDelete.id)
|
||||
const foundMessage = await Message.findOne({
|
||||
where: { id: messageToDelete.id }
|
||||
})
|
||||
expect(foundMessage).toBeNull()
|
||||
})
|
||||
|
||||
it("fails if the message doesn't exist", async () => {
|
||||
const userToken = await authenticateUserTest()
|
||||
const response = await request(application)
|
||||
.delete('/messages/23')
|
||||
.set('Authorization', `${userToken.type} ${userToken.accessToken}`)
|
||||
.send()
|
||||
.expect(404)
|
||||
const errors = formatErrors(response.body.errors)
|
||||
expect(errors.length).toEqual(1)
|
||||
expect(errors).toEqual(expect.arrayContaining(['Not Found']))
|
||||
})
|
||||
})
|
38
src/services/messages/[messageId]/__test__/put.test.ts
Normal file
38
src/services/messages/[messageId]/__test__/put.test.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import request from 'supertest'
|
||||
|
||||
import application from '../../../../application'
|
||||
import { commonErrorsMessages } from '../../../../tools/configurations/constants'
|
||||
import { formatErrors } from '../../../../__test__/utils/formatErrors'
|
||||
import { createMessages } from '../../__test__/utils/createMessages'
|
||||
|
||||
describe('PUT /messages/:messageId', () => {
|
||||
it('succeeds and edit the message', async () => {
|
||||
const value = 'awesome message'
|
||||
const result = await createMessages(['awesome'])
|
||||
const messageToEdit = result.messages[0]
|
||||
const response = await request(application)
|
||||
.put(`/messages/${messageToEdit.id as number}`)
|
||||
.set('Authorization', `${result.user.type} ${result.user.accessToken}`)
|
||||
.send({ value })
|
||||
.expect(200)
|
||||
expect(response.body.message).not.toBeNull()
|
||||
expect(response.body.message.value).toEqual(value)
|
||||
})
|
||||
|
||||
it('fails with no message value', async () => {
|
||||
const result = await createMessages(['awesome'])
|
||||
const messageToEdit = result.messages[0]
|
||||
const response = await request(application)
|
||||
.put(`/messages/${messageToEdit.id as number}`)
|
||||
.set('Authorization', `${result.user.type} ${result.user.accessToken}`)
|
||||
.send()
|
||||
.expect(400)
|
||||
const errors = formatErrors(response.body.errors)
|
||||
expect(errors.length).toEqual(1)
|
||||
expect(errors).toEqual(
|
||||
expect.arrayContaining([
|
||||
commonErrorsMessages.charactersLength('value', { min: 1, max: 50_000 })
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
61
src/services/messages/[messageId]/delete.ts
Normal file
61
src/services/messages/[messageId]/delete.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Request, Response, Router } from 'express'
|
||||
|
||||
import { authenticateUser } from '../../../tools/middlewares/authenticateUser'
|
||||
import Channel from '../../../models/Channel'
|
||||
import Member from '../../../models/Member'
|
||||
import Message from '../../../models/Message'
|
||||
import { messagesFilePath } from '../../../tools/configurations/constants'
|
||||
import { BadRequestError } from '../../../tools/errors/BadRequestError'
|
||||
import { ForbiddenError } from '../../../tools/errors/ForbiddenError'
|
||||
import { NotFoundError } from '../../../tools/errors/NotFoundError'
|
||||
import { deleteFile } from '../../../tools/utils/deleteFiles'
|
||||
import { emitToMembers } from '../../../tools/socket/emitEvents'
|
||||
|
||||
export const deleteByIdMessagesRouter = Router()
|
||||
|
||||
deleteByIdMessagesRouter.delete(
|
||||
'/messages/:messageId',
|
||||
authenticateUser,
|
||||
async (req: Request, res: Response) => {
|
||||
if (req.user == null) {
|
||||
throw new ForbiddenError()
|
||||
}
|
||||
const user = req.user.current
|
||||
const { messageId } = req.params as { messageId: string }
|
||||
const message = await Message.findOne({ where: { id: messageId } })
|
||||
if (message == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
const channel = await Channel.findOne({
|
||||
where: { id: message.channelId }
|
||||
})
|
||||
if (channel == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
const member = await Member.findOne({
|
||||
where: { userId: user.id, guildId: channel.guildId }
|
||||
})
|
||||
if (member == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
if (!member.isOwner && member.id !== message.memberId) {
|
||||
throw new BadRequestError(
|
||||
'You can only delete your messages except if you are owner of the guild'
|
||||
)
|
||||
}
|
||||
const deletedMessageId = message.id
|
||||
if (message.type === 'file') {
|
||||
await deleteFile({
|
||||
basePath: messagesFilePath,
|
||||
valueSavedInDatabase: message.value
|
||||
})
|
||||
}
|
||||
await message.destroy()
|
||||
await emitToMembers({
|
||||
event: 'messages',
|
||||
guildId: channel.guildId,
|
||||
payload: { action: 'delete', item: message }
|
||||
})
|
||||
return res.status(200).json({ deletedMessageId })
|
||||
}
|
||||
)
|
71
src/services/messages/[messageId]/put.ts
Normal file
71
src/services/messages/[messageId]/put.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { Request, Response, Router } from 'express'
|
||||
import { body } from 'express-validator'
|
||||
|
||||
import { authenticateUser } from '../../../tools/middlewares/authenticateUser'
|
||||
import { validateRequest } from '../../../tools/middlewares/validateRequest'
|
||||
import Channel from '../../../models/Channel'
|
||||
import Member from '../../../models/Member'
|
||||
import Message from '../../../models/Message'
|
||||
import { commonErrorsMessages } from '../../../tools/configurations/constants'
|
||||
import { BadRequestError } from '../../../tools/errors/BadRequestError'
|
||||
import { ForbiddenError } from '../../../tools/errors/ForbiddenError'
|
||||
import { NotFoundError } from '../../../tools/errors/NotFoundError'
|
||||
import { emitToMembers } from '../../../tools/socket/emitEvents'
|
||||
|
||||
export const putByIdMessagesRouter = Router()
|
||||
|
||||
putByIdMessagesRouter.put(
|
||||
'/messages/:messageId',
|
||||
authenticateUser,
|
||||
[
|
||||
body('value')
|
||||
.trim()
|
||||
.escape()
|
||||
.isLength({ min: 1, max: 50_000 })
|
||||
.withMessage(
|
||||
commonErrorsMessages.charactersLength('value', { min: 1, max: 50_000 })
|
||||
)
|
||||
],
|
||||
validateRequest,
|
||||
async (req: Request, res: Response) => {
|
||||
if (req.user == null) {
|
||||
throw new ForbiddenError()
|
||||
}
|
||||
const user = req.user.current
|
||||
const { value } = req.body as { value: string }
|
||||
const { messageId } = req.params as { messageId: string }
|
||||
const messageToEdit = await Message.findOne({ where: { id: messageId } })
|
||||
if (messageToEdit == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
if (messageToEdit.type === 'file') {
|
||||
throw new BadRequestError(
|
||||
'You can\'t edit a message with the type "file"'
|
||||
)
|
||||
}
|
||||
const channel = await Channel.findOne({
|
||||
where: { id: messageToEdit.channelId }
|
||||
})
|
||||
if (channel == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
const member = await Member.findOne({
|
||||
where: { userId: user.id, guildId: channel.guildId }
|
||||
})
|
||||
if (member == null) {
|
||||
throw new NotFoundError()
|
||||
}
|
||||
if (member.id !== messageToEdit.memberId) {
|
||||
throw new BadRequestError('You can only update your messages')
|
||||
}
|
||||
messageToEdit.value = value ?? messageToEdit.value
|
||||
await messageToEdit.save()
|
||||
const message = { ...messageToEdit.toJSON(), user: req.user.current }
|
||||
await emitToMembers({
|
||||
event: 'messages',
|
||||
guildId: channel.guildId,
|
||||
payload: { action: 'update', item: message }
|
||||
})
|
||||
return res.status(200).json({ message })
|
||||
}
|
||||
)
|
29
src/services/messages/__docs__/_definitions.yaml
Normal file
29
src/services/messages/__docs__/_definitions.yaml
Normal file
@ -0,0 +1,29 @@
|
||||
definitions:
|
||||
MessageType:
|
||||
type: 'string'
|
||||
enum: ['text', 'file']
|
||||
|
||||
Message:
|
||||
type: 'object'
|
||||
properties:
|
||||
id:
|
||||
type: 'integer'
|
||||
description: 'Unique id'
|
||||
value:
|
||||
type: 'string'
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/MessageType'
|
||||
mimetype:
|
||||
type: 'string'
|
||||
example: 'text/plain'
|
||||
memberId:
|
||||
type: 'integer'
|
||||
channelId:
|
||||
type: 'integer'
|
||||
createdAt:
|
||||
type: 'string'
|
||||
format: 'date-time'
|
||||
updatedAt:
|
||||
type: 'string'
|
||||
format: 'date-time'
|
35
src/services/messages/__test__/utils/createMessages.ts
Normal file
35
src/services/messages/__test__/utils/createMessages.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import request from 'supertest'
|
||||
|
||||
import application from '../../../../application'
|
||||
import Channel from '../../../../models/Channel'
|
||||
import Message from '../../../../models/Message'
|
||||
import { createChannels } from '../../../channels/__test__/utils/createChannel'
|
||||
import { CreateGuildResult } from '../../../guilds/__test__/utils/createGuild'
|
||||
|
||||
interface CreateMessagesResult extends CreateGuildResult {
|
||||
channels: Channel[]
|
||||
channelId: number
|
||||
messages: Message[]
|
||||
}
|
||||
|
||||
export const createMessages = async (
|
||||
messages: string[]
|
||||
): Promise<CreateMessagesResult> => {
|
||||
const channel1 = { name: 'general1', description: 'testing' }
|
||||
const result = await createChannels([channel1])
|
||||
const messagesResponses: Message[] = []
|
||||
const channelId = result.channels[0].id as number
|
||||
for (const message of messages) {
|
||||
const response = await request(application)
|
||||
.post(`/channels/${channelId}/messages`)
|
||||
.set('Authorization', `${result.user.type} ${result.user.accessToken}`)
|
||||
.send({ value: message, type: 'text' })
|
||||
.expect(201)
|
||||
messagesResponses.push(response.body.message)
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
channelId,
|
||||
messages: messagesResponses
|
||||
}
|
||||
}
|
9
src/services/messages/index.ts
Normal file
9
src/services/messages/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { Router } from 'express'
|
||||
|
||||
import { deleteByIdMessagesRouter } from './[messageId]/delete'
|
||||
import { putByIdMessagesRouter } from './[messageId]/put'
|
||||
|
||||
export const messagesRouter = Router()
|
||||
|
||||
messagesRouter.use('/', deleteByIdMessagesRouter)
|
||||
messagesRouter.use('/', putByIdMessagesRouter)
|
Reference in New Issue
Block a user