From ae379b097fe2e90729a825b2b32ad3d9e071eac2 Mon Sep 17 00:00:00 2001 From: compilando Date: Sun, 2 Mar 2025 13:04:36 +0100 Subject: [PATCH] better logging ant throtling --- .vscode/launch.json | 22 +++ package.json | 21 ++- pnpm-lock.yaml | 255 +++++++++++++++++++++++++++++++++++ src/app.controller.ts | 2 +- src/app.module.ts | 23 ++-- src/logger/logger.service.ts | 69 ++++++++++ src/main.ts | 22 ++- src/shared/shared.module.ts | 8 +- src/shared/shared.service.ts | 34 ++++- 9 files changed, 429 insertions(+), 27 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/logger/logger.service.ts diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6f9e9b0 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Debug NestJS", + "runtimeExecutable": "pnpm", + "runtimeArgs": [ + "start:debug" + ], + "console": "integratedTerminal", + "restart": true, + "autoAttachChildProcesses": true, + "sourceMaps": true, + "envFile": "${workspaceFolder}/.env", + "skipFiles": [ + "/**" + ] + } + ] +} \ No newline at end of file diff --git a/package.json b/package.json index e3cc102..2944072 100644 --- a/package.json +++ b/package.json @@ -14,24 +14,29 @@ "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", - "test:watch": "jest --watch", "test:cov": "jest --coverage", + "test:watch": "jest --watch", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@nestjs/cache-manager": "^3.0.0", "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^8.1.0", "@octokit/rest": "^18.12.0", "@types/multer": "^1.4.12", + "cache-manager": "^6.4.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.7", + "helmet": "^8.0.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "simple-git": "^3.27.0", "swagger-ui-express": "^5.0.1", - "uuid": "^9.0.0" + "uuid": "^9.0.0", + "winston": "^3.17.0", + "winston-daily-rotate-file": "^5.0.0" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -72,5 +77,15 @@ "@nestjs/core", "@scarf/scarf" ] + }, + "jest": { + "coverageThreshold": { + "global": { + "branches": 80, + "functions": 80, + "lines": 80, + "statements": 80 + } + } } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d475c2..fa245ff 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@nestjs/cache-manager': + specifier: ^3.0.0 + version: 3.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(cache-manager@6.4.0)(rxjs@7.8.2) '@nestjs/platform-express': specifier: ^10.0.0 version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15) @@ -20,6 +23,9 @@ importers: '@types/multer': specifier: ^1.4.12 version: 1.4.12 + cache-manager: + specifier: ^6.4.0 + version: 6.4.0 class-transformer: specifier: ^0.5.1 version: 0.5.1 @@ -29,6 +35,9 @@ importers: dotenv: specifier: ^16.4.7 version: 16.4.7 + helmet: + specifier: ^8.0.0 + version: 8.0.0 reflect-metadata: specifier: ^0.2.0 version: 0.2.2 @@ -44,6 +53,12 @@ importers: uuid: specifier: ^9.0.0 version: 9.0.1 + winston: + specifier: ^3.17.0 + version: 3.17.0 + winston-daily-rotate-file: + specifier: ^5.0.0 + version: 5.0.0(winston@3.17.0) devDependencies: '@nestjs/cli': specifier: ^10.0.0 @@ -331,10 +346,17 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + '@eslint-community/eslint-utils@4.4.1': resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -468,6 +490,9 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + '@keyv/serialize@1.0.3': + resolution: {integrity: sha512-qnEovoOp5Np2JDGonIDL6Ayihw0RhnRh6vxPuHo4RDn1UOzwEo4AeIfpL6UGIrsceWrCMiVPgwRjbHu4vYFc3g==} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -485,6 +510,14 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@nestjs/cache-manager@3.0.0': + resolution: {integrity: sha512-csKvxHSQWfC0OiDo0bNEhLqrmYDopHEvRyC81MxV9xFj1AO+rOKocpHa4M1ZGH//6uKFIPGN9oiR0mvZY77APA==} + peerDependencies: + '@nestjs/common': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@nestjs/core': ^9.0.0 || ^10.0.0 || ^11.0.0 + cache-manager: '>=6' + rxjs: ^7.8.1 + '@nestjs/cli@10.4.9': resolution: {integrity: sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==} engines: {node: '>= 16.14'} @@ -810,6 +843,9 @@ packages: '@types/supertest@6.0.2': resolution: {integrity: sha512-137ypx2lk/wTQbW6An6safu9hXmajAifU/s7szAHLN/FeIm5w7yR0Wkl9fdJMRSHwOn4HLAI0DaB2TOORuhPDg==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@types/uuid@10.0.0': resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} @@ -1103,6 +1139,9 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -1111,6 +1150,9 @@ packages: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} engines: {node: '>= 0.8'} + cache-manager@6.4.0: + resolution: {integrity: sha512-eUmPyVqQYzWCt7hx1QrYzQ7oC3MGKM1etxxe8zuq1o7IB4NzdBeWcUGDSWYahaI8fkd538SEZRGadyZWQfvOzQ==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1209,13 +1251,28 @@ packages: collect-v8-coverage@1.0.2: resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1417,6 +1474,9 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -1597,6 +1657,9 @@ packages: fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + figures@3.2.0: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} @@ -1605,6 +1668,9 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + file-stream-rotator@0.6.1: + resolution: {integrity: sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==} + filelist@1.0.4: resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} @@ -1631,6 +1697,9 @@ packages: flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1762,6 +1831,10 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + helmet@8.0.0: + resolution: {integrity: sha512-VyusHLEIIO5mjQPUI1wpOAEu+wl6Q0998jzTxqUYGE45xCIcAxy3MsbEK/yyJUJ3ADeMoB6MornPH6GMWAf+Pw==} + engines: {node: '>=18.0.0'} + hexoid@2.0.0: resolution: {integrity: sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==} engines: {node: '>=8'} @@ -1823,6 +1896,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} @@ -2094,10 +2170,16 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + keyv@5.3.0: + resolution: {integrity: sha512-XMBcWGBqH1j04AzjzdIulcsAKr5MaGlYC/N2PLyxdwTrEqVhQnuEIP5h1TPpa5UUcPOH1yVJ+xvhYJ2QAEv94w==} + kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + leo-profanity@1.7.0: resolution: {integrity: sha512-88j1R08jrQzOib9Yxk4nxrzMlrHJi3DzFzAmv0L4APQ+ciGEfJ1rftVEvFjoqL0m+0KGFL3csQGRlxXGnYrA7w==} @@ -2140,6 +2222,10 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} + logform@2.7.0: + resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==} + engines: {node: '>= 12.0.0'} + lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} @@ -2237,6 +2323,9 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -2297,6 +2386,10 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -2308,6 +2401,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -2558,6 +2654,10 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -2630,6 +2730,9 @@ packages: simple-git@3.27.0: resolution: {integrity: sha512-ivHoFS9Yi9GY49ogc6/YAi3Fl9ROnF4VyubNylgCkA+RVqLaKWnDSzXOVzya8csELIaWaYNutsEuAhZrtOjozA==} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2658,6 +2761,9 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + stack-utils@2.0.6: resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} engines: {node: '>=10'} @@ -2781,6 +2887,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -2809,6 +2918,10 @@ packages: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-api-utils@2.0.1: resolution: {integrity: sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==} engines: {node: '>=18.12'} @@ -2996,6 +3109,20 @@ packages: engines: {node: '>= 8'} hasBin: true + winston-daily-rotate-file@5.0.0: + resolution: {integrity: sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==} + engines: {node: '>=8'} + peerDependencies: + winston: ^3 + + winston-transport@4.9.0: + resolution: {integrity: sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==} + engines: {node: '>= 12.0.0'} + + winston@3.17.0: + resolution: {integrity: sha512-DLiFIXYC5fMPxaRg832S6F5mJYvePtmO5G9v9IgUFPhXm9/GkXarH/TUrBAVzhTCzAj9anE/+GjrgXp/54nOgw==} + engines: {node: '>= 12.0.0'} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3292,10 +3419,18 @@ snapshots: '@colors/colors@1.5.0': optional: true + '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + '@eslint-community/eslint-utils@4.4.1(eslint@8.57.1)': dependencies: eslint: 8.57.1 @@ -3539,6 +3674,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 + '@keyv/serialize@1.0.3': + dependencies: + buffer: 6.0.3 + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.4.0 @@ -3555,6 +3694,13 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@nestjs/cache-manager@3.0.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.15)(cache-manager@6.4.0)(rxjs@7.8.2)': + dependencies: + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.2) + cache-manager: 6.4.0 + rxjs: 7.8.2 + '@nestjs/cli@10.4.9': dependencies: '@angular-devkit/core': 17.3.11(chokidar@3.6.0) @@ -3983,6 +4129,8 @@ snapshots: '@types/methods': 1.1.4 '@types/superagent': 8.1.9 + '@types/triple-beam@1.3.5': {} + '@types/uuid@10.0.0': {} '@types/validator@13.12.2': {} @@ -4373,12 +4521,21 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 bytes@3.1.2: {} + cache-manager@6.4.0: + dependencies: + keyv: 5.3.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4469,12 +4626,33 @@ snapshots: collect-v8-coverage@1.0.2: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4643,6 +4821,8 @@ snapshots: emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encodeurl@1.0.2: {} encodeurl@2.0.0: {} @@ -4872,6 +5052,8 @@ snapshots: dependencies: bser: 2.1.1 + fecha@4.2.3: {} + figures@3.2.0: dependencies: escape-string-regexp: 1.0.5 @@ -4880,6 +5062,10 @@ snapshots: dependencies: flat-cache: 3.2.0 + file-stream-rotator@0.6.1: + dependencies: + moment: 2.30.1 + filelist@1.0.4: dependencies: minimatch: 5.1.6 @@ -4918,6 +5104,8 @@ snapshots: flatted@3.3.3: {} + fn.name@1.1.0: {} + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -5059,6 +5247,8 @@ snapshots: dependencies: function-bind: 1.1.2 + helmet@8.0.0: {} + hexoid@2.0.0: {} html-escaper@2.0.2: {} @@ -5140,6 +5330,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 @@ -5584,8 +5776,14 @@ snapshots: dependencies: json-buffer: 3.0.1 + keyv@5.3.0: + dependencies: + '@keyv/serialize': 1.0.3 + kleur@3.0.3: {} + kuler@2.0.0: {} + leo-profanity@1.7.0: optionalDependencies: french-badwords-list: 1.0.7 @@ -5623,6 +5821,15 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 + logform@2.7.0: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.5.0 + triple-beam: 1.4.1 + lru-cache@10.4.3: {} lru-cache@5.1.1: @@ -5698,6 +5905,8 @@ snapshots: dependencies: minimist: 1.2.8 + moment@2.30.1: {} + ms@2.0.0: {} ms@2.1.3: {} @@ -5744,6 +5953,8 @@ snapshots: object-assign@4.1.1: {} + object-hash@3.0.0: {} + object-inspect@1.13.4: {} on-finished@2.4.1: @@ -5754,6 +5965,10 @@ snapshots: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -5980,6 +6195,8 @@ snapshots: safe-buffer@5.2.1: {} + safe-stable-stringify@2.5.0: {} + safer-buffer@2.1.2: {} schema-utils@3.3.0: @@ -6087,6 +6304,10 @@ snapshots: transitivePeerDependencies: - supports-color + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -6109,6 +6330,8 @@ snapshots: sprintf-js@1.0.3: {} + stack-trace@0.0.10: {} + stack-utils@2.0.6: dependencies: escape-string-regexp: 2.0.0 @@ -6233,6 +6456,8 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-hex@1.0.0: {} + text-table@0.2.0: {} through@2.3.8: {} @@ -6253,6 +6478,8 @@ snapshots: tree-kill@1.2.2: {} + triple-beam@1.4.1: {} + ts-api-utils@2.0.1(typescript@5.7.3): dependencies: typescript: 5.7.3 @@ -6438,6 +6665,34 @@ snapshots: dependencies: isexe: 2.0.0 + winston-daily-rotate-file@5.0.0(winston@3.17.0): + dependencies: + file-stream-rotator: 0.6.1 + object-hash: 3.0.0 + triple-beam: 1.4.1 + winston: 3.17.0 + winston-transport: 4.9.0 + + winston-transport@4.9.0: + dependencies: + logform: 2.7.0 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.17.0: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.6 + is-stream: 2.0.1 + logform: 2.7.0 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.5.0 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.9.0 + word-wrap@1.2.5: {} wrap-ansi@6.2.0: diff --git a/src/app.controller.ts b/src/app.controller.ts index 4454e3e..dcd78e1 100644 --- a/src/app.controller.ts +++ b/src/app.controller.ts @@ -3,5 +3,5 @@ import { AppService } from './app.service'; @Controller() export class AppController { - constructor(private readonly appService: AppService) {} + constructor(private readonly appService: AppService) { } } diff --git a/src/app.module.ts b/src/app.module.ts index 23c3ce2..2a9286a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -5,28 +5,23 @@ import { APP_GUARD } from '@nestjs/core'; import { GitHubModule } from './github/github.module'; import { SharedModule } from './shared/shared.module'; import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.filter'; +import { CacheModule } from '@nestjs/cache-manager'; @Module({ imports: [ + CacheModule.register({ + ttl: 300, // 5 minutes + max: 100, // maximum number of items in cache + }), ConfigModule.forRoot({ isGlobal: true, }), ThrottlerModule.forRoot([ { - name: 'short', - ttl: 1000, - limit: 3, - }, - { - name: 'medium', - ttl: 10000, - limit: 20, - }, - { - name: 'long', + name: 'ip', ttl: 60000, - limit: 100, - }, + limit: 20, + } ]), GitHubModule, SharedModule, @@ -39,4 +34,4 @@ import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.f }, ], }) -export class AppModule {} +export class AppModule { } diff --git a/src/logger/logger.service.ts b/src/logger/logger.service.ts new file mode 100644 index 0000000..f8db43c --- /dev/null +++ b/src/logger/logger.service.ts @@ -0,0 +1,69 @@ +import { Injectable, LoggerService as NestLoggerService } from '@nestjs/common'; +import * as winston from 'winston'; +import 'winston-daily-rotate-file'; + +@Injectable() +export class LoggerService implements NestLoggerService { + private logger: winston.Logger; + + constructor() { + const logDir = 'logs'; + + this.logger = winston.createLogger({ + level: 'info', + format: winston.format.combine( + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.json() + ), + defaultMeta: { service: 'zen-rices-api' }, + transports: [ + new winston.transports.DailyRotateFile({ + filename: `${logDir}/error-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + level: 'error', + }), + new winston.transports.DailyRotateFile({ + filename: `${logDir}/combined-%DATE%.log`, + datePattern: 'YYYY-MM-DD', + zippedArchive: true, + maxSize: '20m', + maxFiles: '14d', + }), + ], + }); + + // Add console transport in development + if (process.env.NODE_ENV !== 'production') { + this.logger.add(new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple() + ), + })); + } + } + + log(message: string, context?: string) { + this.logger.info(message, { context }); + } + + error(message: string, trace?: string, context?: string) { + this.logger.error(message, { trace, context }); + } + + warn(message: string, context?: string) { + this.logger.warn(message, { context }); + } + + debug(message: string, context?: string) { + this.logger.debug(message, { context }); + } + + verbose(message: string, context?: string) { + this.logger.verbose(message, { context }); + } +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index ee730b1..2bd7983 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { ThrottlerExceptionFilter } from './common/filters/throttler-exception.filter'; import { HttpExceptionFilter } from './filters/http-exception.filter'; +import helmet from 'helmet'; async function bootstrap() { const app = await NestFactory.create(AppModule); @@ -12,18 +13,33 @@ async function bootstrap() { app.useGlobalFilters(new HttpExceptionFilter()); app.useGlobalFilters(new ThrottlerExceptionFilter()); + app.use(helmet({ + crossOriginResourcePolicy: { policy: "cross-origin" }, + contentSecurityPolicy: { + directives: { + defaultSrc: [`'self'`], + styleSrc: [`'self'`, `'unsafe-inline'`], + imgSrc: [`'self'`, 'data:', 'https:'], + scriptSrc: [`'self'`, `'unsafe-inline'`, `'unsafe-eval'`], + }, + }, + })); + const config = new DocumentBuilder() - .setTitle('Rices API') + .setTitle('Zen Rices API') .setDescription('Zen Rices API management (Zen Browser)') .setVersion('1.0') - // To manage the API with Swagger, we need to add the bearer token - // .addBearerAuth() + .addTag('rices') + .addTag('workspaces') + .addBearerAuth() + .addApiKey() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api', app, document); await app.listen(3000); + console.log('API running on http://localhost:3000'); console.log('Swagger docs on http://localhost:3000/api'); } diff --git a/src/shared/shared.module.ts b/src/shared/shared.module.ts index dff4a49..b8a9877 100644 --- a/src/shared/shared.module.ts +++ b/src/shared/shared.module.ts @@ -1,11 +1,12 @@ // src/rices/rices.module.ts -import { Module } from '@nestjs/common'; +import { Module, } from '@nestjs/common'; import { SharedService } from './shared.service'; import { GitHubModule } from '../github/github.module'; import { RicesController } from './rices.controller'; import { SpacesController } from './spaces.controller'; import { SupabaseService } from '../supabase/supabase.service'; +import { CacheModule } from '@nestjs/cache-manager'; export const SHARED_TYPES = { WORKSPACE: "WORKSPACE", @@ -14,7 +15,10 @@ export const SHARED_TYPES = { @Module({ - imports: [GitHubModule], + imports: [ + GitHubModule, + CacheModule.register(), + ], providers: [SharedService, SupabaseService], controllers: [RicesController, SpacesController], exports: [SharedService], diff --git a/src/shared/shared.service.ts b/src/shared/shared.service.ts index 2e548b6..acb86dd 100644 --- a/src/shared/shared.service.ts +++ b/src/shared/shared.service.ts @@ -4,6 +4,7 @@ import { UnauthorizedException, ConflictException, BadRequestException, + Inject, } from '@nestjs/common'; import xss from 'xss'; @@ -14,12 +15,15 @@ import { ConfigService } from '@nestjs/config'; import { GitHubService } from '../github/github.service'; import { SupabaseService } from '../supabase/supabase.service'; import { SHARED_TYPES } from './shared.module'; +import { Cache } from 'cache-manager'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; const userAgentRegex = /ZenBrowser\/(\d+\.\d\w?\.\d) \((.+)\)/; @Injectable() export class SharedService { constructor( + @Inject(CACHE_MANAGER) private cacheManager: Cache, private readonly gitHubService: GitHubService, private readonly supabaseService: SupabaseService, private readonly configService: ConfigService, @@ -189,11 +193,19 @@ export class SharedService { async findOne(type: string, slug: string) { - // Check if the rice exists in the database + // Try to get from cache first + const cacheKey = `${type}:${slug}`; + const cachedData = await this.cacheManager.get(cacheKey); + + if (cachedData) { + return cachedData; + } + + // If not in cache, get from database const shared = await this.supabaseService.getSharedBySlug(type, slug); if (!shared) throw new NotFoundException('shared not found'); - // Fetch the file from GitHub + // Fetch file from GitHub const filePath = this.getSharedFilePath(type, slug); const fileContent = await this.gitHubService.getFileContent(filePath); @@ -201,16 +213,26 @@ export class SharedService { throw new NotFoundException('Shared file not found in GitHub'); } - // Remove unescaped double quotes at the beginning and end, if present - // const content = contentPrev.replace(/^"|"$/g, ''); + // Store in cache + await this.cacheManager.set(cacheKey, fileContent, 300); // 5 minutes TTL return fileContent; } async getRiceMetadata(type: string, slug: string) { + const cacheKey = `metadata:${type}:${slug}`; + const cachedMetadata = await this.cacheManager.get(cacheKey); + + if (cachedMetadata) { + return cachedMetadata; + } + const shared = await this.supabaseService.getSharedBySlug(type, slug); if (!shared) throw new NotFoundException('Shared not found'); + // Store in cache + await this.cacheManager.set(cacheKey, shared, 300); + return shared; } @@ -285,6 +307,8 @@ export class SharedService { `Update content in shared ${slug}`, ); + await this.cacheManager.del(`${type}:${slug}`); + await this.cacheManager.del(`metadata:${type}:${slug}`); return { message: `shared ${slug} updated successfully.` }; } catch (error) { console.error('Error in update method:', error); @@ -323,6 +347,8 @@ export class SharedService { folderPath, `Remove folder ${folderPath}`, ); + await this.cacheManager.del(`${type}:${slug}`); + await this.cacheManager.del(`metadata:${type}:${slug}`); } /**