feat(services): add GET /guilds/[guildId]

This commit is contained in:
Divlo 2021-12-28 18:19:53 +00:00
parent a746b6a057
commit 265ac006a0
No known key found for this signature in database
GPG Key ID: 8F9478F220CE65E9
9 changed files with 2883 additions and 5462 deletions

View File

@ -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"
ARG VARIANT="16-bullseye"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT} FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}

View File

@ -1,16 +1,16 @@
FROM node:16.11.0 AS dependencies FROM node:16.13.1 AS dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm clean-install RUN npm clean-install
FROM node:16.11.0 AS builder FROM node:16.13.1 AS builder
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY --from=dependencies /usr/src/app/node_modules ./node_modules COPY --from=dependencies /usr/src/app/node_modules ./node_modules
COPY ./ ./ COPY ./ ./
RUN npx prisma generate RUN npx prisma generate
RUN npm run build RUN npm run build
FROM node:16.11.0 AS runner FROM node:16.13.1 AS runner
WORKDIR /usr/src/app WORKDIR /usr/src/app
ENV NODE_ENV=production ENV NODE_ENV=production
COPY --from=builder /usr/src/app/node_modules ./node_modules COPY --from=builder /usr/src/app/node_modules ./node_modules

8052
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -31,44 +31,44 @@
"postinstall": "husky install" "postinstall": "husky install"
}, },
"dependencies": { "dependencies": {
"@prisma/client": "3.5.0", "@prisma/client": "3.7.0",
"@sinclair/typebox": "0.20.6", "@sinclair/typebox": "0.23.2",
"axios": "0.24.0", "axios": "0.24.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"dotenv": "10.0.0", "dotenv": "10.0.0",
"ejs": "3.1.6", "ejs": "3.1.6",
"fastify": "3.24.0", "fastify": "3.25.2",
"fastify-cors": "6.0.2", "fastify-cors": "6.0.2",
"fastify-helmet": "5.3.2", "fastify-helmet": "5.3.2",
"fastify-multipart": "5.1.0", "fastify-multipart": "5.2.1",
"fastify-plugin": "3.0.0", "fastify-plugin": "3.0.0",
"fastify-rate-limit": "5.6.2", "fastify-rate-limit": "5.7.0",
"fastify-sensible": "3.1.2", "fastify-sensible": "3.1.2",
"fastify-static": "4.5.0", "fastify-static": "4.5.0",
"fastify-swagger": "4.12.6", "fastify-swagger": "4.13.0",
"fastify-url-data": "3.0.3", "fastify-url-data": "3.0.3",
"http-errors": "1.8.1", "http-errors": "2.0.0",
"jsonwebtoken": "8.5.1", "jsonwebtoken": "8.5.1",
"ms": "2.1.3", "ms": "2.1.3",
"nodemailer": "6.7.1", "nodemailer": "6.7.2",
"read-pkg": "5.2.0", "read-pkg": "5.2.0",
"socket.io": "4.4.0" "socket.io": "4.4.0"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "15.0.0", "@commitlint/cli": "16.0.1",
"@commitlint/config-conventional": "15.0.0", "@commitlint/config-conventional": "16.0.0",
"@saithodev/semantic-release-backmerge": "2.1.0", "@saithodev/semantic-release-backmerge": "2.1.0",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/busboy": "0.3.0", "@types/busboy": "0.3.1",
"@types/ejs": "3.1.0", "@types/ejs": "3.1.0",
"@types/http-errors": "1.8.1", "@types/http-errors": "1.8.1",
"@types/jest": "27.0.2", "@types/jest": "27.0.3",
"@types/jsonwebtoken": "8.5.6", "@types/jsonwebtoken": "8.5.6",
"@types/ms": "0.7.31", "@types/ms": "0.7.31",
"@types/node": "16.11.7", "@types/node": "17.0.5",
"@types/nodemailer": "6.4.4", "@types/nodemailer": "6.4.4",
"@typescript-eslint/eslint-plugin": "4.33.0", "@typescript-eslint/eslint-plugin": "4.33.0",
"concurrently": "6.4.0", "concurrently": "6.5.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"dockerfilelint": "1.8.0", "dockerfilelint": "1.8.0",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
@ -79,20 +79,20 @@
"eslint-plugin-node": "11.1.0", "eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.0.0", "eslint-plugin-prettier": "4.0.0",
"eslint-plugin-promise": "5.1.1", "eslint-plugin-promise": "5.1.1",
"eslint-plugin-unicorn": "38.0.1", "eslint-plugin-unicorn": "39.0.0",
"husky": "7.0.4", "husky": "7.0.4",
"jest": "27.3.1", "jest": "27.4.5",
"jest-mock-extended": "2.0.4", "jest-mock-extended": "2.0.4",
"jest-ts-webcompat-resolver": "1.0.0", "jest-ts-webcompat-resolver": "1.0.0",
"lint-staged": "12.0.2", "lint-staged": "12.1.4",
"markdownlint-cli": "0.29.0", "markdownlint-cli": "0.30.0",
"nodemon": "2.0.15", "nodemon": "2.0.15",
"plop": "2.7.6", "plop": "3.0.5",
"prettier": "2.4.1", "prettier": "2.5.1",
"prisma": "3.5.0", "prisma": "3.7.0",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"semantic-release": "18.0.0", "semantic-release": "18.0.1",
"ts-jest": "27.0.7", "ts-jest": "27.1.2",
"typescript": "4.5.2" "typescript": "4.5.4"
} }
} }

View 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");

View 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)
})
})

View 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
}
}
}
}
})
}

View File

@ -3,11 +3,13 @@ import { FastifyPluginAsync } from 'fastify'
import { getGuilds } from './get.js' import { getGuilds } from './get.js'
import { postGuilds } from './post.js' import { postGuilds } from './post.js'
import { getGuildsPublic } from './public/get.js' import { getGuildsPublic } from './public/get.js'
import { getGuildMemberByIdService } from './[guildId]/get.js'
import { putGuildIconById } from './[guildId]/icon/put.js' import { putGuildIconById } from './[guildId]/icon/put.js'
export const guildsService: FastifyPluginAsync = async (fastify) => { export const guildsService: FastifyPluginAsync = async (fastify) => {
await fastify.register(postGuilds) await fastify.register(postGuilds)
await fastify.register(getGuilds) await fastify.register(getGuilds)
await fastify.register(putGuildIconById) await fastify.register(putGuildIconById)
await fastify.register(getGuildMemberByIdService)
await fastify.register(getGuildsPublic) await fastify.register(getGuildsPublic)
} }

View File

@ -17,4 +17,15 @@ describe('GET /users/[userId]', () => {
expect(responseJson.user.id).toEqual(userExample.id) expect(responseJson.user.id).toEqual(userExample.id)
expect(responseJson.user.name).toEqual(userExample.name) 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')
})
}) })