fix: file upload and OAuth2 (#10)
This commit is contained in:
		| @@ -8,6 +8,8 @@ services: | ||||
|     volumes: | ||||
|       - '..:/workspace:cached' | ||||
|     command: 'sleep infinity' | ||||
|     extra_hosts: | ||||
|       - 'host.docker.internal:host-gateway' | ||||
|  | ||||
|   thream-database: | ||||
|     image: 'postgres:14.2' | ||||
|   | ||||
							
								
								
									
										40
									
								
								.env.example
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								.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 | ||||
|   | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -35,4 +35,3 @@ npm-debug.log* | ||||
|  | ||||
| # misc | ||||
| .DS_Store | ||||
| /uploads | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										167
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										167
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -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": { | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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<unknown | null> | ||||
| ): Promise<void> => { | ||||
|   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 } | ||||
|   }) | ||||
| }) | ||||
| @@ -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: { | ||||
|   | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
| @@ -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<typeof parameters> | ||||
|  | ||||
| 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)) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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) | ||||
| } | ||||
| @@ -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<typeof parameters> | ||||
|  | ||||
| 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)) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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<typeof parameters> | ||||
|  | ||||
| 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)) | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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 }, | ||||
|   | ||||
| @@ -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) | ||||
| } | ||||
|   | ||||
							
								
								
									
										53
									
								
								src/services/users/oauth2/discord/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/services/users/oauth2/discord/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 })) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
							
								
								
									
										53
									
								
								src/services/users/oauth2/github/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/services/users/oauth2/github/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 })) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
							
								
								
									
										53
									
								
								src/services/users/oauth2/google/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/services/users/oauth2/google/add-strategy/get.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 | ||||
|     } | ||||
|   }) | ||||
| } | ||||
| @@ -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<typeof querySchema> | ||||
|  | ||||
| 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 })) | ||||
|       } | ||||
|     }) | ||||
|   } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -10,7 +10,7 @@ export const swaggerOptions: FastifyDynamicSwaggerOptions = { | ||||
|   routePrefix: '/documentation', | ||||
|   openapi: { | ||||
|     info: { | ||||
|       title: 'Thream', | ||||
|       title: packageJSON.name, | ||||
|       description: packageJSON.description, | ||||
|       version: packageJSON.version | ||||
|     }, | ||||
|   | ||||
| @@ -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<UploadFileResult> => { | ||||
|   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 } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user