feat(services): add guilds endpoints
This commit is contained in:
		| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "extends": ["standard-with-typescript", "prettier"], | ||||
|   "plugins": ["unicorn", "import", "prettier"], | ||||
|   "extends": ["conventions", "prettier"], | ||||
|   "plugins": ["prettier", "import", "unicorn"], | ||||
|   "parserOptions": { | ||||
|     "project": "./tsconfig.json" | ||||
|   }, | ||||
| @@ -10,15 +10,8 @@ | ||||
|   }, | ||||
|   "rules": { | ||||
|     "prettier/prettier": "error", | ||||
|     "import/order": [ | ||||
|       "error", | ||||
|       { | ||||
|         "groups": ["builtin", "external", "internal"], | ||||
|         "newlines-between": "always" | ||||
|       } | ||||
|     ], | ||||
|     "import/extensions": ["error", "always"], | ||||
|     "unicorn/prefer-node-protocol": "error", | ||||
|     "unicorn/prevent-abbreviations": "error" | ||||
|     "unicorn/prevent-abbreviations": "error", | ||||
|     "require-atomic-updates": "off" | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -6,5 +6,5 @@ | ||||
|     "jest --findRelatedTests" | ||||
|   ], | ||||
|   "*.{json,jsonc,yml,yaml}": ["prettier --write"], | ||||
|   "*.{md}": ["prettier --write", "markdownlint --dot --fix"] | ||||
|   "*.md": ["prettier --write", "markdownlint --dot --fix"] | ||||
| } | ||||
|   | ||||
							
								
								
									
										4032
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4032
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										47
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								package.json
									
									
									
									
									
								
							| @@ -31,22 +31,22 @@ | ||||
|     "postinstall": "husky install" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@prisma/client": "3.9.1", | ||||
|     "@sinclair/typebox": "0.23.3", | ||||
|     "@thream/socketio-jwt": "2.1.1", | ||||
|     "axios": "0.25.0", | ||||
|     "@prisma/client": "3.9.2", | ||||
|     "@sinclair/typebox": "0.23.4", | ||||
|     "@thream/socketio-jwt": "2.2.1", | ||||
|     "axios": "0.26.0", | ||||
|     "bcryptjs": "2.4.3", | ||||
|     "dotenv": "16.0.0", | ||||
|     "ejs": "3.1.6", | ||||
|     "fastify": "3.27.1", | ||||
|     "fastify": "3.27.2", | ||||
|     "fastify-cors": "6.0.2", | ||||
|     "fastify-helmet": "7.0.1", | ||||
|     "fastify-multipart": "5.3.0", | ||||
|     "fastify-multipart": "5.3.1", | ||||
|     "fastify-plugin": "3.0.1", | ||||
|     "fastify-rate-limit": "5.7.0", | ||||
|     "fastify-rate-limit": "5.7.2", | ||||
|     "fastify-sensible": "3.1.2", | ||||
|     "fastify-static": "4.5.0", | ||||
|     "fastify-swagger": "4.14.0", | ||||
|     "fastify-swagger": "4.15.0", | ||||
|     "fastify-url-data": "3.0.3", | ||||
|     "http-errors": "2.0.0", | ||||
|     "jsonwebtoken": "8.5.1", | ||||
| @@ -56,11 +56,11 @@ | ||||
|     "socket.io": "4.4.1" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@commitlint/cli": "16.1.0", | ||||
|     "@commitlint/config-conventional": "16.0.0", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.0", | ||||
|     "@commitlint/cli": "16.2.1", | ||||
|     "@commitlint/config-conventional": "16.2.1", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.1", | ||||
|     "@swc/cli": "0.1.55", | ||||
|     "@swc/core": "1.2.136", | ||||
|     "@swc/core": "1.2.143", | ||||
|     "@swc/jest": "0.2.17", | ||||
|     "@types/bcryptjs": "2.4.2", | ||||
|     "@types/busboy": "1.3.0", | ||||
| @@ -69,30 +69,29 @@ | ||||
|     "@types/jest": "27.4.0", | ||||
|     "@types/jsonwebtoken": "8.5.8", | ||||
|     "@types/ms": "0.7.31", | ||||
|     "@types/node": "17.0.15", | ||||
|     "@types/node": "17.0.18", | ||||
|     "@types/nodemailer": "6.4.4", | ||||
|     "@typescript-eslint/eslint-plugin": "4.33.0", | ||||
|     "@typescript-eslint/eslint-plugin": "5.12.0", | ||||
|     "concurrently": "7.0.0", | ||||
|     "cross-env": "7.0.3", | ||||
|     "editorconfig-checker": "4.0.2", | ||||
|     "eslint": "7.32.0", | ||||
|     "eslint-config-prettier": "8.3.0", | ||||
|     "eslint-config-standard-with-typescript": "21.0.1", | ||||
|     "eslint": "8.9.0", | ||||
|     "eslint-config-prettier": "8.4.0", | ||||
|     "eslint-config-conventions": "1.0.2", | ||||
|     "eslint-plugin-import": "2.25.4", | ||||
|     "eslint-plugin-node": "11.1.0", | ||||
|     "eslint-plugin-prettier": "4.0.0", | ||||
|     "eslint-plugin-promise": "5.1.1", | ||||
|     "eslint-plugin-unicorn": "40.1.0", | ||||
|     "eslint-plugin-promise": "6.0.0", | ||||
|     "eslint-plugin-unicorn": "41.0.0", | ||||
|     "husky": "7.0.4", | ||||
|     "jest": "27.5.0", | ||||
|     "jest": "27.5.1", | ||||
|     "jest-mock-extended": "2.0.4", | ||||
|     "jest-ts-webcompat-resolver": "1.0.0", | ||||
|     "lint-staged": "12.3.3", | ||||
|     "markdownlint-cli": "0.31.0", | ||||
|     "lint-staged": "12.3.4", | ||||
|     "markdownlint-cli": "0.31.1", | ||||
|     "nodemon": "2.0.15", | ||||
|     "plop": "3.0.5", | ||||
|     "prettier": "2.5.1", | ||||
|     "prisma": "3.9.1", | ||||
|     "prisma": "3.9.2", | ||||
|     "rimraf": "3.0.2", | ||||
|     "semantic-release": "19.0.2", | ||||
|     "typescript": "4.5.5" | ||||
|   | ||||
							
								
								
									
										41
									
								
								prisma/migrations/20220209214812_init_5/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								prisma/migrations/20220209214812_init_5/migration.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "Member" DROP CONSTRAINT "Member_guildId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "Member" DROP CONSTRAINT "Member_userId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "Message" DROP CONSTRAINT "Message_channelId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "Message" DROP CONSTRAINT "Message_memberId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "OAuth" DROP CONSTRAINT "OAuth_userId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "RefreshToken" DROP CONSTRAINT "RefreshToken_userId_fkey"; | ||||
|  | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "UserSetting" DROP CONSTRAINT "UserSetting_userId_fkey"; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "UserSetting" ADD CONSTRAINT "UserSetting_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "RefreshToken" ADD CONSTRAINT "RefreshToken_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "OAuth" ADD CONSTRAINT "OAuth_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "Member" ADD CONSTRAINT "Member_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "Member" ADD CONSTRAINT "Member_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "Message" ADD CONSTRAINT "Message_memberId_fkey" FOREIGN KEY ("memberId") REFERENCES "Member"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "Message" ADD CONSTRAINT "Message_channelId_fkey" FOREIGN KEY ("channelId") REFERENCES "Channel"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
							
								
								
									
										5
									
								
								prisma/migrations/20220209215000_init_5/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								prisma/migrations/20220209215000_init_5/migration.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| -- DropForeignKey | ||||
| ALTER TABLE "Channel" DROP CONSTRAINT "Channel_guildId_fkey"; | ||||
|  | ||||
| -- AddForeignKey | ||||
| ALTER TABLE "Channel" ADD CONSTRAINT "Channel_guildId_fkey" FOREIGN KEY ("guildId") REFERENCES "Guild"("id") ON DELETE CASCADE ON UPDATE CASCADE; | ||||
| @@ -36,7 +36,7 @@ model UserSetting { | ||||
|   createdAt      DateTime @default(now()) | ||||
|   updatedAt      DateTime @default(now()) @updatedAt | ||||
|   userId         Int      @unique | ||||
|   user           User?    @relation(fields: [userId], references: [id]) | ||||
|   user           User?    @relation(fields: [userId], references: [id], onDelete: Cascade) | ||||
| } | ||||
|  | ||||
| model RefreshToken { | ||||
| @@ -45,7 +45,7 @@ model RefreshToken { | ||||
|   createdAt DateTime @default(now()) | ||||
|   updatedAt DateTime @default(now()) @updatedAt | ||||
|   userId    Int | ||||
|   user      User?    @relation(fields: [userId], references: [id]) | ||||
|   user      User?    @relation(fields: [userId], references: [id], onDelete: Cascade) | ||||
| } | ||||
|  | ||||
| model OAuth { | ||||
| @@ -55,7 +55,7 @@ model OAuth { | ||||
|   createdAt  DateTime @default(now()) | ||||
|   updatedAt  DateTime @default(now()) @updatedAt | ||||
|   userId     Int | ||||
|   user       User?    @relation(fields: [userId], references: [id]) | ||||
|   user       User?    @relation(fields: [userId], references: [id], onDelete: Cascade) | ||||
| } | ||||
|  | ||||
| model Member { | ||||
| @@ -65,9 +65,9 @@ model Member { | ||||
|   createdAt DateTime  @default(now()) | ||||
|   updatedAt DateTime  @default(now()) @updatedAt | ||||
|   userId    Int | ||||
|   user      User?     @relation(fields: [userId], references: [id]) | ||||
|   user      User?     @relation(fields: [userId], references: [id], onDelete: Cascade) | ||||
|   guildId   Int | ||||
|   guild     Guild?    @relation(fields: [guildId], references: [id]) | ||||
|   guild     Guild?    @relation(fields: [guildId], references: [id], onDelete: Cascade) | ||||
| } | ||||
|  | ||||
| model Guild { | ||||
| @@ -87,7 +87,7 @@ model Channel { | ||||
|   createdAt DateTime  @default(now()) | ||||
|   updatedAt DateTime  @default(now()) @updatedAt | ||||
|   guildId   Int | ||||
|   guild     Guild?    @relation(fields: [guildId], references: [id]) | ||||
|   guild     Guild?    @relation(fields: [guildId], references: [id], onDelete: Cascade) | ||||
|   messages  Message[] | ||||
| } | ||||
|  | ||||
| @@ -99,7 +99,7 @@ model Message { | ||||
|   createdAt DateTime @default(now()) | ||||
|   updatedAt DateTime @default(now()) @updatedAt | ||||
|   memberId  Int | ||||
|   member    Member?  @relation(fields: [memberId], references: [id]) | ||||
|   member    Member?  @relation(fields: [memberId], references: [id], onDelete: Cascade) | ||||
|   channelId Int | ||||
|   channel   Channel? @relation(fields: [channelId], references: [id]) | ||||
|   channel   Channel? @relation(fields: [channelId], references: [id], onDelete: Cascade) | ||||
| } | ||||
|   | ||||
| @@ -15,10 +15,10 @@ import { swaggerOptions } from './tools/configurations/swaggerOptions.js' | ||||
| import fastifySocketIo from './tools/plugins/socket-io.js' | ||||
| import { UPLOADS_URL } from './tools/configurations/index.js' | ||||
|  | ||||
| dotenv.config() | ||||
| export const application = fastify({ | ||||
|   logger: process.env.NODE_ENV === 'development' | ||||
| }) | ||||
| dotenv.config() | ||||
|  | ||||
| const main = async (): Promise<void> => { | ||||
|   await application.register(fastifyCors) | ||||
| @@ -34,7 +34,7 @@ const main = async (): Promise<void> => { | ||||
|   }) | ||||
|   await application.register(fastifyHelmet) | ||||
|   await application.register(fastifyRateLimit, { | ||||
|     max: 100, | ||||
|     max: 150, | ||||
|     timeWindow: '1 minute' | ||||
|   }) | ||||
|   await application.register(fastifyStatic, { | ||||
|   | ||||
| @@ -3,7 +3,7 @@ import { HOST, PORT } from './tools/configurations/index.js' | ||||
|  | ||||
| const main = async (): Promise<void> => { | ||||
|   const address = await application.listen(PORT, HOST) | ||||
|   console.log('\x1b[36m%s\x1b[0m', `🚀  Server listening at ${address}`) | ||||
|   console.log('\u001B[36m%s\u001B[0m', `🚀  Server listening at ${address}`) | ||||
| } | ||||
|  | ||||
| main().catch((error) => { | ||||
|   | ||||
| @@ -101,7 +101,6 @@ export const postMessageByChannelIdService: FastifyPluginAsync = async ( | ||||
|           memberId: memberCheck.id | ||||
|         } | ||||
|       }) | ||||
|       reply.statusCode = 201 | ||||
|       const item = { | ||||
|         ...message, | ||||
|         member: { | ||||
| @@ -117,6 +116,7 @@ export const postMessageByChannelIdService: FastifyPluginAsync = async ( | ||||
|         guildId: item.member.guildId, | ||||
|         payload: { action: 'create', item } | ||||
|       }) | ||||
|       reply.statusCode = 201 | ||||
|       return item | ||||
|     } | ||||
|   }) | ||||
|   | ||||
| @@ -95,7 +95,7 @@ export const postMessageUploadsByChannelIdService: FastifyPluginAsync = async ( | ||||
|         fastify, | ||||
|         request, | ||||
|         folderInUploadsFolder: 'messages', | ||||
|         maximumFileSize: maximumFileSize | ||||
|         maximumFileSize | ||||
|       }) | ||||
|       const message = await prisma.message.create({ | ||||
|         data: { | ||||
|   | ||||
							
								
								
									
										58
									
								
								src/services/guilds/[guildId]/__test__/delete.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/services/guilds/[guildId]/__test__/delete.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import { application } from '../../../../application.js' | ||||
| import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' | ||||
| import { prismaMock } from '../../../../__test__/setup.js' | ||||
| import { memberExample } from '../../../../models/Member.js' | ||||
| import { guildExample } from '../../../../models/Guild.js' | ||||
|  | ||||
| describe('DELETE /guilds/[guildId]', () => { | ||||
|   it('succeeds and delete the guild', async () => { | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       isOwner: true | ||||
|     }) | ||||
|     prismaMock.guild.delete.mockResolvedValue(guildExample) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(200) | ||||
|     expect(responseJson.name).toEqual(guildExample.name) | ||||
|     expect(responseJson.description).toEqual(guildExample.description) | ||||
|   }) | ||||
|  | ||||
|   it("fails if the guild doesn't exist", async () => { | ||||
|     prismaMock.member.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the user is not the owner', async () => { | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       isOwner: false | ||||
|     }) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'DELETE', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(403) | ||||
|     expect(responseJson.message).toEqual('You should be an owner of the guild') | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										82
									
								
								src/services/guilds/[guildId]/__test__/put.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/services/guilds/[guildId]/__test__/put.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| import { application } from '../../../../application.js' | ||||
| import { authenticateUserTest } from '../../../../__test__/utils/authenticateUserTest.js' | ||||
| import { prismaMock } from '../../../../__test__/setup.js' | ||||
| import { guildExample } from '../../../../models/Guild.js' | ||||
| import { memberExample } from '../../../../models/Member.js' | ||||
|  | ||||
| describe('PUT /guilds/[guildId]', () => { | ||||
|   it('succeeds and edit the guild', async () => { | ||||
|     const newName = 'New guild name' | ||||
|     const newDescription = 'New guild description' | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       isOwner: true, | ||||
|       guild: guildExample | ||||
|     } as any) | ||||
|     prismaMock.guild.update.mockResolvedValue({ | ||||
|       ...guildExample, | ||||
|       name: newName, | ||||
|       description: newDescription | ||||
|     }) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         name: newName, | ||||
|         description: newDescription | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(200) | ||||
|     expect(responseJson.name).toEqual(newName) | ||||
|     expect(responseJson.description).toEqual(newDescription) | ||||
|   }) | ||||
|  | ||||
|   it("fails if the guild doesn't exist", async () => { | ||||
|     const newName = 'New guild name' | ||||
|     const newDescription = 'New guild description' | ||||
|     prismaMock.member.findFirst.mockResolvedValue(null) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         name: newName, | ||||
|         description: newDescription | ||||
|       } | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|   }) | ||||
|  | ||||
|   it('fails if the user is not the owner', async () => { | ||||
|     const newName = 'New guild name' | ||||
|     const newDescription = 'New guild description' | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       isOwner: false, | ||||
|       guild: guildExample | ||||
|     } as any) | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'PUT', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       }, | ||||
|       payload: { | ||||
|         name: newName, | ||||
|         description: newDescription | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(403) | ||||
|     expect(responseJson.message).toEqual('You should be an owner of the guild') | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										71
									
								
								src/services/guilds/[guildId]/delete.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/services/guilds/[guildId]/delete.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | ||||
| 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 { guildSchema } from '../../../models/Guild.js' | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   guildId: guildSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const deleteServiceSchema: FastifySchema = { | ||||
|   description: 'DELETE a guild with the guildId.', | ||||
|   tags: ['guilds'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object(guildSchema), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     404: fastifyErrors[404], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const deleteGuildByIdService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'DELETE', | ||||
|     url: '/guilds/:guildId', | ||||
|     schema: deleteServiceSchema, | ||||
|     handler: async (request, reply) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { guildId } = request.params | ||||
|       const member = await prisma.member.findFirst({ | ||||
|         where: { guildId, userId: request.user.current.id } | ||||
|       }) | ||||
|       if (member == null) { | ||||
|         throw fastify.httpErrors.notFound('Member not found') | ||||
|       } | ||||
|       if (!member.isOwner) { | ||||
|         throw fastify.httpErrors.forbidden( | ||||
|           'You should be an owner of the guild' | ||||
|         ) | ||||
|       } | ||||
|       const guild = await prisma.guild.delete({ | ||||
|         where: { id: member.guildId } | ||||
|       }) | ||||
|       await fastify.io.emitToMembers({ | ||||
|         event: 'guilds', | ||||
|         guildId: guild.id, | ||||
|         payload: { action: 'delete', item: guild } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|       return guild | ||||
|     } | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										89
									
								
								src/services/guilds/[guildId]/put.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/services/guilds/[guildId]/put.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| 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 { guildSchema } from '../../../models/Guild.js' | ||||
| import { parseStringNullish } from '../../../tools/utils/parseStringNullish.js' | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   guildId: guildSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const bodyPutServiceSchema = Type.Object({ | ||||
|   name: Type.Optional(guildSchema.name), | ||||
|   description: Type.Optional(guildSchema.description) | ||||
| }) | ||||
|  | ||||
| type BodyPutServiceSchemaType = Static<typeof bodyPutServiceSchema> | ||||
|  | ||||
| const putServiceSchema: FastifySchema = { | ||||
|   description: 'Update a guild with the guildId.', | ||||
|   tags: ['guilds'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   body: bodyPutServiceSchema, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object(guildSchema), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     404: fastifyErrors[404], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const putGuildByIdService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Body: BodyPutServiceSchemaType | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'PUT', | ||||
|     url: '/guilds/:guildId', | ||||
|     schema: putServiceSchema, | ||||
|     handler: async (request, reply) => { | ||||
|       if (request.user == null) { | ||||
|         throw fastify.httpErrors.forbidden() | ||||
|       } | ||||
|       const { guildId } = request.params | ||||
|       const { name, description } = request.body | ||||
|       const member = await prisma.member.findFirst({ | ||||
|         where: { guildId, userId: request.user.current.id }, | ||||
|         include: { | ||||
|           guild: true | ||||
|         } | ||||
|       }) | ||||
|       if (member == null || member.guild == null) { | ||||
|         throw fastify.httpErrors.notFound('Member not found') | ||||
|       } | ||||
|       if (!member.isOwner) { | ||||
|         throw fastify.httpErrors.forbidden( | ||||
|           'You should be an owner of the guild' | ||||
|         ) | ||||
|       } | ||||
|       const guild = await prisma.guild.update({ | ||||
|         where: { id: guildId }, | ||||
|         data: { | ||||
|           name: name ?? member.guild.name, | ||||
|           description: parseStringNullish(member.guild.description, description) | ||||
|         } | ||||
|       }) | ||||
|       await fastify.io.emitToMembers({ | ||||
|         event: 'guilds', | ||||
|         guildId: guild.id, | ||||
|         payload: { action: 'update', item: guild } | ||||
|       }) | ||||
|       reply.statusCode = 200 | ||||
|       return guild | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -4,9 +4,11 @@ import { getGuilds } from './get.js' | ||||
| import { postGuilds } from './post.js' | ||||
| import { getGuildsPublic } from './public/get.js' | ||||
| import { getChannelsByGuildIdService } from './[guildId]/channels/get.js' | ||||
| import { deleteGuildByIdService } from './[guildId]/delete.js' | ||||
| import { getGuildMemberByIdService } from './[guildId]/get.js' | ||||
| import { putGuildIconById } from './[guildId]/icon/put.js' | ||||
| import { getMembersByGuildIdService } from './[guildId]/members/get.js' | ||||
| import { putGuildByIdService } from './[guildId]/put.js' | ||||
|  | ||||
| export const guildsService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(postGuilds) | ||||
| @@ -16,4 +18,6 @@ export const guildsService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(getChannelsByGuildIdService) | ||||
|   await fastify.register(getGuildsPublic) | ||||
|   await fastify.register(getMembersByGuildIdService) | ||||
|   await fastify.register(putGuildByIdService) | ||||
|   await fastify.register(deleteGuildByIdService) | ||||
| } | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export const getServiceSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const getGuildsUploadsService: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'GET', | ||||
|   | ||||
| @@ -26,7 +26,7 @@ export const getServiceSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const getUsersUploadsService: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'GET', | ||||
|   | ||||
| @@ -19,7 +19,7 @@ const getServiceSchema: FastifySchema = { | ||||
|   response: { | ||||
|     200: Type.Object({ | ||||
|       user: Type.Object(userPublicSchema), | ||||
|       guilds: Type.Union([Type.Array(Type.Object(guildSchema)), Type.Null()]) | ||||
|       guilds: Type.Array(Type.Object(guildSchema)) | ||||
|     }), | ||||
|     400: fastifyErrors[400], | ||||
|     404: fastifyErrors[404], | ||||
| @@ -28,7 +28,7 @@ const getServiceSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const getUserById: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Params: ParametersGetUser | ||||
|   }>({ | ||||
|     method: 'GET', | ||||
| @@ -65,11 +65,13 @@ export const getUserById: FastifyPluginAsync = async (fastify) => { | ||||
|       return { | ||||
|         user: { | ||||
|           ...user, | ||||
|           email: user.email ?? null, | ||||
|           settings | ||||
|         }, | ||||
|         guilds: !settings.isPublicGuilds | ||||
|           ? null | ||||
|           ? [] | ||||
|           : await prisma.guild.findMany({ | ||||
|               take: 10, | ||||
|               where: { | ||||
|                 members: { | ||||
|                   some: { | ||||
|   | ||||
| @@ -25,7 +25,7 @@ const getConfirmEmailSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const getConfirmEmail: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Querystring: QueryGetConfirmEmailSchemaType | ||||
|   }>({ | ||||
|     method: 'GET', | ||||
|   | ||||
| @@ -35,7 +35,7 @@ const postRefreshTokenSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const postRefreshTokenUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyPostRefreshTokenSchemaType | ||||
|   }>({ | ||||
|     method: 'POST', | ||||
|   | ||||
| @@ -39,7 +39,7 @@ const postResetPasswordSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const postResetPasswordUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyPostResetPasswordSchemaType | ||||
|     Querystring: QueryPostResetPasswordSchemaType | ||||
|   }>({ | ||||
|   | ||||
| @@ -26,7 +26,7 @@ const putResetPasswordSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const putResetPasswordUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyPutResetPasswordSchemaType | ||||
|   }>({ | ||||
|     method: 'PUT', | ||||
|   | ||||
| @@ -60,7 +60,7 @@ describe('POST /users/signin', () => { | ||||
|     const response = await application.inject({ | ||||
|       method: 'POST', | ||||
|       url: '/users/signin', | ||||
|       payload: payload | ||||
|       payload | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(400) | ||||
|   }) | ||||
|   | ||||
| @@ -31,7 +31,7 @@ const postSigninSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const postSigninUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyPostSigninSchemaType | ||||
|   }>({ | ||||
|     method: 'POST', | ||||
|   | ||||
| @@ -24,7 +24,7 @@ const postSignoutSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const postSignoutUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyPostSignoutSchemaType | ||||
|   }>({ | ||||
|     method: 'POST', | ||||
|   | ||||
| @@ -34,7 +34,7 @@ const postSignupSchema: FastifySchema = { | ||||
| } as const | ||||
|  | ||||
| export const postSignupUser: FastifyPluginAsync = async (fastify) => { | ||||
|   fastify.route<{ | ||||
|   await fastify.route<{ | ||||
|     Body: BodyUserSchemaType | ||||
|     Querystring: QueryPostSignupSchemaType | ||||
|   }>({ | ||||
|   | ||||
| @@ -18,7 +18,6 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = { | ||||
|       { name: 'users' }, | ||||
|       { name: 'guilds' }, | ||||
|       { name: 'channels' }, | ||||
|       { name: 'invitations' }, | ||||
|       { name: 'messages' }, | ||||
|       { name: 'members' } | ||||
|     ], | ||||
|   | ||||
| @@ -54,10 +54,11 @@ declare module 'fastify' { | ||||
|  | ||||
| export default fastifyPlugin( | ||||
|   async (fastify) => { | ||||
|     fastify.decorateRequest('user', null) | ||||
|     fastify.addHook('onRequest', async (request) => { | ||||
|     await fastify.decorateRequest('user', null) | ||||
|     await fastify.addHook('onRequest', async (request) => { | ||||
|       const { authorization } = request.headers | ||||
|       request.user = await getUserWithBearerToken(authorization) | ||||
|       const user = await getUserWithBearerToken(authorization) | ||||
|       request.user = user | ||||
|     }) | ||||
|   }, | ||||
|   { fastify: '3.x' } | ||||
|   | ||||
| @@ -80,8 +80,8 @@ export default fastifyPlugin( | ||||
|       emitToAuthorizedUsers, | ||||
|       emitToMembers | ||||
|     } | ||||
|     fastify.decorate('io', io) | ||||
|     fastify.addHook('onClose', async (fastify) => { | ||||
|     await fastify.decorate('io', io) | ||||
|     await fastify.addHook('onClose', (fastify) => { | ||||
|       fastify.io.instance.close() | ||||
|     }) | ||||
|   }, | ||||
|   | ||||
| @@ -3,15 +3,20 @@ import { parseStringNullish } from '../parseStringNullish.js' | ||||
| const defaultString = 'defaultString' | ||||
|  | ||||
| describe('/tools/utils/parseStringNullish', () => { | ||||
|   it('returns `defaultString` if `string === undefined`', () => { | ||||
|     expect(parseStringNullish(defaultString, undefined)).toEqual(defaultString) | ||||
|   }) | ||||
|  | ||||
|   it('returns `null` if `string === null`', () => { | ||||
|     expect(parseStringNullish(defaultString, null)).toEqual(null) | ||||
|   }) | ||||
|  | ||||
|   it('returns `null` if `string.length === 0`', () => { | ||||
|     expect(parseStringNullish(defaultString, '')).toEqual(null) | ||||
|   }) | ||||
|  | ||||
|   it('returns `defaultString` if `string == null`', () => { | ||||
|     expect(parseStringNullish(defaultString)).toEqual(defaultString) | ||||
|   }) | ||||
|  | ||||
|   it('returns `string` if `string.length > 0`', () => { | ||||
|     expect(parseStringNullish(defaultString, 'string')).toEqual('string') | ||||
|     const string = 'myString' | ||||
|     expect(parseStringNullish(defaultString, string)).toEqual(string) | ||||
|   }) | ||||
| }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user