From a4c77fec507b2cd0dc3d9872f30478c93c3082e6 Mon Sep 17 00:00:00 2001 From: Divlo Date: Fri, 8 Apr 2022 21:36:29 +0200 Subject: [PATCH] fix: file upload and OAuth2 (#10) --- .devcontainer/docker-compose.yml | 2 + .env.example | 40 +++-- .gitignore | 1 - Dockerfile | 2 +- README.md | 2 + package-lock.json | 167 ++++++++++++------ package.json | 5 +- src/application.ts | 10 -- src/scripts/delete-dead-uploaded-files.ts | 51 ------ .../[channelId]/messages/uploads/post.ts | 4 +- src/services/guilds/[guildId]/icon/put.ts | 8 +- src/services/index.ts | 2 - src/services/uploads/guilds/get.ts | 40 ----- src/services/uploads/index.ts | 11 -- src/services/uploads/messages/get.ts | 76 -------- src/services/uploads/users/get.ts | 40 ----- src/services/users/current/logo/put.ts | 8 +- src/services/users/index.ts | 12 ++ .../users/oauth2/discord/add-strategy/get.ts | 53 ++++++ .../discord/callback-add-strategy/get.ts | 56 ++++++ .../users/oauth2/github/add-strategy/get.ts | 53 ++++++ .../github/callback-add-strategy/get.ts | 56 ++++++ .../users/oauth2/google/add-strategy/get.ts | 53 ++++++ .../google/callback-add-strategy/get.ts | 56 ++++++ src/tools/configurations/index.ts | 18 +- src/tools/configurations/swaggerOptions.ts | 2 +- src/tools/utils/uploadFile.ts | 67 +++---- uploads/guilds/.gitkeep | 0 uploads/messages/.gitkeep | 0 uploads/users/.gitkeep | 0 30 files changed, 529 insertions(+), 366 deletions(-) delete mode 100644 src/scripts/delete-dead-uploaded-files.ts delete mode 100644 src/services/uploads/guilds/get.ts delete mode 100644 src/services/uploads/index.ts delete mode 100644 src/services/uploads/messages/get.ts delete mode 100644 src/services/uploads/users/get.ts create mode 100644 src/services/users/oauth2/discord/add-strategy/get.ts create mode 100644 src/services/users/oauth2/discord/callback-add-strategy/get.ts create mode 100644 src/services/users/oauth2/github/add-strategy/get.ts create mode 100644 src/services/users/oauth2/github/callback-add-strategy/get.ts create mode 100644 src/services/users/oauth2/google/add-strategy/get.ts create mode 100644 src/services/users/oauth2/google/callback-add-strategy/get.ts delete mode 100644 uploads/guilds/.gitkeep delete mode 100644 uploads/messages/.gitkeep delete mode 100644 uploads/users/.gitkeep diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 0bfb896..29a71e8 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -8,6 +8,8 @@ services: volumes: - '..:/workspace:cached' command: 'sleep infinity' + extra_hosts: + - 'host.docker.internal:host-gateway' thream-database: image: 'postgres:14.2' diff --git a/.env.example b/.env.example index 3824e1e..7b456bd 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,21 @@ -COMPOSE_PROJECT_NAME='thream-api' -HOST='0.0.0.0' -PORT='8080' -NODE_ENV='development' -API_URL='http://localhost:8080' -DATABASE_URL='postgresql://user:password@thream-database:5432/thream' -JWT_ACCESS_EXPIRES_IN='15 minutes' -JWT_ACCESS_SECRET='accessTokenSecret' -JWT_REFRESH_SECRET='refreshTokenSecret' -DISCORD_CLIENT_ID='' -DISCORD_CLIENT_SECRET='' -GITHUB_CLIENT_ID='' -GITHUB_CLIENT_SECRET='' -GOOGLE_CLIENT_ID='' -GOOGLE_CLIENT_SECRET='' -EMAIL_HOST='thream-maildev' -EMAIL_USER='no-reply@thream.fr' -EMAIL_PASSWORD='password' -EMAIL_PORT='25' +API_URL=http://localhost:8080 +COMPOSE_PROJECT_NAME=thream-api +DATABASE_URL=postgresql://user:password@thream-database:5432/thream +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= +EMAIL_HOST=thream-maildev +EMAIL_PASSWORD=password +EMAIL_PORT=25 +EMAIL_USER=no-reply@thream.fr +FILE_UPLOADS_API_KEY=apiKeySecret +FILE_UPLOADS_API_URL=http://host.docker.internal:8000 +GITHUB_CLIENT_ID= +GITHUB_CLIENT_SECRET= +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +HOST=0.0.0.0 +JWT_ACCESS_EXPIRES_IN=15 minutes +JWT_ACCESS_SECRET=accessTokenSecret +JWT_REFRESH_SECRET=refreshTokenSecret +NODE_ENV=development +PORT=8080 diff --git a/.gitignore b/.gitignore index 5d67d93..1beca0f 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,3 @@ npm-debug.log* # misc .DS_Store -/uploads diff --git a/Dockerfile b/Dockerfile index 82f35f6..99c1cc8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ FROM node:16.14.2 AS runner WORKDIR /usr/src/app ENV NODE_ENV=production COPY --from=builder /usr/src/app/node_modules ./node_modules -COPY --from=builder /usr/src/app/start.sh ./start.sh +COPY --from=builder /usr/src/app/start.sh ./docker-start.sh COPY --from=builder /usr/src/app/package.json ./package.json COPY --from=builder /usr/src/app/email ./email COPY --from=builder /usr/src/app/build ./build diff --git a/README.md b/README.md index a4c708d..9b5f78d 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,8 @@ Thream's Application Programming Interface (API) to stay close with your friends and communities. +It uses [Thream/file-uploads-api](https://github.com/Thream/file-uploads-api) [v1.0.0](https://github.com/Thream/file-uploads-api/releases/tag/v1.0.0). + ## ⚙️ Getting Started ### Prerequisites diff --git a/package-lock.json b/package-lock.json index ef1a832..70f9e55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,9 +23,8 @@ "fastify-plugin": "3.0.1", "fastify-rate-limit": "5.8.0", "fastify-sensible": "3.1.2", - "fastify-static": "4.6.1", "fastify-swagger": "5.1.0", - "fastify-url-data": "3.0.3", + "form-data": "4.0.0", "http-errors": "2.0.0", "jsonwebtoken": "8.5.1", "ms": "2.1.3", @@ -37,6 +36,7 @@ "@commitlint/cli": "16.2.3", "@commitlint/config-conventional": "16.2.1", "@saithodev/semantic-release-backmerge": "2.1.2", + "@semantic-release/git": "10.0.1", "@swc/cli": "0.1.57", "@swc/core": "1.2.164", "@types/bcryptjs": "2.4.2", @@ -1159,6 +1159,28 @@ "node": ">=14.17" } }, + "node_modules/@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + }, + "engines": { + "node": ">=14.17" + }, + "peerDependencies": { + "semantic-release": ">=18.0.0" + } + }, "node_modules/@semantic-release/github": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.4.tgz", @@ -2380,6 +2402,11 @@ "node": ">=10" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "node_modules/atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -2844,9 +2871,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001325", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz", - "integrity": "sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==", + "version": "1.0.30001327", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", + "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", "dev": true, "funding": [ { @@ -3212,6 +3239,17 @@ "node": ">=0.1.90" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -3773,6 +3811,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4916,23 +4962,6 @@ "openapi-types": "^10.0.0" } }, - "node_modules/fastify-url-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fastify-url-data/-/fastify-url-data-3.0.3.tgz", - "integrity": "sha512-fw9F6JhAPjg8oHm1xyZCcdj/agY8+z3xGZBUoHrUvBXOW0PDH+yDBihOsCeXYk4fbpI0KfJ9E44a5KOgpQkoBw==", - "dependencies": { - "fastify-plugin": "^2.0.0", - "uri-js": "^4.2.1" - } - }, - "node_modules/fastify-url-data/node_modules/fastify-plugin": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-2.3.4.tgz", - "integrity": "sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ==", - "dependencies": { - "semver": "^7.3.2" - } - }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -5182,6 +5211,19 @@ "node": ">=8.0.0" } }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -7815,9 +7857,9 @@ "dev": true }, "node_modules/marked": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz", - "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.13.tgz", + "integrity": "sha512-lS/ZCa4X0gsRcfWs1eoh6dLnHr9kVH3K1t2X4M/tTtNouhZ7anS1Csb6464VGLQHv8b2Tw1cLeZQs58Jav8Rzw==", "dev": true, "bin": { "marked": "bin/marked.js" @@ -17303,6 +17345,22 @@ "integrity": "sha512-5hiM4Un+tpl4cKw3lV4UgzJj+SmfNIDCLLw0TepzQxz9ZGV5ixnqkzIVF+3tp0ZHgcMKE+VNGHJjEeyFG2dcSw==", "dev": true }, + "@semantic-release/git": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/git/-/git-10.0.1.tgz", + "integrity": "sha512-eWrx5KguUcU2wUPaO6sfvZI0wPafUKAMNC18aXY4EnNcrZL86dEmpNVnC9uMpGZkmZJ9EfCVJBQx4pV4EMGT1w==", + "dev": true, + "requires": { + "@semantic-release/error": "^3.0.0", + "aggregate-error": "^3.0.0", + "debug": "^4.0.0", + "dir-glob": "^3.0.0", + "execa": "^5.0.0", + "lodash": "^4.17.4", + "micromatch": "^4.0.0", + "p-reduce": "^2.0.0" + } + }, "@semantic-release/github": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-8.0.4.tgz", @@ -18172,6 +18230,11 @@ "integrity": "sha512-14LjCmlK1PK8eDtTezR6WX8TMaYNIzBIsd2D1sGoGjgx0BuNMMoSdk7i/drlbtamy0AWv9yv2tkB+ASdmeqFIw==", "dev": true }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, "atomic-sleep": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", @@ -18511,9 +18574,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001325", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001325.tgz", - "integrity": "sha512-sB1bZHjseSjDtijV1Hb7PB2Zd58Kyx+n/9EotvZ4Qcz2K3d0lWB8dB4nb8wN/TsOGFq3UuAm0zQZNQ4SoR7TrQ==", + "version": "1.0.30001327", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001327.tgz", + "integrity": "sha512-1/Cg4jlD9qjZzhbzkzEaAC2JHsP0WrOc8Rd/3a3LuajGzGWR/hD7TVyvq99VqmTy99eVh8Zkmdq213OgvgXx7w==", "dev": true }, "capital-case": { @@ -18789,6 +18852,14 @@ "dev": true, "optional": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", @@ -19221,6 +19292,11 @@ "slash": "^3.0.0" } }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -20141,25 +20217,6 @@ "openapi-types": "^10.0.0" } }, - "fastify-url-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fastify-url-data/-/fastify-url-data-3.0.3.tgz", - "integrity": "sha512-fw9F6JhAPjg8oHm1xyZCcdj/agY8+z3xGZBUoHrUvBXOW0PDH+yDBihOsCeXYk4fbpI0KfJ9E44a5KOgpQkoBw==", - "requires": { - "fastify-plugin": "^2.0.0", - "uri-js": "^4.2.1" - }, - "dependencies": { - "fastify-plugin": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-2.3.4.tgz", - "integrity": "sha512-I+Oaj6p9oiRozbam30sh39BiuiqBda7yK2nmSPVwDCfIBlKnT8YB3MY+pRQc2Fcd07bf6KPGklHJaQ2Qu81TYQ==", - "requires": { - "semver": "^7.3.2" - } - } - } - }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -20340,6 +20397,16 @@ "signal-exit": "^3.0.2" } }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -22320,9 +22387,9 @@ "dev": true }, "marked": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.12.tgz", - "integrity": "sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ==", + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.0.13.tgz", + "integrity": "sha512-lS/ZCa4X0gsRcfWs1eoh6dLnHr9kVH3K1t2X4M/tTtNouhZ7anS1Csb6464VGLQHv8b2Tw1cLeZQs58Jav8Rzw==", "dev": true }, "marked-terminal": { diff --git a/package.json b/package.json index fead22c..b8f8181 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "start": "node build/index.js", "dev": "concurrently -k -n \"TypeScript,Node\" -p \"[{name}]\" -c \"blue,green\" \"npm run build:dev\" \"cross-env NODE_ENV=development nodemon build/index.js\"", "generate": "plop", - "scripts:delete-dead-uploaded-files": "node build/scripts/delete-dead-uploaded-files.js", "lint:commit": "commitlint", "lint:editorconfig": "editorconfig-checker", "lint:markdown": "markdownlint \"**/*.md\" --dot --ignore-path \".gitignore\"", @@ -48,9 +47,8 @@ "fastify-plugin": "3.0.1", "fastify-rate-limit": "5.8.0", "fastify-sensible": "3.1.2", - "fastify-static": "4.6.1", "fastify-swagger": "5.1.0", - "fastify-url-data": "3.0.3", + "form-data": "4.0.0", "http-errors": "2.0.0", "jsonwebtoken": "8.5.1", "ms": "2.1.3", @@ -62,6 +60,7 @@ "@commitlint/cli": "16.2.3", "@commitlint/config-conventional": "16.2.1", "@saithodev/semantic-release-backmerge": "2.1.2", + "@semantic-release/git": "10.0.1", "@swc/cli": "0.1.57", "@swc/core": "1.2.164", "@types/bcryptjs": "2.4.2", diff --git a/src/application.ts b/src/application.ts index f44e069..abf67a4 100644 --- a/src/application.ts +++ b/src/application.ts @@ -1,19 +1,14 @@ -import { fileURLToPath } from 'node:url' - import dotenv from 'dotenv' import fastify from 'fastify' import fastifyCors from 'fastify-cors' import fastifySwagger from 'fastify-swagger' -import fastifyUrlData from 'fastify-url-data' import fastifyHelmet from 'fastify-helmet' import fastifyRateLimit from 'fastify-rate-limit' import fastifySensible from 'fastify-sensible' -import fastifyStatic from 'fastify-static' import { services } from './services/index.js' 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({ @@ -27,7 +22,6 @@ export const application = fastify({ await application.register(fastifyCors) await application.register(fastifySensible) -await application.register(fastifyUrlData) await application.register(fastifySocketIo, { cors: { origin: '*', @@ -41,9 +35,5 @@ await application.register(fastifyRateLimit, { max: 200, timeWindow: '1 minute' }) -await application.register(fastifyStatic, { - root: fileURLToPath(UPLOADS_URL), - prefix: '/uploads/' -}) await application.register(fastifySwagger, swaggerOptions) await application.register(services) diff --git a/src/scripts/delete-dead-uploaded-files.ts b/src/scripts/delete-dead-uploaded-files.ts deleted file mode 100644 index 5d81f10..0000000 --- a/src/scripts/delete-dead-uploaded-files.ts +++ /dev/null @@ -1,51 +0,0 @@ -import fs from 'node:fs' - -import prisma from '../tools/database/prisma.js' -import { UPLOADS_URL } from '../tools/configurations/index.js' - -const getPathStoredInDatabaseFromFile = ( - file: string, - folderInUploadsFolder: string -): string => { - return `/uploads/${folderInUploadsFolder}/${file}` -} - -const deleteDeadUploadedFiles = async ( - folderInUploadsFolder: string, - getElementInDatabase: (file: string) => Promise -): Promise => { - const UPLOADS_FILES_URL = new URL(`./${folderInUploadsFolder}`, UPLOADS_URL) - const files = await fs.promises.readdir(UPLOADS_FILES_URL) - for (const file of files) { - if (file !== '.gitkeep') { - const pathStoredInDatabase = getPathStoredInDatabaseFromFile( - file, - folderInUploadsFolder - ) - const element = await getElementInDatabase(pathStoredInDatabase) - if (element == null) { - const fileURL = new URL( - `./${folderInUploadsFolder}/${file}`, - UPLOADS_URL - ) - await fs.promises.rm(fileURL) - } - } - } -} - -await deleteDeadUploadedFiles('guilds', async (icon: string) => { - return await prisma.guild.findFirst({ - where: { icon } - }) -}) -await deleteDeadUploadedFiles('messages', async (value: string) => { - return await prisma.message.findFirst({ - where: { type: 'file', value } - }) -}) -await deleteDeadUploadedFiles('users', async (logo: string) => { - return await prisma.user.findFirst({ - where: { logo } - }) -}) diff --git a/src/services/channels/[channelId]/messages/uploads/post.ts b/src/services/channels/[channelId]/messages/uploads/post.ts index 3977d24..b29546b 100644 --- a/src/services/channels/[channelId]/messages/uploads/post.ts +++ b/src/services/channels/[channelId]/messages/uploads/post.ts @@ -10,7 +10,6 @@ import { memberSchema } from '../../../../../models/Member.js' import { userPublicWithoutSettingsSchema } from '../../../../../models/User.js' import { channelSchema } from '../../../../../models/Channel.js' import { uploadFile } from '../../../../../tools/utils/uploadFile.js' -import { MAXIMUM_FILE_SIZE } from '../../../../../tools/configurations/index.js' const parametersSchema = Type.Object({ channelId: channelSchema.id @@ -94,8 +93,7 @@ export const postMessageUploadsByChannelIdService: FastifyPluginAsync = async ( const file = await uploadFile({ fastify, request, - folderInUploadsFolder: 'messages', - maximumFileSize: MAXIMUM_FILE_SIZE + folderInUploadsFolder: 'messages' }) const message = await prisma.message.create({ data: { diff --git a/src/services/guilds/[guildId]/icon/put.ts b/src/services/guilds/[guildId]/icon/put.ts index 28aeb76..5d0a49f 100644 --- a/src/services/guilds/[guildId]/icon/put.ts +++ b/src/services/guilds/[guildId]/icon/put.ts @@ -7,10 +7,6 @@ import { fastifyErrors } from '../../../../models/utils.js' import prisma from '../../../../tools/database/prisma.js' import { uploadFile } from '../../../../tools/utils/uploadFile.js' import { guildSchema } from '../../../../models/Guild.js' -import { - MAXIMUM_IMAGE_SIZE, - SUPPORTED_IMAGE_MIMETYPE -} from '../../../../tools/configurations/index.js' import { channelSchema } from '../../../../models/Channel.js' const parametersSchema = Type.Object({ @@ -67,9 +63,7 @@ export const putGuildIconById: FastifyPluginAsync = async (fastify) => { const file = await uploadFile({ fastify, request, - folderInUploadsFolder: 'guilds', - maximumFileSize: MAXIMUM_IMAGE_SIZE, - supportedFileMimetype: SUPPORTED_IMAGE_MIMETYPE + folderInUploadsFolder: 'guilds' }) await prisma.guild.update({ where: { id: guildId }, diff --git a/src/services/index.ts b/src/services/index.ts index 78c8c88..c71f0fa 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -2,7 +2,6 @@ import { FastifyPluginAsync } from 'fastify' 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' @@ -10,6 +9,5 @@ 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) } diff --git a/src/services/uploads/guilds/get.ts b/src/services/uploads/guilds/get.ts deleted file mode 100644 index ed5e0f0..0000000 --- a/src/services/uploads/guilds/get.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from 'node:path' - -import { FastifyPluginAsync, FastifySchema } from 'fastify' -import { Static, Type } from '@sinclair/typebox' - -import { fastifyErrors } from '../../../models/utils.js' - -const parameters = Type.Object({ - file: Type.String() -}) - -type Parameters = Static - -export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], - params: parameters, - response: { - 200: { - type: 'string', - format: 'binary' - }, - 400: fastifyErrors[400], - 404: fastifyErrors[404], - 500: fastifyErrors[500] - } -} as const - -export const getGuildsUploadsService: FastifyPluginAsync = async (fastify) => { - await fastify.route<{ - Params: Parameters - }>({ - method: 'GET', - url: '/uploads/guilds/:file', - schema: getServiceSchema, - handler: async (request, reply) => { - const { file } = request.params - return await reply.sendFile(path.join('guilds', file)) - } - }) -} diff --git a/src/services/uploads/index.ts b/src/services/uploads/index.ts deleted file mode 100644 index ba46fdd..0000000 --- a/src/services/uploads/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { FastifyPluginAsync } from 'fastify' - -import { getGuildsUploadsService } from './guilds/get.js' -import { getMessagesUploadsService } from './messages/get.js' -import { getUsersUploadsService } from './users/get.js' - -export const uploadsService: FastifyPluginAsync = async (fastify) => { - await fastify.register(getGuildsUploadsService) - await fastify.register(getMessagesUploadsService) - await fastify.register(getUsersUploadsService) -} diff --git a/src/services/uploads/messages/get.ts b/src/services/uploads/messages/get.ts deleted file mode 100644 index fb72ff6..0000000 --- a/src/services/uploads/messages/get.ts +++ /dev/null @@ -1,76 +0,0 @@ -import path from 'node:path' - -import { FastifyPluginAsync, FastifySchema } from 'fastify' -import { Static, Type } from '@sinclair/typebox' - -import { fastifyErrors } from '../../../models/utils.js' -import authenticateUser from '../../../tools/plugins/authenticateUser.js' -import prisma from '../../../tools/database/prisma.js' - -const parameters = Type.Object({ - file: Type.String() -}) - -type Parameters = Static - -export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], - security: [ - { - bearerAuth: [] - } - ] as Array<{ [key: string]: [] }>, - params: parameters, - response: { - 200: { - type: 'string', - format: 'binary' - }, - 400: fastifyErrors[400], - 401: fastifyErrors[401], - 403: fastifyErrors[403], - 404: fastifyErrors[404], - 500: fastifyErrors[500] - } -} as const - -export const getMessagesUploadsService: FastifyPluginAsync = async ( - fastify -) => { - await fastify.register(authenticateUser) - - fastify.route<{ - Params: Parameters - }>({ - method: 'GET', - url: '/uploads/messages/:file', - schema: getServiceSchema, - handler: async (request, reply) => { - if (request.user == null) { - throw fastify.httpErrors.forbidden() - } - const { file } = request.params - const message = await prisma.message.findFirst({ - where: { value: `/uploads/messages/${file}` }, - include: { - member: { - select: { guildId: true } - } - } - }) - if (message == null) { - throw fastify.httpErrors.notFound('Message not found') - } - const member = await prisma.member.findFirst({ - where: { - guildId: message.member?.guildId, - userId: request.user.current.id - } - }) - if (member == null) { - throw fastify.httpErrors.notFound('Member not found') - } - return await reply.sendFile(path.join('messages', file)) - } - }) -} diff --git a/src/services/uploads/users/get.ts b/src/services/uploads/users/get.ts deleted file mode 100644 index 1f77de8..0000000 --- a/src/services/uploads/users/get.ts +++ /dev/null @@ -1,40 +0,0 @@ -import path from 'node:path' - -import { FastifyPluginAsync, FastifySchema } from 'fastify' -import { Static, Type } from '@sinclair/typebox' - -import { fastifyErrors } from '../../../models/utils.js' - -const parameters = Type.Object({ - file: Type.String() -}) - -type Parameters = Static - -export const getServiceSchema: FastifySchema = { - tags: ['uploads'] as string[], - params: parameters, - response: { - 200: { - type: 'string', - format: 'binary' - }, - 400: fastifyErrors[400], - 404: fastifyErrors[404], - 500: fastifyErrors[500] - } -} as const - -export const getUsersUploadsService: FastifyPluginAsync = async (fastify) => { - await fastify.route<{ - Params: Parameters - }>({ - method: 'GET', - url: '/uploads/users/:file', - schema: getServiceSchema, - handler: async (request, reply) => { - const { file } = request.params - return await reply.sendFile(path.join('users', file)) - } - }) -} diff --git a/src/services/users/current/logo/put.ts b/src/services/users/current/logo/put.ts index a4c79cc..9d5953a 100644 --- a/src/services/users/current/logo/put.ts +++ b/src/services/users/current/logo/put.ts @@ -6,10 +6,6 @@ import authenticateUser from '../../../../tools/plugins/authenticateUser.js' import { fastifyErrors } from '../../../../models/utils.js' import prisma from '../../../../tools/database/prisma.js' import { uploadFile } from '../../../../tools/utils/uploadFile.js' -import { - MAXIMUM_IMAGE_SIZE, - SUPPORTED_IMAGE_MIMETYPE -} from '../../../../tools/configurations/index.js' const putServiceSchema: FastifySchema = { description: 'Edit the current connected user logo', @@ -51,9 +47,7 @@ export const putCurrentUserLogo: FastifyPluginAsync = async (fastify) => { const file = await uploadFile({ fastify, request, - folderInUploadsFolder: 'users', - maximumFileSize: MAXIMUM_IMAGE_SIZE, - supportedFileMimetype: SUPPORTED_IMAGE_MIMETYPE + folderInUploadsFolder: 'users' }) await prisma.user.update({ where: { id: request.user.current.id }, diff --git a/src/services/users/index.ts b/src/services/users/index.ts index 6f6c339..0e00bba 100644 --- a/src/services/users/index.ts +++ b/src/services/users/index.ts @@ -20,6 +20,12 @@ import { getCallbackGoogleOAuth2Service } from './oauth2/google/callback/get.js' import { getSigninGitHubOAuth2Service } from './oauth2/github/signin/get.js' import { getCallbackGitHubOAuth2Service } from './oauth2/github/callback/get.js' import { deleteProviderService } from './oauth2/[provider]/delete.js' +import { getCallbackAddStrategyDiscordOAuth2Service } from './oauth2/discord/callback-add-strategy/get.js' +import { getAddStrategyDiscordOAuth2Service } from './oauth2/discord/add-strategy/get.js' +import { getAddStrategyGitHubOAuth2Service } from './oauth2/github/add-strategy/get.js' +import { getCallbackAddStrategyGitHubOAuth2Service } from './oauth2/github/callback-add-strategy/get.js' +import { getCallbackAddStrategyGoogleOAuth2Service } from './oauth2/google/callback-add-strategy/get.js' +import { getAddStrategyGoogleOAuth2Service } from './oauth2/google/add-strategy/get.js' export const usersService: FastifyPluginAsync = async (fastify) => { await fastify.register(postSignupUser) @@ -38,12 +44,18 @@ export const usersService: FastifyPluginAsync = async (fastify) => { await fastify.register(getSigninDiscordOAuth2Service) await fastify.register(getCallbackDiscordOAuth2Service) + await fastify.register(getCallbackAddStrategyDiscordOAuth2Service) + await fastify.register(getAddStrategyDiscordOAuth2Service) await fastify.register(getSigninGoogleOAuth2Service) await fastify.register(getCallbackGoogleOAuth2Service) + await fastify.register(getCallbackAddStrategyGoogleOAuth2Service) + await fastify.register(getAddStrategyGoogleOAuth2Service) await fastify.register(getSigninGitHubOAuth2Service) await fastify.register(getCallbackGitHubOAuth2Service) + await fastify.register(getCallbackAddStrategyGitHubOAuth2Service) + await fastify.register(getAddStrategyGitHubOAuth2Service) await fastify.register(deleteProviderService) } diff --git a/src/services/users/oauth2/discord/add-strategy/get.ts b/src/services/users/oauth2/discord/add-strategy/get.ts new file mode 100644 index 0000000..8b40185 --- /dev/null +++ b/src/services/users/oauth2/discord/add-strategy/get.ts @@ -0,0 +1,53 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { DISCORD_BASE_URL, DISCORD_CLIENT_ID } from '../__utils__/utils.js' +import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'Discord OAuth2 - add-strategy', + tags: ['users'] as string[], + security: [ + { + bearerAuth: [] + } + ] as Array<{ [key: string]: [] }>, + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getAddStrategyDiscordOAuth2Service: FastifyPluginAsync = async ( + fastify +) => { + await fastify.register(authenticateUser) + + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/discord/add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + if (request.user == null) { + throw fastify.httpErrors.forbidden() + } + const { redirectURI } = request.query + const redirectCallback = `${API_URL}/users/oauth2/discord/callback-add-strategy?redirectURI=${redirectURI}` + const url = `${DISCORD_BASE_URL}/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&scope=identify&response_type=code&state=${request.user.accessToken}&redirect_uri=${redirectCallback}` + reply.statusCode = 200 + return url + } + }) +} diff --git a/src/services/users/oauth2/discord/callback-add-strategy/get.ts b/src/services/users/oauth2/discord/callback-add-strategy/get.ts new file mode 100644 index 0000000..9e65e0f --- /dev/null +++ b/src/services/users/oauth2/discord/callback-add-strategy/get.ts @@ -0,0 +1,56 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { discordStrategy, getDiscordUserData } from '../__utils__/utils.js' +import { buildQueryURL } from '../../../../../tools/utils/buildQueryURL.js' +import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + code: Type.String(), + state: Type.String(), + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'Discord OAuth2 - callback-add-strategy', + tags: ['users'] as string[], + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getCallbackAddStrategyDiscordOAuth2Service: FastifyPluginAsync = + async (fastify) => { + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/discord/callback-add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + const { redirectURI, code, state: accessToken } = request.query + const userRequest = await getUserWithBearerToken( + `Bearer ${accessToken}` + ) + const discordUser = await getDiscordUserData( + code, + `${API_URL}/users/oauth2/discord/callback-add-strategy?redirectURI=${redirectURI}` + ) + const message = await discordStrategy.callbackAddStrategy( + { + name: discordUser.username, + id: discordUser.id + }, + userRequest + ) + return await reply.redirect(buildQueryURL(redirectURI, { message })) + } + }) + } diff --git a/src/services/users/oauth2/github/add-strategy/get.ts b/src/services/users/oauth2/github/add-strategy/get.ts new file mode 100644 index 0000000..757c7b4 --- /dev/null +++ b/src/services/users/oauth2/github/add-strategy/get.ts @@ -0,0 +1,53 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { GITHUB_BASE_URL, GITHUB_CLIENT_ID } from '../__utils__/utils.js' +import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'GitHub OAuth2 - add-strategy', + tags: ['users'] as string[], + security: [ + { + bearerAuth: [] + } + ] as Array<{ [key: string]: [] }>, + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getAddStrategyGitHubOAuth2Service: FastifyPluginAsync = async ( + fastify +) => { + await fastify.register(authenticateUser) + + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/github/add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + if (request.user == null) { + throw fastify.httpErrors.forbidden() + } + const { redirectURI } = request.query + const redirectCallback = `${API_URL}/users/oauth2/github/callback-add-strategy?redirectURI=${redirectURI}` + const url = `${GITHUB_BASE_URL}/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&state=${request.user.accessToken}&redirect_uri=${redirectCallback}` + reply.statusCode = 200 + return url + } + }) +} diff --git a/src/services/users/oauth2/github/callback-add-strategy/get.ts b/src/services/users/oauth2/github/callback-add-strategy/get.ts new file mode 100644 index 0000000..24f2ecf --- /dev/null +++ b/src/services/users/oauth2/github/callback-add-strategy/get.ts @@ -0,0 +1,56 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { githubStrategy, getGitHubUserData } from '../__utils__/utils.js' +import { buildQueryURL } from '../../../../../tools/utils/buildQueryURL.js' +import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + code: Type.String(), + state: Type.String(), + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'GitHub OAuth2 - callback-add-strategy', + tags: ['users'] as string[], + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getCallbackAddStrategyGitHubOAuth2Service: FastifyPluginAsync = + async (fastify) => { + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/github/callback-add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + const { redirectURI, code, state: accessToken } = request.query + const userRequest = await getUserWithBearerToken( + `Bearer ${accessToken}` + ) + const githubUser = await getGitHubUserData( + code, + `${API_URL}/users/oauth2/github/callback-add-strategy?redirectURI=${redirectURI}` + ) + const message = await githubStrategy.callbackAddStrategy( + { + name: githubUser.name, + id: githubUser.id + }, + userRequest + ) + return await reply.redirect(buildQueryURL(redirectURI, { message })) + } + }) + } diff --git a/src/services/users/oauth2/google/add-strategy/get.ts b/src/services/users/oauth2/google/add-strategy/get.ts new file mode 100644 index 0000000..df1e050 --- /dev/null +++ b/src/services/users/oauth2/google/add-strategy/get.ts @@ -0,0 +1,53 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { GOOGLE_BASE_URL, GOOGLE_CLIENT_ID } from '../__utils__/utils.js' +import authenticateUser from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'Google OAuth2 - add-strategy', + tags: ['users'] as string[], + security: [ + { + bearerAuth: [] + } + ] as Array<{ [key: string]: [] }>, + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getAddStrategyGoogleOAuth2Service: FastifyPluginAsync = async ( + fastify +) => { + await fastify.register(authenticateUser) + + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/google/add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + if (request.user == null) { + throw fastify.httpErrors.forbidden() + } + const { redirectURI } = request.query + const redirectCallback = `${API_URL}/users/oauth2/google/callback-add-strategy?redirectURI=${redirectURI}` + const url = `${GOOGLE_BASE_URL}?client_id=${GOOGLE_CLIENT_ID}&state=${request.user.accessToken}&redirect_uri=${redirectCallback}&response_type=code&scope=profile&access_type=online` + reply.statusCode = 200 + return url + } + }) +} diff --git a/src/services/users/oauth2/google/callback-add-strategy/get.ts b/src/services/users/oauth2/google/callback-add-strategy/get.ts new file mode 100644 index 0000000..f4c6593 --- /dev/null +++ b/src/services/users/oauth2/google/callback-add-strategy/get.ts @@ -0,0 +1,56 @@ +import { Static, Type } from '@sinclair/typebox' +import { FastifyPluginAsync, FastifySchema } from 'fastify' + +import { API_URL } from '../../../../../tools/configurations/index.js' +import { fastifyErrors } from '../../../../../models/utils.js' +import { googleStrategy, getGoogleUserData } from '../__utils__/utils.js' +import { buildQueryURL } from '../../../../../tools/utils/buildQueryURL.js' +import { getUserWithBearerToken } from '../../../../../tools/plugins/authenticateUser.js' + +const querySchema = Type.Object({ + code: Type.String(), + state: Type.String(), + redirectURI: Type.String({ format: 'uri-reference' }) +}) + +type QuerySchemaType = Static + +const getServiceSchema: FastifySchema = { + description: 'Google OAuth2 - callback-add-strategy', + tags: ['users'] as string[], + querystring: querySchema, + response: { + 200: Type.String(), + 400: fastifyErrors[400], + 500: fastifyErrors[500] + } +} as const + +export const getCallbackAddStrategyGoogleOAuth2Service: FastifyPluginAsync = + async (fastify) => { + await fastify.route<{ + Querystring: QuerySchemaType + }>({ + method: 'GET', + url: '/users/oauth2/google/callback-add-strategy', + schema: getServiceSchema, + handler: async (request, reply) => { + const { redirectURI, code, state: accessToken } = request.query + const userRequest = await getUserWithBearerToken( + `Bearer ${accessToken}` + ) + const googleUser = await getGoogleUserData( + code, + `${API_URL}/users/oauth2/google/callback-add-strategy?redirectURI=${redirectURI}` + ) + const message = await googleStrategy.callbackAddStrategy( + { + name: googleUser.name, + id: googleUser.id + }, + userRequest + ) + return await reply.redirect(buildQueryURL(redirectURI, { message })) + } + }) + } diff --git a/src/tools/configurations/index.ts b/src/tools/configurations/index.ts index fdfea8a..e72bdf0 100644 --- a/src/tools/configurations/index.ts +++ b/src/tools/configurations/index.ts @@ -7,6 +7,10 @@ dotenv.config() export const PORT = parseInt(process.env.PORT ?? '8080', 10) export const HOST = process.env.HOST ?? '0.0.0.0' export const API_URL = process.env.API_URL ?? `http://${HOST}:${PORT}` +export const FILE_UPLOADS_API_URL = + process.env.FILE_UPLOADS_API_URL ?? 'http://localhost:8000' +export const FILE_UPLOADS_API_KEY = + process.env.FILE_UPLOADS_API_KEY ?? 'apiKeySecret' export const JWT_ACCESS_SECRET = process.env.JWT_ACCESS_SECRET ?? 'accessTokenSecret' export const JWT_REFRESH_SECRET = @@ -19,17 +23,3 @@ export const ROOT_URL = new URL('../', SRC_URL) export const EMAIL_URL = new URL('./email/', ROOT_URL) export const EMAIL_TEMPLATE_URL = new URL('./email-template.ejs', EMAIL_URL) export const EMAIL_LOCALES_URL = new URL('./locales/', EMAIL_URL) -export const UPLOADS_URL = new URL('./uploads/', ROOT_URL) - -export const SUPPORTED_IMAGE_MIMETYPE = [ - 'image/png', - 'image/jpg', - 'image/jpeg', - 'image/gif' -] - -/** in megabytes */ -export const MAXIMUM_IMAGE_SIZE = 10 - -/** in megabytes */ -export const MAXIMUM_FILE_SIZE = 100 diff --git a/src/tools/configurations/swaggerOptions.ts b/src/tools/configurations/swaggerOptions.ts index a89dc18..0812e4f 100644 --- a/src/tools/configurations/swaggerOptions.ts +++ b/src/tools/configurations/swaggerOptions.ts @@ -10,7 +10,7 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = { routePrefix: '/documentation', openapi: { info: { - title: 'Thream', + title: packageJSON.name, description: packageJSON.description, version: packageJSON.version }, diff --git a/src/tools/utils/uploadFile.ts b/src/tools/utils/uploadFile.ts index a14b88b..098d5cc 100644 --- a/src/tools/utils/uploadFile.ts +++ b/src/tools/utils/uploadFile.ts @@ -1,21 +1,29 @@ import fs from 'node:fs' -import { URL } from 'node:url' -import { randomUUID } from 'node:crypto' +import axios from 'axios' +import FormData from 'form-data' import { FastifyInstance, FastifyRequest } from 'fastify' import { Multipart } from 'fastify-multipart' -import { ROOT_URL } from '../configurations/index.js' +import { + FILE_UPLOADS_API_KEY, + FILE_UPLOADS_API_URL +} from '../configurations/index.js' + +export const fileUploadAPI = axios.create({ + baseURL: FILE_UPLOADS_API_URL, + headers: { + 'Content-Type': 'application/json' + } +}) + +/** in megabytes */ +export const MAXIMUM_FILE_SIZE = 100 export interface UploadFileOptions { folderInUploadsFolder: 'guilds' | 'messages' | 'users' request: FastifyRequest fastify: FastifyInstance - - /** in megabytes */ - maximumFileSize: number - - supportedFileMimetype?: string[] } export interface UploadFileResult { @@ -26,43 +34,42 @@ export interface UploadFileResult { export const uploadFile = async ( options: UploadFileOptions ): Promise => { - const { - fastify, - request, - folderInUploadsFolder, - maximumFileSize, - supportedFileMimetype - } = options + const { fastify, request, folderInUploadsFolder } = options let files: Multipart[] = [] try { files = await request.saveRequestFiles({ limits: { files: 1, - fileSize: maximumFileSize * 1024 * 1024 + fileSize: MAXIMUM_FILE_SIZE * 1024 * 1024 } }) } catch (error) { throw fastify.httpErrors.requestHeaderFieldsTooLarge( - `File should be less than ${maximumFileSize}mb.` + `File should be less than ${MAXIMUM_FILE_SIZE}mb.` ) } if (files.length !== 1) { throw fastify.httpErrors.badRequest('You must upload at most one file.') } const file = files[0] - if ( - supportedFileMimetype != null && - !supportedFileMimetype.includes(file.mimetype) - ) { - throw fastify.httpErrors.badRequest( - `The file must have a valid type (${supportedFileMimetype.join(', ')}).` + const formData = new FormData() + formData.append('file', fs.createReadStream(file.filepath)) + try { + const response = await fileUploadAPI.post( + `/uploads/${folderInUploadsFolder}`, + formData, + { + headers: { + 'X-API-Key': FILE_UPLOADS_API_KEY, + ...formData.getHeaders() + } + } + ) + return { pathToStoreInDatabase: response.data, mimetype: file.mimetype } + } catch (error: any) { + throw fastify.httpErrors.createError( + error.response.data.statusCode, + error.response.data.message ) } - const splitedMimetype = file.mimetype.split('/') - const fileExtension = splitedMimetype[1] - const filePath = `uploads/${folderInUploadsFolder}/${randomUUID()}.${fileExtension}` - const fileURL = new URL(filePath, ROOT_URL) - const pathToStoreInDatabase = `/${filePath}` - await fs.promises.copyFile(file.filepath, fileURL) - return { pathToStoreInDatabase, mimetype: file.mimetype } } diff --git a/uploads/guilds/.gitkeep b/uploads/guilds/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/uploads/messages/.gitkeep b/uploads/messages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/uploads/users/.gitkeep b/uploads/users/.gitkeep deleted file mode 100644 index e69de29..0000000