feat(services): add messages endpoints
This commit is contained in:
		| @@ -4,10 +4,12 @@ import { usersService } from './users/index.js' | ||||
| import { guildsService } from './guilds/index.js' | ||||
| import { uploadsService } from './uploads/index.js' | ||||
| import { channelsService } from './channels/index.js' | ||||
| import { messagesService } from './messages/index.js' | ||||
|  | ||||
| export const services: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(channelsService) | ||||
|   await fastify.register(guildsService) | ||||
|   await fastify.register(messagesService) | ||||
|   await fastify.register(uploadsService) | ||||
|   await fastify.register(usersService) | ||||
| } | ||||
|   | ||||
							
								
								
									
										90
									
								
								src/services/messages/[messageId]/__test__/delete.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/services/messages/[messageId]/__test__/delete.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,90 @@ | ||||
| import { application } from '../../../../application.js' | ||||
| import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' | ||||
| import { prismaMock } from '../../../../__test__/setup.js' | ||||
| import { messageExample } from '../../../../models/Message.js' | ||||
| import { memberExample } from '../../../../models/Member.js' | ||||
| import { userExample } from '../../../../models/User.js' | ||||
| import { channelExample } from '../../../../models/Channel.js' | ||||
|  | ||||
| describe('DELETE /messsages/[messageId]', () => { | ||||
|   it('succeeds', async () => { | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       user: userExample | ||||
|     } as any) | ||||
|     prismaMock.message.delete.mockResolvedValue(messageExample) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(200) | ||||
|     expect(responseJson.id).toEqual(messageExample.id) | ||||
|     expect(responseJson.value).toEqual(messageExample.value) | ||||
|     expect(responseJson.type).toEqual(messageExample.type) | ||||
|     expect(responseJson.mimetype).toEqual(messageExample.mimetype) | ||||
|     expect(responseJson.member.id).toEqual(memberExample.id) | ||||
|     expect(responseJson.member.isOwner).toEqual(memberExample.isOwner) | ||||
|     expect(responseJson.member.user.id).toEqual(userExample.id) | ||||
|     expect(responseJson.member.user.name).toEqual(userExample.name) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the message is not found', async () => { | ||||
|     prismaMock.message.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the member is not found', async () => { | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the member is not owner of the message', async () => { | ||||
|     const randomUserIdOwnerOfMessage = 14 | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       userId: randomUserIdOwnerOfMessage | ||||
|     }) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(400) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										109
									
								
								src/services/messages/[messageId]/__test__/put.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								src/services/messages/[messageId]/__test__/put.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,109 @@ | ||||
| import { application } from '../../../../application.js' | ||||
| import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' | ||||
| import { prismaMock } from '../../../../__test__/setup.js' | ||||
| import { messageExample } from '../../../../models/Message.js' | ||||
| import { memberExample } from '../../../../models/Member.js' | ||||
| import { userExample } from '../../../../models/User.js' | ||||
| import { channelExample } from '../../../../models/Channel.js' | ||||
|  | ||||
| describe('PUT /messsages/[messageId]', () => { | ||||
|   it('succeeds', async () => { | ||||
|     const newValue = 'some message' | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       user: userExample | ||||
|     } as any) | ||||
|     prismaMock.message.update.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       value: newValue | ||||
|     }) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         value: newValue | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(200) | ||||
|     expect(responseJson.id).toEqual(messageExample.id) | ||||
|     expect(responseJson.value).toEqual(newValue) | ||||
|     expect(responseJson.type).toEqual(messageExample.type) | ||||
|     expect(responseJson.mimetype).toEqual(messageExample.mimetype) | ||||
|     expect(responseJson.member.id).toEqual(memberExample.id) | ||||
|     expect(responseJson.member.isOwner).toEqual(memberExample.isOwner) | ||||
|     expect(responseJson.member.user.id).toEqual(userExample.id) | ||||
|     expect(responseJson.member.user.name).toEqual(userExample.name) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the message is not found', async () => { | ||||
|     const newValue = 'some message' | ||||
|     prismaMock.message.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         value: newValue | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the member is not found', async () => { | ||||
|     const newValue = 'some message' | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         value: newValue | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the member is not owner of the message', async () => { | ||||
|     const newValue = 'some message' | ||||
|     const randomUserIdOwnerOfMessage = 14 | ||||
|     prismaMock.message.findFirst.mockResolvedValue({ | ||||
|       ...messageExample, | ||||
|       channel: channelExample | ||||
|     } as any) | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       userId: randomUserIdOwnerOfMessage | ||||
|     }) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/messages/${messageExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         value: newValue | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(400) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										118
									
								
								src/services/messages/[messageId]/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/services/messages/[messageId]/delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | ||||
| import { Static, Type } from '@sinclair/typebox' | ||||
| import { FastifyPluginAsync, FastifySchema } from 'fastify' | ||||
|  | ||||
| import prisma from '../../../tools/database/prisma.js' | ||||
| import { fastifyErrors } from '../../../models/utils.js' | ||||
| import authenticateUser from '../../../tools/plugins/authenticateUser.js' | ||||
| import { messageSchema } from '../../../models/Message.js' | ||||
| import { memberSchema } from '../../../models/Member.js' | ||||
| import { userPublicWithoutSettingsSchema } from '../../../models/User.js' | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   messageId: messageSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const putServiceSchema: FastifySchema = { | ||||
|   description: 'UPDATE a message with its id.', | ||||
|   tags: ['messages'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object({ | ||||
|       ...messageSchema, | ||||
|       member: Type.Object({ | ||||
|         ...memberSchema, | ||||
|         user: Type.Object(userPublicWithoutSettingsSchema) | ||||
|       }) | ||||
|     }), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     404: fastifyErrors[404], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const deleteMessageService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'DELETE', | ||||
|     url: '/messages/:messageId', | ||||
|     schema: putServiceSchema, | ||||
|     handler: async (request, reply) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { user } = request | ||||
|       const { messageId } = request.params | ||||
|       const messageCheck = await prisma.message.findFirst({ | ||||
|         where: { id: messageId }, | ||||
|         include: { | ||||
|           channel: true | ||||
|         } | ||||
|       }) | ||||
|       if (messageCheck == null || messageCheck.channel == null) { | ||||
|         throw fastify.httpErrors.notFound('Message not found') | ||||
|       } | ||||
|       const member = await prisma.member.findFirst({ | ||||
|         where: { | ||||
|           guildId: messageCheck.channel.guildId, | ||||
|           userId: user.current.id | ||||
|         }, | ||||
|         include: { | ||||
|           user: { | ||||
|             select: { | ||||
|               id: true, | ||||
|               name: true, | ||||
|               logo: true, | ||||
|               status: true, | ||||
|               biography: true, | ||||
|               website: true, | ||||
|               createdAt: true, | ||||
|               updatedAt: true | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|       if (member == null) { | ||||
|         throw fastify.httpErrors.notFound('Member not found') | ||||
|       } | ||||
|       if (member.userId !== user.current.id) { | ||||
|         throw fastify.httpErrors.badRequest( | ||||
|           'You should be the owner of the message' | ||||
|         ) | ||||
|       } | ||||
|       const message = await prisma.message.delete({ | ||||
|         where: { | ||||
|           id: messageCheck.id | ||||
|         } | ||||
|       }) | ||||
|       const item = { | ||||
|         ...message, | ||||
|         member: { | ||||
|           ...member, | ||||
|           user: { | ||||
|             ...member.user, | ||||
|             email: null | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       await fastify.io.emitToMembers({ | ||||
|         event: 'messages', | ||||
|         guildId: item.member.guildId, | ||||
|         payload: { action: 'delete', item } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|       return item | ||||
|     } | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										130
									
								
								src/services/messages/[messageId]/put.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										130
									
								
								src/services/messages/[messageId]/put.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,130 @@ | ||||
| import { Static, Type } from '@sinclair/typebox' | ||||
| import { FastifyPluginAsync, FastifySchema } from 'fastify' | ||||
|  | ||||
| import prisma from '../../../tools/database/prisma.js' | ||||
| import { fastifyErrors } from '../../../models/utils.js' | ||||
| import authenticateUser from '../../../tools/plugins/authenticateUser.js' | ||||
| import { messageSchema } from '../../../models/Message.js' | ||||
| import { memberSchema } from '../../../models/Member.js' | ||||
| import { userPublicWithoutSettingsSchema } from '../../../models/User.js' | ||||
|  | ||||
| const bodyPutServiceSchema = Type.Object({ | ||||
|   value: messageSchema.value | ||||
| }) | ||||
|  | ||||
| type BodyPutServiceSchemaType = Static<typeof bodyPutServiceSchema> | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   messageId: messageSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const putServiceSchema: FastifySchema = { | ||||
|   description: 'UPDATE a message with its id.', | ||||
|   tags: ['messages'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   body: bodyPutServiceSchema, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object({ | ||||
|       ...messageSchema, | ||||
|       member: Type.Object({ | ||||
|         ...memberSchema, | ||||
|         user: Type.Object(userPublicWithoutSettingsSchema) | ||||
|       }) | ||||
|     }), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     404: fastifyErrors[404], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const putMessageService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Body: BodyPutServiceSchemaType | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'PUT', | ||||
|     url: '/messages/:messageId', | ||||
|     schema: putServiceSchema, | ||||
|     handler: async (request, reply) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { user } = request | ||||
|       const { messageId } = request.params | ||||
|       const { value } = request.body | ||||
|       const messageCheck = await prisma.message.findFirst({ | ||||
|         where: { id: messageId, type: 'text' }, | ||||
|         include: { | ||||
|           channel: true | ||||
|         } | ||||
|       }) | ||||
|       if (messageCheck == null || messageCheck.channel == null) { | ||||
|         throw fastify.httpErrors.notFound('Message not found') | ||||
|       } | ||||
|       const member = await prisma.member.findFirst({ | ||||
|         where: { | ||||
|           guildId: messageCheck.channel.guildId, | ||||
|           userId: user.current.id | ||||
|         }, | ||||
|         include: { | ||||
|           user: { | ||||
|             select: { | ||||
|               id: true, | ||||
|               name: true, | ||||
|               logo: true, | ||||
|               status: true, | ||||
|               biography: true, | ||||
|               website: true, | ||||
|               createdAt: true, | ||||
|               updatedAt: true | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|       if (member == null) { | ||||
|         throw fastify.httpErrors.notFound('Member not found') | ||||
|       } | ||||
|       if (member.userId !== user.current.id) { | ||||
|         throw fastify.httpErrors.badRequest( | ||||
|           'You should be the owner of the message' | ||||
|         ) | ||||
|       } | ||||
|       const message = await prisma.message.update({ | ||||
|         where: { | ||||
|           id: messageCheck.id | ||||
|         }, | ||||
|         data: { | ||||
|           value | ||||
|         } | ||||
|       }) | ||||
|       const item = { | ||||
|         ...message, | ||||
|         member: { | ||||
|           ...member, | ||||
|           user: { | ||||
|             ...member.user, | ||||
|             email: null | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       await fastify.io.emitToMembers({ | ||||
|         event: 'messages', | ||||
|         guildId: item.member.guildId, | ||||
|         payload: { action: 'update', item } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|       return item | ||||
|     } | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/services/messages/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/services/messages/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| import { FastifyPluginAsync } from 'fastify' | ||||
|  | ||||
| import { deleteMessageService } from './[messageId]/delete.js' | ||||
| import { putMessageService } from './[messageId]/put.js' | ||||
|  | ||||
| export const messagesService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(putMessageService) | ||||
|   await fastify.register(deleteMessageService) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user