feat(services): add GET /guilds/[guildId]
This commit is contained in:
		| @@ -1,4 +1,2 @@ | ||||
| # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/javascript-node/.devcontainer/base.Dockerfile | ||||
|  | ||||
| ARG VARIANT="16-bullseye" | ||||
| ARG VARIANT="16" | ||||
| FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} | ||||
|   | ||||
| @@ -1,16 +1,16 @@ | ||||
| FROM node:16.11.0 AS dependencies | ||||
| FROM node:16.13.1 AS dependencies | ||||
| WORKDIR /usr/src/app | ||||
| COPY ./package*.json ./ | ||||
| RUN npm clean-install | ||||
|  | ||||
| FROM node:16.11.0 AS builder | ||||
| FROM node:16.13.1 AS builder | ||||
| WORKDIR /usr/src/app | ||||
| COPY --from=dependencies /usr/src/app/node_modules ./node_modules | ||||
| COPY ./ ./ | ||||
| RUN npx prisma generate | ||||
| RUN npm run build | ||||
|  | ||||
| FROM node:16.11.0 AS runner | ||||
| FROM node:16.13.1 AS runner | ||||
| WORKDIR /usr/src/app | ||||
| ENV NODE_ENV=production | ||||
| COPY --from=builder /usr/src/app/node_modules ./node_modules | ||||
|   | ||||
							
								
								
									
										8062
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8062
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										48
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								package.json
									
									
									
									
									
								
							| @@ -31,44 +31,44 @@ | ||||
|     "postinstall": "husky install" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@prisma/client": "3.5.0", | ||||
|     "@sinclair/typebox": "0.20.6", | ||||
|     "@prisma/client": "3.7.0", | ||||
|     "@sinclair/typebox": "0.23.2", | ||||
|     "axios": "0.24.0", | ||||
|     "bcryptjs": "2.4.3", | ||||
|     "dotenv": "10.0.0", | ||||
|     "ejs": "3.1.6", | ||||
|     "fastify": "3.24.0", | ||||
|     "fastify": "3.25.2", | ||||
|     "fastify-cors": "6.0.2", | ||||
|     "fastify-helmet": "5.3.2", | ||||
|     "fastify-multipart": "5.1.0", | ||||
|     "fastify-multipart": "5.2.1", | ||||
|     "fastify-plugin": "3.0.0", | ||||
|     "fastify-rate-limit": "5.6.2", | ||||
|     "fastify-rate-limit": "5.7.0", | ||||
|     "fastify-sensible": "3.1.2", | ||||
|     "fastify-static": "4.5.0", | ||||
|     "fastify-swagger": "4.12.6", | ||||
|     "fastify-swagger": "4.13.0", | ||||
|     "fastify-url-data": "3.0.3", | ||||
|     "http-errors": "1.8.1", | ||||
|     "http-errors": "2.0.0", | ||||
|     "jsonwebtoken": "8.5.1", | ||||
|     "ms": "2.1.3", | ||||
|     "nodemailer": "6.7.1", | ||||
|     "nodemailer": "6.7.2", | ||||
|     "read-pkg": "5.2.0", | ||||
|     "socket.io": "4.4.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@commitlint/cli": "15.0.0", | ||||
|     "@commitlint/config-conventional": "15.0.0", | ||||
|     "@commitlint/cli": "16.0.1", | ||||
|     "@commitlint/config-conventional": "16.0.0", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.0", | ||||
|     "@types/bcryptjs": "2.4.2", | ||||
|     "@types/busboy": "0.3.0", | ||||
|     "@types/busboy": "0.3.1", | ||||
|     "@types/ejs": "3.1.0", | ||||
|     "@types/http-errors": "1.8.1", | ||||
|     "@types/jest": "27.0.2", | ||||
|     "@types/jest": "27.0.3", | ||||
|     "@types/jsonwebtoken": "8.5.6", | ||||
|     "@types/ms": "0.7.31", | ||||
|     "@types/node": "16.11.7", | ||||
|     "@types/node": "17.0.5", | ||||
|     "@types/nodemailer": "6.4.4", | ||||
|     "@typescript-eslint/eslint-plugin": "4.33.0", | ||||
|     "concurrently": "6.4.0", | ||||
|     "concurrently": "6.5.1", | ||||
|     "cross-env": "7.0.3", | ||||
|     "dockerfilelint": "1.8.0", | ||||
|     "editorconfig-checker": "4.0.2", | ||||
| @@ -79,20 +79,20 @@ | ||||
|     "eslint-plugin-node": "11.1.0", | ||||
|     "eslint-plugin-prettier": "4.0.0", | ||||
|     "eslint-plugin-promise": "5.1.1", | ||||
|     "eslint-plugin-unicorn": "38.0.1", | ||||
|     "eslint-plugin-unicorn": "39.0.0", | ||||
|     "husky": "7.0.4", | ||||
|     "jest": "27.3.1", | ||||
|     "jest": "27.4.5", | ||||
|     "jest-mock-extended": "2.0.4", | ||||
|     "jest-ts-webcompat-resolver": "1.0.0", | ||||
|     "lint-staged": "12.0.2", | ||||
|     "markdownlint-cli": "0.29.0", | ||||
|     "lint-staged": "12.1.4", | ||||
|     "markdownlint-cli": "0.30.0", | ||||
|     "nodemon": "2.0.15", | ||||
|     "plop": "2.7.6", | ||||
|     "prettier": "2.4.1", | ||||
|     "prisma": "3.5.0", | ||||
|     "plop": "3.0.5", | ||||
|     "prettier": "2.5.1", | ||||
|     "prisma": "3.7.0", | ||||
|     "rimraf": "3.0.2", | ||||
|     "semantic-release": "18.0.0", | ||||
|     "ts-jest": "27.0.7", | ||||
|     "typescript": "4.5.2" | ||||
|     "semantic-release": "18.0.1", | ||||
|     "ts-jest": "27.1.2", | ||||
|     "typescript": "4.5.4" | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										68
									
								
								prisma/migrations/20211228152051_init_2/migration.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								prisma/migrations/20211228152051_init_2/migration.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| /* | ||||
|   Warnings: | ||||
|  | ||||
|   - You are about to alter the column `name` on the `Channel` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(20)`. | ||||
|   - You are about to alter the column `name` on the `Guild` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(30)`. | ||||
|   - You are about to alter the column `type` on the `Message` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(10)`. | ||||
|   - You are about to alter the column `mimetype` on the `Message` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(127)`. | ||||
|   - You are about to alter the column `provider` on the `OAuth` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(20)`. | ||||
|   - You are about to alter the column `name` on the `User` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(30)`. | ||||
|   - You are about to alter the column `email` on the `User` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(254)`. | ||||
|   - You are about to alter the column `status` on the `User` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(50)`. | ||||
|   - You are about to alter the column `biography` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Text` to `VarChar(160)`. | ||||
|   - You are about to alter the column `language` on the `UserSetting` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(10)`. | ||||
|   - You are about to alter the column `theme` on the `UserSetting` table. The data in that column could be lost. The data in that column will be cast from `VarChar(255)` to `VarChar(10)`. | ||||
|   - A unique constraint covering the columns `[guildId]` on the table `Channel` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[userId]` on the table `Member` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[guildId]` on the table `Member` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[memberId]` on the table `Message` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[channelId]` on the table `Message` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[userId]` on the table `OAuth` will be added. If there are existing duplicate values, this will fail. | ||||
|   - A unique constraint covering the columns `[userId]` on the table `RefreshToken` will be added. If there are existing duplicate values, this will fail. | ||||
|  | ||||
| */ | ||||
| -- AlterTable | ||||
| ALTER TABLE "Channel" ALTER COLUMN "name" SET DATA TYPE VARCHAR(20); | ||||
|  | ||||
| -- AlterTable | ||||
| ALTER TABLE "Guild" ALTER COLUMN "name" SET DATA TYPE VARCHAR(30); | ||||
|  | ||||
| -- AlterTable | ||||
| ALTER TABLE "Message" ALTER COLUMN "type" SET DATA TYPE VARCHAR(10), | ||||
| ALTER COLUMN "mimetype" SET DATA TYPE VARCHAR(127); | ||||
|  | ||||
| -- AlterTable | ||||
| ALTER TABLE "OAuth" ALTER COLUMN "provider" SET DATA TYPE VARCHAR(20); | ||||
|  | ||||
| -- AlterTable | ||||
| ALTER TABLE "User" ALTER COLUMN "name" SET DATA TYPE VARCHAR(30), | ||||
| ALTER COLUMN "email" SET DATA TYPE VARCHAR(254), | ||||
| ALTER COLUMN "status" SET DATA TYPE VARCHAR(50), | ||||
| ALTER COLUMN "biography" SET DATA TYPE VARCHAR(160); | ||||
|  | ||||
| -- AlterTable | ||||
| ALTER TABLE "UserSetting" ALTER COLUMN "language" SET DEFAULT E'en', | ||||
| ALTER COLUMN "language" SET DATA TYPE VARCHAR(10), | ||||
| ALTER COLUMN "theme" SET DEFAULT E'dark', | ||||
| ALTER COLUMN "theme" SET DATA TYPE VARCHAR(10); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "Channel_guildId_key" ON "Channel"("guildId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "Member_userId_key" ON "Member"("userId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "Member_guildId_key" ON "Member"("guildId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "Message_memberId_key" ON "Message"("memberId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "Message_channelId_key" ON "Message"("channelId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "OAuth_userId_key" ON "OAuth"("userId"); | ||||
|  | ||||
| -- CreateIndex | ||||
| CREATE UNIQUE INDEX "RefreshToken_userId_key" ON "RefreshToken"("userId"); | ||||
							
								
								
									
										53
									
								
								src/services/guilds/[guildId]/__test__/get.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/services/guilds/[guildId]/__test__/get.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| 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' | ||||
| import { userExample } from '../../../../models/User.js' | ||||
|  | ||||
| describe('GET /guilds/[guildId]', () => { | ||||
|   it('succeeds', async () => { | ||||
|     prismaMock.member.findFirst.mockResolvedValue({ | ||||
|       ...memberExample, | ||||
|       guild: guildExample, | ||||
|       user: userExample | ||||
|     } as any) | ||||
|     const { accessToken, user } = await authenticateUserTest() | ||||
|     const response = await application.inject({ | ||||
|       method: 'GET', | ||||
|       url: `/guilds/${guildExample.id}`, | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(200) | ||||
|     expect(responseJson.member.isOwner).toEqual(memberExample.isOwner) | ||||
|     expect(responseJson.member.user.name).toEqual(user.name) | ||||
|     expect(responseJson.member.user.email).toBeNull() | ||||
|     expect(responseJson.guild.name).toEqual(guildExample.name) | ||||
|   }) | ||||
|  | ||||
|   it('fails with not found guild', async () => { | ||||
|     const { accessToken } = await authenticateUserTest() | ||||
|     prismaMock.member.findFirst.mockResolvedValue(null) | ||||
|     const response = await application.inject({ | ||||
|       method: 'GET', | ||||
|       url: '/guilds/1', | ||||
|       headers: { | ||||
|         authorization: `Bearer ${accessToken}` | ||||
|       } | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|     expect(responseJson.message).toEqual('Member not found') | ||||
|   }) | ||||
|  | ||||
|   it('fails with unauthenticated user', async () => { | ||||
|     const response = await application.inject({ | ||||
|       method: 'GET', | ||||
|       url: '/guilds/1' | ||||
|     }) | ||||
|     expect(response.statusCode).toEqual(401) | ||||
|   }) | ||||
| }) | ||||
							
								
								
									
										91
									
								
								src/services/guilds/[guildId]/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								src/services/guilds/[guildId]/get.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| 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 { memberSchema } from '../../../models/Member.js' | ||||
| import { userPublicWithoutSettingsSchema } from '../../../models/User.js' | ||||
|  | ||||
| const parametersSchema = Type.Object({ | ||||
|   guildId: guildSchema.id | ||||
| }) | ||||
|  | ||||
| type Parameters = Static<typeof parametersSchema> | ||||
|  | ||||
| const getServiceSchema: FastifySchema = { | ||||
|   description: 'GET a guild member with the guildId.', | ||||
|   tags: ['guilds'] as string[], | ||||
|   security: [ | ||||
|     { | ||||
|       bearerAuth: [] | ||||
|     } | ||||
|   ] as Array<{ [key: string]: [] }>, | ||||
|   params: parametersSchema, | ||||
|   response: { | ||||
|     200: Type.Object({ | ||||
|       guild: Type.Object(guildSchema), | ||||
|       member: Type.Object({ | ||||
|         ...memberSchema, | ||||
|         user: Type.Object(userPublicWithoutSettingsSchema) | ||||
|       }) | ||||
|     }), | ||||
|     400: fastifyErrors[400], | ||||
|     401: fastifyErrors[401], | ||||
|     403: fastifyErrors[403], | ||||
|     500: fastifyErrors[500] | ||||
|   } | ||||
| } as const | ||||
|  | ||||
| export const getGuildMemberByIdService: FastifyPluginAsync = async ( | ||||
|   fastify | ||||
| ) => { | ||||
|   await fastify.register(authenticateUser) | ||||
|  | ||||
|   fastify.route<{ | ||||
|     Params: Parameters | ||||
|   }>({ | ||||
|     method: 'GET', | ||||
|     url: '/guilds/:guildId', | ||||
|     schema: getServiceSchema, | ||||
|     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 }, | ||||
|         include: { | ||||
|           user: { | ||||
|             select: { | ||||
|               id: true, | ||||
|               name: true, | ||||
|               logo: true, | ||||
|               status: true, | ||||
|               biography: true, | ||||
|               website: true, | ||||
|               createdAt: true, | ||||
|               updatedAt: true | ||||
|             } | ||||
|           }, | ||||
|           guild: true | ||||
|         } | ||||
|       }) | ||||
|       if (member == null) { | ||||
|         throw fastify.httpErrors.notFound('Member not found') | ||||
|       } | ||||
|       reply.statusCode = 200 | ||||
|       return { | ||||
|         guild: member.guild, | ||||
|         member: { | ||||
|           ...member, | ||||
|           user: { | ||||
|             ...member.user, | ||||
|             email: null | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -3,11 +3,13 @@ import { FastifyPluginAsync } from 'fastify' | ||||
| import { getGuilds } from './get.js' | ||||
| import { postGuilds } from './post.js' | ||||
| import { getGuildsPublic } from './public/get.js' | ||||
| import { getGuildMemberByIdService } from './[guildId]/get.js' | ||||
| import { putGuildIconById } from './[guildId]/icon/put.js' | ||||
|  | ||||
| export const guildsService: FastifyPluginAsync = async (fastify) => { | ||||
|   await fastify.register(postGuilds) | ||||
|   await fastify.register(getGuilds) | ||||
|   await fastify.register(putGuildIconById) | ||||
|   await fastify.register(getGuildMemberByIdService) | ||||
|   await fastify.register(getGuildsPublic) | ||||
| } | ||||
|   | ||||
| @@ -17,4 +17,15 @@ describe('GET /users/[userId]', () => { | ||||
|     expect(responseJson.user.id).toEqual(userExample.id) | ||||
|     expect(responseJson.user.name).toEqual(userExample.name) | ||||
|   }) | ||||
|  | ||||
|   it('fails with not found user', async () => { | ||||
|     prismaMock.userSetting.findFirst.mockResolvedValue(null) | ||||
|     const response = await application.inject({ | ||||
|       method: 'GET', | ||||
|       url: `/users/1` | ||||
|     }) | ||||
|     const responseJson = response.json() | ||||
|     expect(response.statusCode).toEqual(404) | ||||
|     expect(responseJson.message).toEqual('User not found') | ||||
|   }) | ||||
| }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user