feat(scripts): add delete dead uploaded files
This commit is contained in:
		| @@ -21,7 +21,7 @@ All work on **Thream/api** happens directly on [GitHub](https://github.com/Threa | ||||
|  | ||||
| - **Please first discuss** the change you wish to make via issues. | ||||
|  | ||||
| - Ensure your code respect `eslint` and `prettier`. | ||||
| - Ensure your code respect linting. | ||||
|  | ||||
| - Make sure your **code passes the tests**. | ||||
|  | ||||
| @@ -70,6 +70,7 @@ git commit -m "fix(services): should emit events to connected users" | ||||
| ├── prisma | ||||
| └── src | ||||
|     ├── models | ||||
|     ├── scripts | ||||
|     ├── services | ||||
|     ├── tools | ||||
|     └── typings | ||||
| @@ -81,6 +82,7 @@ git commit -m "fix(services): should emit events to connected users" | ||||
| - `prisma` : contains the prisma schema and migrations | ||||
| - `src` : all source files | ||||
|   - `models` : models that represent tables in database as JSON schema | ||||
|   - `scripts` : scripts | ||||
|   - `services` : all REST API endpoints | ||||
|   - `tools` : configs and utilities | ||||
|   - `typings` : types gloablly used in the project | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
| FROM node:16.13.2 AS dependencies | ||||
| FROM node:16.14.0 AS dependencies | ||||
| WORKDIR /usr/src/app | ||||
| COPY ./package*.json ./ | ||||
| RUN npm install | ||||
|  | ||||
| FROM node:16.13.2 AS builder | ||||
| FROM node:16.14.0 AS builder | ||||
| WORKDIR /usr/src/app | ||||
| COPY --from=dependencies /usr/src/app/node_modules ./node_modules | ||||
| COPY ./ ./ | ||||
| RUN npm run prisma:generate && npm run build | ||||
|  | ||||
| FROM node:16.13.2 AS runner | ||||
| FROM node:16.14.0 AS runner | ||||
| WORKDIR /usr/src/app | ||||
| ENV NODE_ENV=production | ||||
| COPY --from=builder /usr/src/app/node_modules ./node_modules | ||||
|   | ||||
| @@ -16,9 +16,7 @@ | ||||
|  | ||||
| ## 📜 About | ||||
|  | ||||
| Thream's application programming interface to stay close with your friends and communities. | ||||
|  | ||||
| This project was bootstrapped with [create-fullstack-app](https://github.com/Divlo/create-fullstack-app). | ||||
| Thream's Application Programming Interface (API) to stay close with your friends and communities. | ||||
|  | ||||
| ## ⚙️ Getting Started | ||||
|  | ||||
|   | ||||
							
								
								
									
										695
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										695
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -16,6 +16,7 @@ | ||||
|     "start": "cross-env NODE_ENV=production node build/index.js", | ||||
|     "dev": "concurrently -k -n \"TypeScript,Node\" -p \"[{name}]\" -c \"blue,green\" \"swc ./src --out-dir ./build --watch\" \"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\"", | ||||
| @@ -46,7 +47,7 @@ | ||||
|     "fastify-rate-limit": "5.7.2", | ||||
|     "fastify-sensible": "3.1.2", | ||||
|     "fastify-static": "4.5.0", | ||||
|     "fastify-swagger": "4.15.0", | ||||
|     "fastify-swagger": "4.17.0", | ||||
|     "fastify-url-data": "3.0.3", | ||||
|     "http-errors": "2.0.0", | ||||
|     "jsonwebtoken": "8.5.1", | ||||
| @@ -60,7 +61,7 @@ | ||||
|     "@commitlint/config-conventional": "16.2.1", | ||||
|     "@saithodev/semantic-release-backmerge": "2.1.2", | ||||
|     "@swc/cli": "0.1.55", | ||||
|     "@swc/core": "1.2.146", | ||||
|     "@swc/core": "1.2.148", | ||||
|     "@swc/jest": "0.2.20", | ||||
|     "@types/bcryptjs": "2.4.2", | ||||
|     "@types/busboy": "1.3.0", | ||||
| @@ -76,7 +77,7 @@ | ||||
|     "cross-env": "7.0.3", | ||||
|     "editorconfig-checker": "4.0.2", | ||||
|     "eslint": "8.10.0", | ||||
|     "eslint-config-prettier": "8.4.0", | ||||
|     "eslint-config-prettier": "8.5.0", | ||||
|     "eslint-config-conventions": "1.1.0", | ||||
|     "eslint-plugin-import": "2.25.4", | ||||
|     "eslint-plugin-prettier": "4.0.0", | ||||
| @@ -86,7 +87,7 @@ | ||||
|     "jest": "27.5.1", | ||||
|     "jest-mock-extended": "2.0.4", | ||||
|     "jest-ts-webcompat-resolver": "1.0.0", | ||||
|     "lint-staged": "12.3.4", | ||||
|     "lint-staged": "12.3.5", | ||||
|     "markdownlint-cli": "0.31.1", | ||||
|     "nodemon": "2.0.15", | ||||
|     "plop": "3.0.5", | ||||
|   | ||||
| @@ -3,13 +3,15 @@ import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended' | ||||
|  | ||||
| import prisma from '../tools/database/prisma.js' | ||||
|  | ||||
| jest.mock('nodemailer', () => ({ | ||||
|   createTransport: () => { | ||||
|     return { | ||||
|       sendMail: jest.fn(async () => {}) | ||||
| jest.mock('nodemailer', () => { | ||||
|   return { | ||||
|     createTransport: () => { | ||||
|       return { | ||||
|         sendMail: jest.fn(async () => {}) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| })) | ||||
| }) | ||||
|  | ||||
| jest.mock('../tools/database/prisma.js', () => ({ | ||||
|   __esModule: true, | ||||
|   | ||||
							
								
								
									
										58
									
								
								src/scripts/delete-dead-uploaded-files.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/scripts/delete-dead-uploaded-files.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| 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) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| const main = async (): Promise<void> => { | ||||
|   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 } | ||||
|     }) | ||||
|   }) | ||||
| } | ||||
|  | ||||
| main().catch((error) => { | ||||
|   console.error(error) | ||||
|   process.exit(1) | ||||
| }) | ||||
							
								
								
									
										77
									
								
								src/tools/plugins/__test__/authenticateUser.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/tools/plugins/__test__/authenticateUser.test.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| import httpErrors from 'http-errors' | ||||
| import jwt from 'jsonwebtoken' | ||||
|  | ||||
| import { userExample } from '../../../models/User.js' | ||||
| import { prismaMock } from '../../../__test__/setup.js' | ||||
| import { getUserWithBearerToken } from '../authenticateUser.js' | ||||
|  | ||||
| const { Unauthorized, Forbidden, BadRequest } = httpErrors | ||||
|  | ||||
| describe('tools/plugins/authenticateUser - getUserWithBearerToken', () => { | ||||
|   afterEach(() => { | ||||
|     jest.clearAllMocks() | ||||
|   }) | ||||
|  | ||||
|   it('shoulds succeeds with the right information', async () => { | ||||
|     prismaMock.user.findUnique.mockResolvedValue(userExample) | ||||
|     const currentStrategy = 'local' | ||||
|     jwt.verify = jest.fn<any, any[]>((() => { | ||||
|       return { id: userExample.id, currentStrategy } | ||||
|     }) as any) | ||||
|     const userWithBearerToken = await getUserWithBearerToken('Bearer token') | ||||
|     expect(userWithBearerToken.current.id).toEqual(userExample.id) | ||||
|     expect(userWithBearerToken.current.name).toEqual(userExample.name) | ||||
|     expect(userWithBearerToken.accessToken).toEqual('token') | ||||
|     expect(userWithBearerToken.currentStrategy).toEqual(currentStrategy) | ||||
|   }) | ||||
|  | ||||
|   it('shoulds throws `Unauthorized` if `bearerToken` is not a string', async () => { | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken(undefined) | ||||
|     ).rejects.toThrow(Unauthorized) | ||||
|   }) | ||||
|  | ||||
|   it('shoulds throws `Unauthorized` if `bearerToken` is not to the right format: `"Bearer token"`', async () => { | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken('Bearer') | ||||
|     ).rejects.toThrow(Unauthorized) | ||||
|     await expect(async () => await getUserWithBearerToken('')).rejects.toThrow( | ||||
|       Unauthorized | ||||
|     ) | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken('Bearer token token2') | ||||
|     ).rejects.toThrow(Unauthorized) | ||||
|   }) | ||||
|  | ||||
|   it('shoulds throws `Forbidden` if invalid `bearerToken` by `jwt.verify`', async () => { | ||||
|     jwt.verify = jest.fn<any, any[]>((() => { | ||||
|       throw new Error('Invalid token') | ||||
|     }) as any) | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken('Bearer token') | ||||
|     ).rejects.toThrow(Forbidden) | ||||
|   }) | ||||
|  | ||||
|   it("shoulds throws `Forbidden` if the user doesn't exist", async () => { | ||||
|     prismaMock.user.findUnique.mockResolvedValue(null) | ||||
|     jwt.verify = jest.fn<any, any[]>((() => { | ||||
|       return { id: userExample.id } | ||||
|     }) as any) | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken('Bearer token') | ||||
|     ).rejects.toThrow(Forbidden) | ||||
|   }) | ||||
|  | ||||
|   it('shoulds throws `BadRequest` if the user account is not confirmed', async () => { | ||||
|     prismaMock.user.findUnique.mockResolvedValue({ | ||||
|       ...userExample, | ||||
|       isConfirmed: false | ||||
|     }) | ||||
|     jwt.verify = jest.fn<any, any[]>((() => { | ||||
|       return { id: userExample.id, currentStrategy: 'local' } | ||||
|     }) as any) | ||||
|     await expect( | ||||
|       async () => await getUserWithBearerToken('Bearer token') | ||||
|     ).rejects.toThrow(BadRequest) | ||||
|   }) | ||||
| }) | ||||
		Reference in New Issue
	
	Block a user