From 243c9aabce64c4892ca29720a01f494fb48c43bd Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:28 +0100 Subject: [PATCH 01/42] Update package-lock.json: Remove TypeORM dependencies, add Prisma --- backend/package-lock.json | 324 +++++++------------------------------- 1 file changed, 55 insertions(+), 269 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index 88f5843..6afd9bd 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -23,8 +23,7 @@ "@nestjs/schedule": "^5.0.1", "@nestjs/swagger": "github:nestjs/swagger", "@nestjs/throttler": "^6.4.0", - "@nestjs/typeorm": "^11.0.0", - "@prisma/client": "^6.8.2", + "@prisma/client": "^6.9.0", "@types/cache-manager": "^4.0.6", "axios": "^1.8.1", "bcrypt": "^5.1.1", @@ -49,13 +48,12 @@ "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pg": "^8.13.3", - "prisma": "^6.8.2", + "prisma": "^6.9.0", "puppeteer": "^24.10.0", "redis": "^4.7.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.22", "uuid": "^11.1.0" }, "devDependencies": { @@ -911,7 +909,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -924,7 +922,7 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -1915,7 +1913,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1946,7 +1944,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -2574,19 +2572,6 @@ "reflect-metadata": "^0.1.13 || ^0.2.0" } }, - "node_modules/@nestjs/typeorm": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@nestjs/typeorm/-/typeorm-11.0.0.tgz", - "integrity": "sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==", - "license": "MIT", - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "@nestjs/core": "^10.0.0 || ^11.0.0", - "reflect-metadata": "^0.1.13 || ^0.2.0", - "rxjs": "^7.2.0", - "typeorm": "^0.3.0" - } - }, "node_modules/@noble/curves": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", @@ -2706,9 +2691,9 @@ } }, "node_modules/@prisma/client": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.8.2.tgz", - "integrity": "sha512-5II+vbyzv4si6Yunwgkj0qT/iY0zyspttoDrL3R4BYgLdp42/d2C8xdi9vqkrYtKt9H32oFIukvyw3Koz5JoDg==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.9.0.tgz", + "integrity": "sha512-Gg7j1hwy3SgF1KHrh0PZsYvAaykeR0PaxusnLXydehS96voYCGt1U5zVR31NIouYc63hWzidcrir1a7AIyCsNQ==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { @@ -2728,57 +2713,57 @@ } }, "node_modules/@prisma/config": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.8.2.tgz", - "integrity": "sha512-ZJY1fF4qRBPdLQ/60wxNtX+eu89c3AkYEcP7L3jkp0IPXCNphCYxikTg55kPJLDOG6P0X+QG5tCv6CmsBRZWFQ==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.9.0.tgz", + "integrity": "sha512-Wcfk8/lN3WRJd5w4jmNQkUwhUw0eksaU/+BlAJwPQKW10k0h0LC9PD/6TQFmqKVbHQL0vG2z266r0S1MPzzhbA==", "license": "Apache-2.0", "dependencies": { "jiti": "2.4.2" } }, "node_modules/@prisma/debug": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.8.2.tgz", - "integrity": "sha512-4muBSSUwJJ9BYth5N8tqts8JtiLT8QI/RSAzEogwEfpbYGFo9mYsInsVo8dqXdPO2+Rm5OG5q0qWDDE3nyUbVg==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.9.0.tgz", + "integrity": "sha512-bFeur/qi/Q+Mqk4JdQ3R38upSYPebv5aOyD1RKywVD+rAMLtRkmTFn28ZuTtVOnZHEdtxnNOCH+bPIeSGz1+Fg==", "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.8.2.tgz", - "integrity": "sha512-XqAJ//LXjqYRQ1RRabs79KOY4+v6gZOGzbcwDQl0D6n9WBKjV7qdrbd042CwSK0v0lM9MSHsbcFnU2Yn7z8Zlw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.9.0.tgz", + "integrity": "sha512-im0X0bwDLA0244CDf8fuvnLuCQcBBdAGgr+ByvGfQY9wWl6EA+kRGwVk8ZIpG65rnlOwtaWIr/ZcEU5pNVvq9g==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2", - "@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "@prisma/fetch-engine": "6.8.2", - "@prisma/get-platform": "6.8.2" + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/fetch-engine": "6.9.0", + "@prisma/get-platform": "6.9.0" } }, "node_modules/@prisma/engines-version": { - "version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e.tgz", - "integrity": "sha512-Rkik9lMyHpFNGaLpPF3H5q5TQTkm/aE7DsGM5m92FZTvWQsvmi6Va8On3pWvqLHOt5aPUvFb/FeZTmphI4CPiQ==", + "version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e.tgz", + "integrity": "sha512-Qp9gMoBHgqhKlrvumZWujmuD7q4DV/gooEyPCLtbkc13EZdSz2RsGUJ5mHb3RJgAbk+dm6XenqG7obJEhXcJ6Q==", "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.8.2.tgz", - "integrity": "sha512-lCvikWOgaLOfqXGacEKSNeenvj0n3qR5QvZUOmPE2e1Eh8cMYSobxonCg9rqM6FSdTfbpqp9xwhSAOYfNqSW0g==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.9.0.tgz", + "integrity": "sha512-PMKhJdl4fOdeE3J3NkcWZ+tf3W6rx3ht/rLU8w4SXFRcLhd5+3VcqY4Kslpdm8osca4ej3gTfB3+cSk5pGxgFg==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2", - "@prisma/engines-version": "6.8.0-43.2060c79ba17c6bb9f5823312b6f6b7f4a845738e", - "@prisma/get-platform": "6.8.2" + "@prisma/debug": "6.9.0", + "@prisma/engines-version": "6.9.0-10.81e4af48011447c3cc503a190e86995b66d2a28e", + "@prisma/get-platform": "6.9.0" } }, "node_modules/@prisma/get-platform": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.8.2.tgz", - "integrity": "sha512-vXSxyUgX3vm1Q70QwzwkjeYfRryIvKno1SXbIqwSptKwqKzskINnDUcx85oX+ys6ooN2ATGSD0xN2UTfg6Zcow==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.9.0.tgz", + "integrity": "sha512-/B4n+5V1LI/1JQcHp+sUpyRT1bBgZVPHbsC4lt4/19Xp4jvNIVcq5KYNtQDk5e/ukTSjo9PZVAxxy9ieFtlpTQ==", "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "6.8.2" + "@prisma/debug": "6.9.0" } }, "node_modules/@puppeteer/browsers": { @@ -2909,12 +2894,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "node_modules/@sqltools/formatter": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz", - "integrity": "sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==", - "license": "MIT" - }, "node_modules/@tokenizer/inflate": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", @@ -2949,28 +2928,28 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -3903,7 +3882,7 @@ "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -3916,7 +3895,7 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -4088,15 +4067,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/ansis": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/ansis/-/ansis-3.17.0.tgz", - "integrity": "sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==", - "license": "ISC", - "engines": { - "node": ">=14" - } - }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -4111,15 +4081,6 @@ "node": ">= 8" } }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -4164,7 +4125,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/argparse": { @@ -5682,7 +5643,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/cron": { @@ -5767,12 +5728,6 @@ "node": ">= 14" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -5794,6 +5749,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, "license": "MIT", "peerDependencies": { "babel-plugin-macros": "^3.1.0" @@ -5979,7 +5935,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "devOptional": true, + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -10516,7 +10472,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/makeerror": { @@ -12481,14 +12437,14 @@ } }, "node_modules/prisma": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.8.2.tgz", - "integrity": "sha512-JNricTXQxzDtRS7lCGGOB4g5DJ91eg3nozdubXze3LpcMl1oWwcFddrj++Up3jnRE6X/3gB/xz3V+ecBk/eEGA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.9.0.tgz", + "integrity": "sha512-resJAwMyZREC/I40LF6FZ6rZTnlrlrYrb63oW37Gq+U+9xHwbyMSPJjKtM7VZf3gTO86t/Oyz+YeSXr3CmAY1Q==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/config": "6.8.2", - "@prisma/engines": "6.8.2" + "@prisma/config": "6.9.0", + "@prisma/engines": "6.9.0" }, "bin": { "prisma": "build/index.js" @@ -13510,19 +13466,6 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -13741,22 +13684,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sql-highlight": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/sql-highlight/-/sql-highlight-6.0.0.tgz", - "integrity": "sha512-+fLpbAbWkQ+d0JEchJT/NrRRXbYRNbG15gFpANx73EwxQB1PRjj+k/OI0GTU0J63g8ikGkJECQp9z8XEJZvPRw==", - "funding": [ - "https://github.com/scriptcoded/sql-highlight?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/scriptcoded" - } - ], - "license": "MIT", - "engines": { - "node": ">=14" - } - }, "node_modules/stack-utils": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", @@ -14616,7 +14543,7 @@ "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -14660,7 +14587,7 @@ "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "devOptional": true, + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -14778,147 +14705,6 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, - "node_modules/typeorm": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.24.tgz", - "integrity": "sha512-4IrHG7A0tY8l5gEGXfW56VOMfUVWEkWlH/h5wmcyZ+V8oCiLj7iTPp0lEjMEZVrxEkGSdP9ErgTKHKXQApl/oA==", - "license": "MIT", - "dependencies": { - "@sqltools/formatter": "^1.2.5", - "ansis": "^3.17.0", - "app-root-path": "^3.1.0", - "buffer": "^6.0.3", - "dayjs": "^1.11.13", - "debug": "^4.4.0", - "dedent": "^1.6.0", - "dotenv": "^16.4.7", - "glob": "^10.4.5", - "sha.js": "^2.4.11", - "sql-highlight": "^6.0.0", - "tslib": "^2.8.1", - "uuid": "^11.1.0", - "yargs": "^17.7.2" - }, - "bin": { - "typeorm": "cli.js", - "typeorm-ts-node-commonjs": "cli-ts-node-commonjs.js", - "typeorm-ts-node-esm": "cli-ts-node-esm.js" - }, - "engines": { - "node": ">=16.13.0" - }, - "funding": { - "url": "https://opencollective.com/typeorm" - }, - "peerDependencies": { - "@google-cloud/spanner": "^5.18.0 || ^6.0.0 || ^7.0.0", - "@sap/hana-client": "^2.12.25", - "better-sqlite3": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", - "hdb-pool": "^0.1.6", - "ioredis": "^5.0.4", - "mongodb": "^5.8.0 || ^6.0.0", - "mssql": "^9.1.1 || ^10.0.1 || ^11.0.1", - "mysql2": "^2.2.5 || ^3.0.1", - "oracledb": "^6.3.0", - "pg": "^8.5.1", - "pg-native": "^3.0.0", - "pg-query-stream": "^4.0.0", - "redis": "^3.1.1 || ^4.0.0", - "reflect-metadata": "^0.1.14 || ^0.2.0", - "sql.js": "^1.4.0", - "sqlite3": "^5.0.3", - "ts-node": "^10.7.0", - "typeorm-aurora-data-api-driver": "^2.0.0 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@google-cloud/spanner": { - "optional": true - }, - "@sap/hana-client": { - "optional": true - }, - "better-sqlite3": { - "optional": true - }, - "hdb-pool": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "mongodb": { - "optional": true - }, - "mssql": { - "optional": true - }, - "mysql2": { - "optional": true - }, - "oracledb": { - "optional": true - }, - "pg": { - "optional": true - }, - "pg-native": { - "optional": true - }, - "pg-query-stream": { - "optional": true - }, - "redis": { - "optional": true - }, - "sql.js": { - "optional": true - }, - "sqlite3": { - "optional": true - }, - "ts-node": { - "optional": true - }, - "typeorm-aurora-data-api-driver": { - "optional": true - } - } - }, - "node_modules/typeorm/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/typeorm/node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -15081,7 +14867,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/v8-to-istanbul": { @@ -15803,7 +15589,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=6" From a9ce87d0353e0342c7146ce16d2d07c708bfd389 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:33 +0100 Subject: [PATCH 02/42] Update package.json: Replace TypeORM with Prisma dependencies --- backend/package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/backend/package.json b/backend/package.json index b19d26c..9e235a6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -40,8 +40,7 @@ "@nestjs/schedule": "^5.0.1", "@nestjs/swagger": "github:nestjs/swagger", "@nestjs/throttler": "^6.4.0", - "@nestjs/typeorm": "^11.0.0", - "@prisma/client": "^6.8.2", + "@prisma/client": "^6.9.0", "@types/cache-manager": "^4.0.6", "axios": "^1.8.1", "bcrypt": "^5.1.1", @@ -66,13 +65,12 @@ "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pg": "^8.13.3", - "prisma": "^6.8.2", + "prisma": "^6.9.0", "puppeteer": "^24.10.0", "redis": "^4.7.0", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", "swagger-ui-express": "^5.0.1", - "typeorm": "^0.3.22", "uuid": "^11.1.0" }, "devDependencies": { From 7857f2fc2bbbb511e3ab38da688f154864da196a Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:34 +0100 Subject: [PATCH 03/42] Update Prisma service: Add proper connection lifecycle management --- backend/prisma/prisma.service.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/backend/prisma/prisma.service.ts b/backend/prisma/prisma.service.ts index 8439ab6..671763f 100644 --- a/backend/prisma/prisma.service.ts +++ b/backend/prisma/prisma.service.ts @@ -1,15 +1,13 @@ -import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common'; +import { Injectable, OnModuleInit } from '@nestjs/common'; import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { - async onModuleInit() { - await this.$connect(); - } + async onModuleInit() { + await this.$connect(); + } - async enableShutdownHooks(app: INestApplication) { - this.$on('beforeExit' as Parameters[0], async () => { - await app.close(); - }); - } + async onModuleDestroy() { + await this.$disconnect(); + } } From 6115921f009f712d51a1dcc8449d607969aded68 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:34 +0100 Subject: [PATCH 04/42] Update Prisma schema: Add ShortenedAddress model with proper relations and indexes --- backend/prisma/schema.prisma | 82 +++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index ab21bab..10bfce1 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -14,17 +14,87 @@ model User { email String @unique password String refreshToken String? - profileImage String? // ✅ Add this line + profileImage String? verified Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + + // Relations + toolAccessLogs ToolAccessLog[] + shortenedAddresses ShortenedAddress[] + + @@map("user") } model ShortenedAddress { - id String @id @default(cuid()) - shortId String @unique - originalAddress String + id String @id @default(uuid()) + shortId String @unique @db.VarChar(50) + originalAddress String @db.VarChar(255) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + expiresAt DateTime? + userId String + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([shortId]) + @@index([originalAddress]) + @@map("shortened_address") +} + +enum ToolType { + FRONTEND + BACKEND + WEB3 +} + +model Tool { + id String @id @default(uuid()) + name String + slug String @unique + description String @db.Text + type ToolType + tags String[] + iconUrl String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + // Relations + accessLogs ToolAccessLog[] + + @@map("tool") +} + +model ToolAccessLog { + id String @id @default(uuid()) + userId String + toolId String + accessedAt DateTime @default(now()) + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + tool Tool @relation(fields: [toolId], references: [id], onDelete: Cascade) + + @@map("tool_access_log") +} + +model OtpVerification { + id String @id @default(uuid()) + email String + otp String createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - userId String + expiresAt DateTime + isUsed Boolean @default(false) + + @@map("otp_verification") +} + +model OtpResendLog { + id String @id @default(uuid()) + email String + requestedAt DateTime @default(now()) + success Boolean @default(false) + + @@map("otp_resend_log") } \ No newline at end of file From 12a6f663f5c838937d96c03d3e004e4c548a5d56 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:34 +0100 Subject: [PATCH 05/42] Add Prisma migrations: Database schema migration from TypeORM to Prisma --- .../20250604155130_init/migration.sql | 70 +++++++++++++++++++ backend/prisma/migrations/migration_lock.toml | 3 + 2 files changed, 73 insertions(+) create mode 100644 backend/prisma/migrations/20250604155130_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml diff --git a/backend/prisma/migrations/20250604155130_init/migration.sql b/backend/prisma/migrations/20250604155130_init/migration.sql new file mode 100644 index 0000000..63c45c7 --- /dev/null +++ b/backend/prisma/migrations/20250604155130_init/migration.sql @@ -0,0 +1,70 @@ +-- CreateEnum +CREATE TYPE "ToolType" AS ENUM ('FRONTEND', 'BACKEND', 'WEB3'); + +-- CreateTable +CREATE TABLE "user" ( + "id" TEXT NOT NULL, + "firstName" TEXT NOT NULL, + "lastName" TEXT NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "refreshToken" TEXT, + "profileImage" TEXT, + "verified" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "user_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "shortened_address" ( + "id" TEXT NOT NULL, + "shortId" TEXT NOT NULL, + "originalAddress" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + "userId" TEXT NOT NULL, + + CONSTRAINT "shortened_address_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tool" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "slug" TEXT NOT NULL, + "description" TEXT NOT NULL, + "type" "ToolType" NOT NULL, + "tags" TEXT[], + "iconUrl" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "tool_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "tool_access_log" ( + "id" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "toolId" TEXT NOT NULL, + "accessedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "tool_access_log_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "user_email_key" ON "user"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "shortened_address_shortId_key" ON "shortened_address"("shortId"); + +-- CreateIndex +CREATE UNIQUE INDEX "tool_slug_key" ON "tool"("slug"); + +-- AddForeignKey +ALTER TABLE "tool_access_log" ADD CONSTRAINT "tool_access_log_userId_fkey" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "tool_access_log" ADD CONSTRAINT "tool_access_log_toolId_fkey" FOREIGN KEY ("toolId") REFERENCES "tool"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" From 3ccff9cb6244813603dc509320eb7f162ac2d9ca Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:35 +0100 Subject: [PATCH 06/42] Update Tools DTOs: Remove TypeORM decorators, keep validation --- backend/src/Tools/dto/create-tool-access-log.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/Tools/dto/create-tool-access-log.dto.ts b/backend/src/Tools/dto/create-tool-access-log.dto.ts index f5dc9af..bbb5521 100644 --- a/backend/src/Tools/dto/create-tool-access-log.dto.ts +++ b/backend/src/Tools/dto/create-tool-access-log.dto.ts @@ -6,4 +6,4 @@ export class CreateToolAccessLogDto { @IsUUID() toolId: string; -} +} \ No newline at end of file From 2755223bb8b0e73ee41d8c037d32917b360644db Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:35 +0100 Subject: [PATCH 07/42] Update Tools filter DTO: Migrate from TypeORM to Prisma compatible structure --- backend/src/Tools/dto/filter-tools.dto.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/backend/src/Tools/dto/filter-tools.dto.ts b/backend/src/Tools/dto/filter-tools.dto.ts index 22fe184..c361306 100644 --- a/backend/src/Tools/dto/filter-tools.dto.ts +++ b/backend/src/Tools/dto/filter-tools.dto.ts @@ -1,12 +1,17 @@ import { IsEnum, IsOptional, IsString } from 'class-validator'; -import { ToolType } from '../entities/tool.entity'; + +export enum ToolType { + FRONTEND = 'FRONTEND', + BACKEND = 'BACKEND', + WEB3 = 'WEB3', +} export class FilterToolsDto { - @IsEnum(ToolType) - @IsOptional() - type?: ToolType; + @IsEnum(ToolType) + @IsOptional() + type?: ToolType; - @IsString() - @IsOptional() - tags?: string; + @IsString() + @IsOptional() + tags?: string; } From 5a331ae99497f62a3ecd69192a57a4b69c18d455 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:36 +0100 Subject: [PATCH 08/42] Update Tool DTO: Remove TypeORM entity references --- backend/src/Tools/dto/tool.dto.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/Tools/dto/tool.dto.ts b/backend/src/Tools/dto/tool.dto.ts index 1b5f8bb..ccf477e 100644 --- a/backend/src/Tools/dto/tool.dto.ts +++ b/backend/src/Tools/dto/tool.dto.ts @@ -1,5 +1,5 @@ import { IsEnum, IsOptional, IsString, IsArray, ArrayNotEmpty } from 'class-validator'; -import { ToolType } from '../entities/tool.entity'; +import { ToolType } from './filter-tools.dto'; export class CreateToolDto { @IsString() @@ -22,4 +22,4 @@ export class CreateToolDto { @IsOptional() @IsString() iconUrl?: string; -} +} \ No newline at end of file From 418d8786c49d901179c55d5056edcecb4005a2fc Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:36 +0100 Subject: [PATCH 09/42] Remove TypeORM entities: Delete tool and tool-access-log entities --- .../Tools/entities/tool-access-log.entity.ts | 23 ---------- backend/src/Tools/entities/tool.entity.ts | 43 ------------------- 2 files changed, 66 deletions(-) delete mode 100644 backend/src/Tools/entities/tool-access-log.entity.ts delete mode 100644 backend/src/Tools/entities/tool.entity.ts diff --git a/backend/src/Tools/entities/tool-access-log.entity.ts b/backend/src/Tools/entities/tool-access-log.entity.ts deleted file mode 100644 index a9f4971..0000000 --- a/backend/src/Tools/entities/tool-access-log.entity.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - ManyToOne, - CreateDateColumn, -} from 'typeorm'; -import { User } from '../../userAuth/entities/user.entity'; -import { Tool } from './tool.entity'; - -@Entity() -export class ToolAccessLog { - @PrimaryGeneratedColumn('uuid') - id: string; - - @ManyToOne(() => User, { eager: true, onDelete: 'CASCADE' }) - user: User; - - @ManyToOne(() => Tool, { eager: true, onDelete: 'CASCADE' }) - tool: Tool; - - @CreateDateColumn() - accessedAt: Date; -} diff --git a/backend/src/Tools/entities/tool.entity.ts b/backend/src/Tools/entities/tool.entity.ts deleted file mode 100644 index 969498f..0000000 --- a/backend/src/Tools/entities/tool.entity.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { - Entity, - PrimaryGeneratedColumn, - Column, - CreateDateColumn, - UpdateDateColumn, -} from 'typeorm'; - -export enum ToolType { - FRONTEND = 'frontend', - BACKEND = 'backend', - WEB3 = 'web3', -} - -@Entity() -export class Tool { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column() - name: string; - - @Column({ unique: true }) - slug: string; - - @Column({ type: 'text' }) - description: string; - - @Column({ type: 'enum', enum: ToolType }) - type: ToolType; - - @Column('text', { array: true }) - tags: string[]; - - @Column({ nullable: true }) - iconUrl?: string; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; -} From df6be3219b8f2d668c9c0ce4f0350d37d85d91d7 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:37 +0100 Subject: [PATCH 10/42] Migrate ToolAccessLog service: Replace TypeORM repository with Prisma client --- backend/src/Tools/tool-access-log.service.ts | 30 ++++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/backend/src/Tools/tool-access-log.service.ts b/backend/src/Tools/tool-access-log.service.ts index 6b348d9..13f8552 100644 --- a/backend/src/Tools/tool-access-log.service.ts +++ b/backend/src/Tools/tool-access-log.service.ts @@ -1,21 +1,21 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { ToolAccessLog } from './entities/tool-access-log.entity'; -import { Repository } from 'typeorm'; -import { User } from 'src/userAuth/entities/user.entity'; -import { Tool } from './entities/tool.entity'; +import { PrismaService } from './../../prisma/prisma.service'; +import { User, Tool, ToolAccessLog } from '@prisma/client'; @Injectable() export class ToolAccessLogService { - constructor( - @InjectRepository(ToolAccessLog) - private accessLogRepo: Repository, - ) {} - - async logAccess(user: User, tool: Tool): Promise { - const log = this.accessLogRepo.create({ user, tool }); - return this.accessLogRepo.save(log); - } - + constructor(private prisma: PrismaService) {} + async logAccess(user: User, tool: Tool): Promise { + return this.prisma.toolAccessLog.create({ + data: { + userId: user.id, + toolId: tool.id, + }, + include: { + user: true, + tool: true, + }, + }); + } } From 07dec7ce8e84f8be10d3b7977e5840e16fa8eaac Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:37 +0100 Subject: [PATCH 11/42] Update Tool controller: Adapt to Prisma service changes --- backend/src/Tools/tool.controller.ts | 105 +++++++++++++-------------- 1 file changed, 50 insertions(+), 55 deletions(-) diff --git a/backend/src/Tools/tool.controller.ts b/backend/src/Tools/tool.controller.ts index 32fbfa9..4e7051f 100644 --- a/backend/src/Tools/tool.controller.ts +++ b/backend/src/Tools/tool.controller.ts @@ -1,74 +1,69 @@ import { - Controller, - Post, - Get, - Param, - Body, - Patch, - Delete, - UseGuards, - Query, - Req, + Controller, + Post, + Get, + Param, + Body, + Patch, + Delete, + UseGuards, + Query, + Req, } from '@nestjs/common'; import { ToolService } from './tool.service'; -import { CreateToolDto } from '../Tools/dto/tool.dto'; +import { CreateToolDto } from './dto/tool.dto'; import { UpdateToolDto } from './dto/update-tool.dto'; import { JwtAuthGuard } from '../userAuth/guards/jwt-auth.guard'; -import { FilterToolsDto } from '../Tools/dto/filter-tools.dto'; -import { ToolAccessLogService } from '../Tools/tool-access-log.service' +import { FilterToolsDto } from './dto/filter-tools.dto'; +import { ToolAccessLogService } from './tool-access-log.service'; +import { Tool } from '@prisma/client'; @Controller('tools') @UseGuards(JwtAuthGuard) export class ToolController { - constructor( - private readonly toolService: ToolService, - private readonly toolAccessLogService: ToolAccessLogService, - ) {} + constructor( + private readonly toolService: ToolService, + private readonly toolAccessLogService: ToolAccessLogService + ) {} - @Post() - create(@Body() dto: CreateToolDto) { - return this.toolService.createTool(dto); - } + @Post() + create(@Body() dto: CreateToolDto) { + return this.toolService.createTool(dto); + } - @Get() - getAll() { - return this.toolService.getAllTools(); - } + @Get() + getAll() { + return this.toolService.getAllTools(); + } - @Get(':slug') - getBySlug(@Param('slug') slug: string) { - return this.toolService.getToolBySlug(slug); - } + @Get('filtered') + findFiltered(@Query() filterDto: FilterToolsDto) { + return this.toolService.findAllFiltered(filterDto); + } - @Patch(':slug') - update(@Param('slug') slug: string, @Body() dto: UpdateToolDto) { - return this.toolService.updateTool(slug, dto); - } + @Get(':slug') + getBySlug(@Param('slug') slug: string) { + return this.toolService.getToolBySlug(slug); + } - @Delete(':slug') - delete(@Param('slug') slug: string) { - return this.toolService.deleteTool(slug); - } + @Patch(':slug') + update(@Param('slug') slug: string, @Body() dto: UpdateToolDto) { + return this.toolService.updateTool(slug, dto); + } - @Get() - findFiltered(@Query() filterDto: FilterToolsDto) { - return this.toolService.findAllFiltered(filterDto); - } + @Delete(':slug') + delete(@Param('slug') slug: string) { + return this.toolService.deleteTool(slug); + } - @Get(':id') -@UseGuards(JwtAuthGuard) -async getTool( - @Param('id') id: string, - @Req() req: any, -): Promise { - const tool = await this.toolService.findOne(id) -const user = req.user; - -await this.toolAccessLogService.logAccess(user, tool); + @Get('access/:id') + @UseGuards(JwtAuthGuard) + async getTool(@Param('id') id: string, @Req() req: any): Promise { + const tool = await this.toolService.findOne(id); + const user = req.user; -return tool; + await this.toolAccessLogService.logAccess(user, tool); + return tool; + } } - -} - From 456f2e0c9cb94ccbf548ec7b229611b7baa58d9a Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:37 +0100 Subject: [PATCH 12/42] Update Tool module: Remove TypeORM imports, add Prisma dependencies --- backend/src/Tools/tool.module.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/Tools/tool.module.ts b/backend/src/Tools/tool.module.ts index 7c23970..e01ac64 100644 --- a/backend/src/Tools/tool.module.ts +++ b/backend/src/Tools/tool.module.ts @@ -1,17 +1,12 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { Tool } from './entities/tool.entity'; import { ToolController } from './tool.controller'; import { ToolService } from './tool.service'; -import { ToolAccessLog } from '../Tools/entities/tool-access-log.entity'; -import { ToolAccessLogService } from '../Tools/tool-access-log.service'; -import { User } from 'src/userAuth/entities/user.entity'; +import { ToolAccessLogService } from './tool-access-log.service'; +import { PrismaService } from './../../prisma/prisma.service'; @Module({ - imports: [ - TypeOrmModule.forFeature([Tool, ToolAccessLog, User]), - ], - controllers: [ToolController], - providers: [ToolService, ToolAccessLogService], + controllers: [ToolController], + providers: [ToolService, ToolAccessLogService, PrismaService], + exports: [ToolService, ToolAccessLogService], }) export class ToolModule {} From 643c9a290f42ca82b54315587ac3d3d483e543d9 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:38 +0100 Subject: [PATCH 13/42] Migrate Tool service: Replace TypeORM repository with Prisma client operations --- backend/src/Tools/tool.service.ts | 142 ++++++++++++++++++------------ 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/backend/src/Tools/tool.service.ts b/backend/src/Tools/tool.service.ts index 6985492..4e74beb 100644 --- a/backend/src/Tools/tool.service.ts +++ b/backend/src/Tools/tool.service.ts @@ -1,63 +1,91 @@ import { Injectable, NotFoundException } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; -import { Tool } from './entities/tool.entity'; -import { CreateToolDto } from '../Tools/dto/tool.dto'; -import { UpdateToolDto } from '../Tools/dto/update-tool.dto'; -import { FilterToolsDto } from '../Tools/dto/filter-tools.dto' +import { PrismaService } from './../../prisma/prisma.service'; +import { Tool, Prisma } from '@prisma/client'; +import { CreateToolDto } from './dto/tool.dto'; +import { UpdateToolDto } from './dto/update-tool.dto'; +import { FilterToolsDto, ToolType } from './dto/filter-tools.dto'; @Injectable() export class ToolService { - constructor( - @InjectRepository(Tool) - private toolRepository: Repository, - ) {} - - async createTool(dto: CreateToolDto): Promise { - const tool = this.toolRepository.create(dto); - return this.toolRepository.save(tool); - } - - async getAllTools(): Promise { - return this.toolRepository.find(); - } - - async getToolBySlug(slug: string): Promise { - const tool = await this.toolRepository.findOne({ where: { slug } }); - if (!tool) throw new NotFoundException('Tool not found'); - return tool; - } - - async updateTool(slug: string, dto: UpdateToolDto): Promise { - const tool = await this.getToolBySlug(slug); - Object.assign(tool, dto); - return this.toolRepository.save(tool); - } - - async deleteTool(slug: string): Promise { - const result = await this.toolRepository.delete({ slug }); - if (result.affected === 0) throw new NotFoundException('Tool not found'); - } - - async findAllFiltered(filter: FilterToolsDto): Promise { - const { type, tags } = filter; - - const query = this.toolRepository.createQueryBuilder('tool'); - - if (type) { - query.andWhere('tool.type = :type', { type }); - } - - if (tags) { - const tagsArray = tags.split(',').map((tag) => tag.trim().toLowerCase()); - query.andWhere('tool.tags && ARRAY[:...tagsArray]', { tagsArray }); - } - - return query.getMany(); - } - - async findOne(id: string): Promise { - return this.toolRepository.findOne({ where: { id } }); -} + constructor(private prisma: PrismaService) {} + + async createTool(dto: CreateToolDto): Promise { + return this.prisma.tool.create({ + data: dto, + }); + } + + async getAllTools(): Promise { + return this.prisma.tool.findMany(); + } + + async getToolBySlug(slug: string): Promise { + const tool = await this.prisma.tool.findUnique({ + where: { slug }, + }); + if (!tool) throw new NotFoundException('Tool not found'); + return tool; + } + + async updateTool(slug: string, dto: UpdateToolDto): Promise { + try { + return await this.prisma.tool.update({ + where: { slug }, + data: dto, + }); + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2025' + ) { + throw new NotFoundException('Tool not found'); + } + throw error; + } + } + + async deleteTool(slug: string): Promise { + try { + await this.prisma.tool.delete({ + where: { slug }, + }); + } catch (error) { + if ( + error instanceof Prisma.PrismaClientKnownRequestError && + error.code === 'P2025' + ) { + throw new NotFoundException('Tool not found'); + } + throw error; + } + } + + async findAllFiltered(filter: FilterToolsDto): Promise { + const { type, tags } = filter; + + const where: Prisma.ToolWhereInput = {}; + + if (type) { + where.type = type; + } + + if (tags) { + const tagsArray = tags.split(',').map((tag) => tag.trim().toLowerCase()); + where.tags = { + hasSome: tagsArray, + }; + } + + return this.prisma.tool.findMany({ + where, + }); + } + async findOne(id: string): Promise { + const tool = await this.prisma.tool.findUnique({ + where: { id }, + }); + if (!tool) throw new NotFoundException('Tool not found'); + return tool; + } } From e8c1df37ac4ba289ef3dd4a5744c83bd3919f0ce Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:38 +0100 Subject: [PATCH 14/42] Update AppModule: Remove TypeORM configuration, add ShortenerModule import --- backend/src/app.module.ts | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 79da34b..325b771 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,6 +1,5 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; -import { TypeOrmModule } from '@nestjs/typeorm'; import { JwtModule } from '@nestjs/jwt'; import { UserProfileService } from './userAuth/user.profile.service'; import { PrismaService } from '../prisma/prisma.service'; @@ -12,6 +11,7 @@ import { MailService } from './userAuth/mail.service'; import { OtpService } from './userAuth/otp.service'; import { ToolModule } from './Tools/tool.module'; import { PdfModule } from './pdf/pdf.module'; +import { ShortenerModule } from './smartContract/shortener.module'; @Module({ imports: [ @@ -23,25 +23,8 @@ import { PdfModule } from './pdf/pdf.module'; signOptions: { expiresIn: '1d' }, }), PdfModule, - TypeOrmModule.forRoot({ - type: 'postgres', - host: process.env.DB_HOST, - port: Number(process.env.DB_PORT) || 5432, - username: process.env.DB_USERNAME, - password: process.env.DB_PASSWORD, - database: process.env.DB_NAME, - autoLoadEntities: true, - entities: [__dirname + '/**/*.entity{.ts}'], - migrations: ['src/migrations/*.ts'], - synchronize: true, - ssl: - process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false, - extra: { - ssl: - process.env.DB_SSL === 'true' ? { rejectUnauthorized: false } : false, - }, - }), ToolModule, + ShortenerModule, ], controllers: [UserProfileController, AuthController], providers: [ From 8629ff637b4682dac04fee5c9faa38f877ea280b Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:39 +0100 Subject: [PATCH 15/42] Remove old SmartContractModule: Replaced with ShortenerModule --- backend/src/smartContract/SmartContractModule.ts | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 backend/src/smartContract/SmartContractModule.ts diff --git a/backend/src/smartContract/SmartContractModule.ts b/backend/src/smartContract/SmartContractModule.ts deleted file mode 100644 index 4b86fbf..0000000 --- a/backend/src/smartContract/SmartContractModule.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { SmartContract } from './entities/smart_contract.entity'; -import { ShortenerController } from './shortener.controller'; -import { ShortenerService } from './shortener.service'; - -@Module({ - imports: [TypeOrmModule.forFeature([SmartContract])], - providers: [ShortenerService], - controllers: [ShortenerController], - exports: [TypeOrmModule], -}) -export class SmartContractModule {} From d23110ff8c6967852241c58f661a007d07a8a6a1 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:39 +0100 Subject: [PATCH 16/42] Update shorten-address DTOs: Clean up imports and maintain validation --- .../smartContract/dto/shorten-address.dto.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/src/smartContract/dto/shorten-address.dto.ts b/backend/src/smartContract/dto/shorten-address.dto.ts index 82a4b1d..1036798 100644 --- a/backend/src/smartContract/dto/shorten-address.dto.ts +++ b/backend/src/smartContract/dto/shorten-address.dto.ts @@ -1,24 +1,23 @@ -// src/dto/shorten-address.dto.ts -import { IsNotEmpty, validate } from 'class-validator'; +import { IsNotEmpty } from 'class-validator'; import { StarknetAddressValidator } from '../utils/starknet-address.validator'; export class ShortenAddressDto { - @IsNotEmpty() - address: string; + @IsNotEmpty() + address: string; - async isValid(): Promise { - const validator = new StarknetAddressValidator(); - return validator.validate(this.address); - } + async isValid(): Promise { + const validator = new StarknetAddressValidator(); + return validator.validate(this.address); + } } export class ShortenAddressResponseDto { - shortId: string; - originalAddress: string; - createdAt: Date; + shortId: string; + originalAddress: string; + createdAt: Date; } export class ResolveAddressResponseDto { - originalAddress: string; - shortId: string; + originalAddress: string; + shortId: string; } From 8ea87e210c0e028e283273a8173de6898937cf4a Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:40 +0100 Subject: [PATCH 17/42] Add create-shortened-address DTO: New DTO for Prisma model structure --- .../dto/create-shortened-address.dto.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 backend/src/smartContract/dto/create-shortened-address.dto.ts diff --git a/backend/src/smartContract/dto/create-shortened-address.dto.ts b/backend/src/smartContract/dto/create-shortened-address.dto.ts new file mode 100644 index 0000000..f77bbf9 --- /dev/null +++ b/backend/src/smartContract/dto/create-shortened-address.dto.ts @@ -0,0 +1,15 @@ +import { IsString, IsOptional, MaxLength, IsDateString } from 'class-validator'; + +export class CreateShortenedAddressDto { + @IsString() + @MaxLength(255) + originalAddress: string; + + @IsString() + @MaxLength(50) + shortId: string; + + @IsOptional() + @IsDateString() + expiresAt?: string; +} From c24bf7a2dc6b052ff344b8493ec78e8045ba855e Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:40 +0100 Subject: [PATCH 18/42] Add update-shortened-address DTO: Partial DTO for updates --- .../src/smartContract/dto/update-shortened-address.dto.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 backend/src/smartContract/dto/update-shortened-address.dto.ts diff --git a/backend/src/smartContract/dto/update-shortened-address.dto.ts b/backend/src/smartContract/dto/update-shortened-address.dto.ts new file mode 100644 index 0000000..f1b0707 --- /dev/null +++ b/backend/src/smartContract/dto/update-shortened-address.dto.ts @@ -0,0 +1,6 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateShortenedAddressDto } from './create-shortened-address.dto'; + +export class UpdateShortenedAddressDto extends PartialType( + CreateShortenedAddressDto +) {} From 0fc67ea6279f3d8ba153220771058e9fbee46534 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:40 +0100 Subject: [PATCH 19/42] Remove SmartContract entity: Replaced with Prisma ShortenedAddress model --- .../entities/smart_contract.entity.ts | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 backend/src/smartContract/entities/smart_contract.entity.ts diff --git a/backend/src/smartContract/entities/smart_contract.entity.ts b/backend/src/smartContract/entities/smart_contract.entity.ts deleted file mode 100644 index 4d7e41c..0000000 --- a/backend/src/smartContract/entities/smart_contract.entity.ts +++ /dev/null @@ -1,22 +0,0 @@ -// src/entities/smart_contract.entity.ts -import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, Index } from 'typeorm'; - -@Entity() -export class SmartContract { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Index() - @Column({ unique: true, length: 255 }) - originalAddress: string; - - @Index() - @Column({ unique: true, length: 50 }) - shortId: string; - - @CreateDateColumn() - createdAt: Date; - - @Column({ type: 'timestamp', nullable: true }) - expiresAt?: Date; -} From bccd45ca028ad9e8aa580ed00464351d5b55499e Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:41 +0100 Subject: [PATCH 20/42] Update Shortener service: Migrate from TypeORM to Prisma with improved logic --- .../src/smartContract/shortener.service.ts | 167 ++++++++++++------ 1 file changed, 110 insertions(+), 57 deletions(-) diff --git a/backend/src/smartContract/shortener.service.ts b/backend/src/smartContract/shortener.service.ts index 5bd192f..cabd58e 100644 --- a/backend/src/smartContract/shortener.service.ts +++ b/backend/src/smartContract/shortener.service.ts @@ -1,63 +1,116 @@ -import { ConflictException, Injectable, NotFoundException } from '@nestjs/common'; -import { ShortenAddressDto, ShortenAddressResponseDto, ResolveAddressResponseDto } from './dto/shorten-address.dto'; +import { + ConflictException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { ResolveAddressResponseDto } from './dto/shorten-address.dto'; import { ShortIdGenerator } from './utils/short-id-generator'; import { PrismaService } from '../../prisma/prisma.service'; import { StarknetAddressValidator } from './utils/starknet-address.validator'; @Injectable() export class ShortenerService { - private readonly MAX_RETRIES = 3; - - constructor( - private prisma: PrismaService, - private idGenerator: ShortIdGenerator, - private addressValidator: StarknetAddressValidator - ) {} - - async shortenAddress(userId: string, address: string): Promise { - // StarkNet-specific validation - if (!this.addressValidator.validate(address)) { - throw new Error('Invalid StarkNet contract address'); - } - - let retries = 0; - let shortId: string; - let existingEntry; - - // Collision-resistant generation with retries - do { - shortId = retries > 0 - ? this.idGenerator.generate() + retries.toString() - : this.idGenerator.generate(); - - existingEntry = await this.prisma.shortenedAddress.findUnique({ - where: { shortId } - }); - - retries++; - } while (existingEntry && retries < this.MAX_RETRIES); - - if (existingEntry) throw new ConflictException('Unable to generate unique short ID'); - - await this.prisma.shortenedAddress.create({ - data: { shortId, originalAddress: address, userId } - }); - - return shortId; - } - - async resolveAddress(shortId: string): Promise { - const entry = await this.prisma.shortenedAddress.findUnique({ - where: { shortId }, - }); - - if (!entry) { - throw new NotFoundException('Short ID not found'); - } - - return { - originalAddress: entry.originalAddress, - shortId: entry.shortId, - }; - } -} \ No newline at end of file + private readonly MAX_RETRIES = 3; + + constructor( + private prisma: PrismaService, + private idGenerator: ShortIdGenerator, + private addressValidator: StarknetAddressValidator + ) {} + + async shortenAddress(userId: string, address: string): Promise { + // StarkNet-specific validation + if (!this.addressValidator.validate(address)) { + throw new Error('Invalid StarkNet contract address'); + } + + // Check if address already exists for this user + const existingAddress = await this.prisma.shortenedAddress.findFirst({ + where: { + originalAddress: address, + userId: userId, + }, + }); + + if (existingAddress) { + return existingAddress.shortId; + } + + let retries = 0; + let shortId: string; + let existingEntry; + + // Collision-resistant generation with retries + do { + shortId = + retries > 0 + ? this.idGenerator.generate() + retries.toString() + : this.idGenerator.generate(); + + existingEntry = await this.prisma.shortenedAddress.findUnique({ + where: { shortId }, + }); + + retries++; + } while (existingEntry && retries < this.MAX_RETRIES); + + if (existingEntry) { + throw new ConflictException('Unable to generate unique short ID'); + } + + const created = await this.prisma.shortenedAddress.create({ + data: { + shortId, + originalAddress: address, + userId, + }, + }); + + return created.shortId; + } + + async resolveAddress(shortId: string): Promise { + const entry = await this.prisma.shortenedAddress.findUnique({ + where: { shortId }, + }); + + if (!entry) { + throw new NotFoundException('Short ID not found'); + } + + // Check if expired + if (entry.expiresAt && entry.expiresAt < new Date()) { + throw new NotFoundException('Short ID has expired'); + } + + return { + originalAddress: entry.originalAddress, + shortId: entry.shortId, + }; + } + + async getUserAddresses(userId: string) { + return this.prisma.shortenedAddress.findMany({ + where: { userId }, + orderBy: { createdAt: 'desc' }, + }); + } + + async deleteAddress(shortId: string, userId: string): Promise { + const entry = await this.prisma.shortenedAddress.findUnique({ + where: { shortId }, + }); + + if (!entry) { + throw new NotFoundException('Short ID not found'); + } + + if (entry.userId !== userId) { + throw new ConflictException('You can only delete your own addresses'); + } + + await this.prisma.shortenedAddress.delete({ + where: { shortId }, + }); + } +} From d77cdc8016a9da778a28b9a468afbc61400854b6 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:41 +0100 Subject: [PATCH 21/42] Add ShortenerModule: New module with Prisma dependencies and utilities --- backend/src/smartContract/shortener.module.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 backend/src/smartContract/shortener.module.ts diff --git a/backend/src/smartContract/shortener.module.ts b/backend/src/smartContract/shortener.module.ts new file mode 100644 index 0000000..9577623 --- /dev/null +++ b/backend/src/smartContract/shortener.module.ts @@ -0,0 +1,19 @@ +// src/shortener/shortener.module.ts +import { Module } from '@nestjs/common'; +import { ShortenerController } from './shortener.controller'; +import { ShortenerService } from './shortener.service'; +import { ShortIdGenerator } from './utils/short-id-generator'; +import { StarknetAddressValidator } from './utils/starknet-address.validator'; +import { PrismaService } from '../../prisma/prisma.service'; + +@Module({ + providers: [ + ShortenerService, + ShortIdGenerator, + StarknetAddressValidator, + PrismaService, + ], + controllers: [ShortenerController], + exports: [ShortenerService], +}) +export class ShortenerModule {} From ba5501542d331c9ee01b1bb8f663d89e542894a9 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:42 +0100 Subject: [PATCH 22/42] Update Auth module: Remove TypeORM imports, keep Prisma service --- backend/src/userAuth/auth.module.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/backend/src/userAuth/auth.module.ts b/backend/src/userAuth/auth.module.ts index e7d2f22..7e3f203 100644 --- a/backend/src/userAuth/auth.module.ts +++ b/backend/src/userAuth/auth.module.ts @@ -2,15 +2,20 @@ import { JwtModule } from '@nestjs/jwt'; import { AuthService } from './auth.service'; import { JwtStrategy } from './strategies/jwt.strategy'; import { Module } from '@nestjs/common'; +import { AuthController } from './auth.controller'; +import { MailService } from './mail.service'; +import { OtpService } from './otp.service'; +import { PrismaService } from '../../prisma/prisma.service'; @Module({ - imports: [ - JwtModule.register({ - secret: 'ACCESS_SECRET', - signOptions: { expiresIn: '15m' }, - }), - ], - providers: [AuthService, JwtStrategy], - exports: [AuthService], + imports: [ + JwtModule.register({ + secret: 'ACCESS_SECRET', + signOptions: { expiresIn: '15m' }, + }), + ], + controllers: [AuthController], + providers: [AuthService, JwtStrategy, MailService, OtpService, PrismaService], + exports: [AuthService], }) export class AuthModule {} From 14fd478c9201bb8ae193671c1d8a9ae8b1c0ac03 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:42 +0100 Subject: [PATCH 23/42] Update create-user DTO: Remove TypeORM decorators, maintain validation --- backend/src/userAuth/dto/create-user.dto.ts | 51 +++++++++------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/backend/src/userAuth/dto/create-user.dto.ts b/backend/src/userAuth/dto/create-user.dto.ts index 6c18551..a5b43c9 100644 --- a/backend/src/userAuth/dto/create-user.dto.ts +++ b/backend/src/userAuth/dto/create-user.dto.ts @@ -1,36 +1,29 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn, UpdateDateColumn, BeforeInsert } from 'typeorm'; -import { Exclude } from 'class-transformer'; +import { + IsString, + IsEmail, + MinLength, + MaxLength, + IsOptional, +} from 'class-validator'; -@Entity('users') export class User { - @PrimaryGeneratedColumn('uuid') - id: string; + @IsString() + @MaxLength(50) + firstName: string; - @Column({ length: 50 }) - firstName: string; + @IsString() + @MaxLength(50) + lastName: string; - @Column({ length: 50 }) - lastName: string; + @IsEmail() + email: string; - @Column({ unique: true }) - email: string; + @IsString() + @MinLength(8) + @MaxLength(32) + password: string; - @Exclude() - @Column() - password: string; - - @Column({ nullable: true }) - refreshToken?: string; - - @Column({ nullable: true }) - profileImage?: string; - - @Column({ default: false }) - verified: boolean; - - @CreateDateColumn() - createdAt: Date; - - @UpdateDateColumn() - updatedAt: Date; + @IsOptional() + @IsString() + profileImage?: string; } From 143d336e17b1e07fbc6e00331bd737a521c97bb8 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:43 +0100 Subject: [PATCH 24/42] Update login DTO: Clean up imports and validation --- backend/src/userAuth/dto/login.dto.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/backend/src/userAuth/dto/login.dto.ts b/backend/src/userAuth/dto/login.dto.ts index 384b6df..1310cdb 100644 --- a/backend/src/userAuth/dto/login.dto.ts +++ b/backend/src/userAuth/dto/login.dto.ts @@ -1,14 +1,9 @@ -import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; +import { IsEmail, IsString } from 'class-validator'; -@Entity('users') export class Login { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column({ unique: true }) - email: string; - - @Column() - password: string; + @IsEmail() + email: string; + @IsString() + password: string; } From 8c5a50d9b0ac3890ed32f3e296d72bcea78756bf Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:43 +0100 Subject: [PATCH 25/42] Update refresh-token DTO: Remove TypeORM references --- backend/src/userAuth/dto/refresh-token.dto.ts | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/backend/src/userAuth/dto/refresh-token.dto.ts b/backend/src/userAuth/dto/refresh-token.dto.ts index f06ef9c..7a06c79 100644 --- a/backend/src/userAuth/dto/refresh-token.dto.ts +++ b/backend/src/userAuth/dto/refresh-token.dto.ts @@ -1,21 +1,6 @@ -import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn } from 'typeorm'; -import { User } from './create-user.dto'; +import { IsString } from 'class-validator'; -@Entity('refresh_tokens') export class RefreshToken { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column() - refreshToken: string; - - @ManyToOne(() => User, user => user.refreshToken, { onDelete: 'CASCADE' }) - @JoinColumn({ name: 'userId' }) - user: User; - - @Column() - userId: string; - - @Column({ type: 'timestamp', nullable: true }) - expiresAt: Date; + @IsString() + refreshToken: string; } From 3544c20d361eb7da9f6123232dc0effcd8fb1e23 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:44 +0100 Subject: [PATCH 26/42] Update resend-otp DTO: Clean validation and imports --- backend/src/userAuth/dto/resend-otp.dto.ts | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/backend/src/userAuth/dto/resend-otp.dto.ts b/backend/src/userAuth/dto/resend-otp.dto.ts index be2b4df..245f7d0 100644 --- a/backend/src/userAuth/dto/resend-otp.dto.ts +++ b/backend/src/userAuth/dto/resend-otp.dto.ts @@ -1,16 +1,6 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; +import { IsEmail } from 'class-validator'; -@Entity('otp_resend_logs') export class OtpResendLog { - @PrimaryGeneratedColumn('uuid') - id: string; - - @Column() - email: string; - - @CreateDateColumn() - requestedAt: Date; - - @Column({ default: false }) - success: boolean; + @IsEmail() + email: string; } From 22a0fc71d123e610231a3174a759545f9db53aa6 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:44 +0100 Subject: [PATCH 27/42] Update verify-otp DTO: Remove TypeORM dependencies --- backend/src/userAuth/dto/verify-otp.dto.ts | 23 +++++----------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/backend/src/userAuth/dto/verify-otp.dto.ts b/backend/src/userAuth/dto/verify-otp.dto.ts index f279d4a..d03dee2 100644 --- a/backend/src/userAuth/dto/verify-otp.dto.ts +++ b/backend/src/userAuth/dto/verify-otp.dto.ts @@ -1,22 +1,9 @@ -import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm'; +import { IsEmail, IsString } from 'class-validator'; -@Entity('otp_verifications') export class VerifyOtp { - @PrimaryGeneratedColumn('uuid') - id: string; + @IsEmail() + email: string; - @Column() - email: string; - - @Column() - otp: string; - - @CreateDateColumn() - createdAt: Date; - - @Column({ type: 'timestamp' }) - expiresAt: Date; - - @Column({ default: false }) - isUsed: boolean; + @IsString() + otp: string; } From 1ea7a7e2cf49beeda7ca3577bb31e9aae691365a Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:44 +0100 Subject: [PATCH 28/42] Remove User entity: Replaced with Prisma User model --- backend/src/userAuth/entities/user.entity.ts | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 backend/src/userAuth/entities/user.entity.ts diff --git a/backend/src/userAuth/entities/user.entity.ts b/backend/src/userAuth/entities/user.entity.ts deleted file mode 100644 index 694cdef..0000000 --- a/backend/src/userAuth/entities/user.entity.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ToolAccessLog } from '../../Tools/entities/tool-access-log.entity'; -import { Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm'; - -@Entity() -export class User { - @PrimaryGeneratedColumn() - id: number; - - @OneToMany(() => ToolAccessLog, (log) => log.user) - accessLogs: ToolAccessLog[]; -} From 923277126112229ad12833810c7b9955f26e7ada Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:45 +0100 Subject: [PATCH 29/42] Update OTP service: Migrate from TypeORM repository to Prisma client --- backend/src/userAuth/otp.service.ts | 60 +++++++++++++++++++++-------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/backend/src/userAuth/otp.service.ts b/backend/src/userAuth/otp.service.ts index df8941d..4e54be0 100644 --- a/backend/src/userAuth/otp.service.ts +++ b/backend/src/userAuth/otp.service.ts @@ -1,19 +1,49 @@ -import { Injectable } from "@nestjs/common"; +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; @Injectable() export class OtpService { - private otpStore = new Map(); - - generate(email: string): string { - const otp = Math.floor(100000 + Math.random() * 900000).toString(); - this.otpStore.set(email, { otp, expires: Date.now() + 5 * 60 * 1000 }); - return otp; - } - - verify(email: string, otp: string): boolean { - const record = this.otpStore.get(email); - if (!record || record.expires < Date.now() || record.otp !== otp) return false; - this.otpStore.delete(email); - return true; - } + constructor(private prisma: PrismaService) {} + + async generate(email: string): Promise { + const otp = Math.floor(100000 + Math.random() * 900000).toString(); + const expiresAt = new Date(Date.now() + 5 * 60 * 1000); + + await this.prisma.otpVerification.deleteMany({ + where: { email }, + }); + + await this.prisma.otpVerification.create({ + data: { + email, + otp, + expiresAt, + }, + }); + + return otp; + } + + async verify(email: string, otp: string): Promise { + const record = await this.prisma.otpVerification.findFirst({ + where: { + email, + otp, + isUsed: false, + expiresAt: { + gt: new Date(), + }, + }, + }); + + if (!record) return false; + + // Mark OTP as used + await this.prisma.otpVerification.update({ + where: { id: record.id }, + data: { isUsed: true }, + }); + + return true; + } } From 32495d9979c974355c9a103255da8f9e7c81fd49 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 19:42:45 +0100 Subject: [PATCH 30/42] Add git utility script: Helper for repository management --- backend/git.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 backend/git.sh diff --git a/backend/git.sh b/backend/git.sh new file mode 100755 index 0000000..e69de29 From 780d5581144927413888b516821ffa5c51c5e2e0 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:26:14 +0100 Subject: [PATCH 31/42] Apply global middleware structure and bootstrap JWT auth guard for PDF generation (#15) --- backend/src/main.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main.ts b/backend/src/main.ts index 8bcc257..ae69688 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -4,6 +4,7 @@ import * as dotenv from 'dotenv'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { ValidationPipe, BadRequestException } from '@nestjs/common'; +import { AuthExceptionFilter } from 'common/filters/auth-exception.filter'; dotenv.config(); @@ -59,6 +60,7 @@ async function bootstrap() { methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], // Allowed methods allowedHeaders: ['Content-Type', 'Authorization'], }); + app.useGlobalFilters(new AuthExceptionFilter()); app.setGlobalPrefix('api/v1'); await app.listen(process.env.PORT ?? 3000); } From 2646cfc646864a5607510ae333d5cf82b9d39d7d Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:26:19 +0100 Subject: [PATCH 32/42] Protect /pdf/generate endpoint with JWT Auth Guard (#15) --- backend/src/pdf/controllers/pdf.controller.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/backend/src/pdf/controllers/pdf.controller.ts b/backend/src/pdf/controllers/pdf.controller.ts index a1499d3..f010866 100644 --- a/backend/src/pdf/controllers/pdf.controller.ts +++ b/backend/src/pdf/controllers/pdf.controller.ts @@ -7,10 +7,16 @@ import { Logger, ValidationPipe, UsePipes, + UseGuards, + Req, } from '@nestjs/common'; import { Response } from 'express'; import { PdfService } from '../services/pdf.service'; import { GeneratePdfDto } from '../dto/generate-pdf.dto'; +import { + AuthenticatedRequest, + JwtAuthGuard, +} from 'src/userAuth/guards/jwt-auth.guard'; @Controller('pdf') export class PdfController { @@ -19,10 +25,12 @@ export class PdfController { constructor(private readonly pdfService: PdfService) {} @Post('generate') + @UseGuards(JwtAuthGuard) @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async generatePdf( @Body() generatePdfDto: GeneratePdfDto, - @Res() res: Response + @Res() res: Response, + @Req() req: AuthenticatedRequest ) { try { // Validate that exactly one field is provided From c0bf1eab57f615705d912ab3fa99bf6240a804bc Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:26:19 +0100 Subject: [PATCH 33/42] Customize JWT Auth Guard logic to support token validation for protected routes (#15) --- backend/src/userAuth/guards/jwt-auth.guard.ts | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/backend/src/userAuth/guards/jwt-auth.guard.ts b/backend/src/userAuth/guards/jwt-auth.guard.ts index 2155290..b8eb578 100644 --- a/backend/src/userAuth/guards/jwt-auth.guard.ts +++ b/backend/src/userAuth/guards/jwt-auth.guard.ts @@ -1,5 +1,57 @@ -import { Injectable } from '@nestjs/common'; +import { + Injectable, + ExecutionContext, + UnauthorizedException, +} from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; +import { Observable } from 'rxjs'; + +export interface AuthenticatedRequest extends Request { + user: { + userId: string; + email: string; + }; +} @Injectable() -export class JwtAuthGuard extends AuthGuard('jwt') {} +export class JwtAuthGuard extends AuthGuard('jwt') { + canActivate( + context: ExecutionContext + ): boolean | Promise | Observable { + return super.canActivate(context); + } + + handleRequest(err: any, user: any, info: any, context: ExecutionContext) { + const request = context.switchToHttp().getRequest(); + const authHeader = request.headers.authorization; + + // Check if Authorization header is missing + if (!authHeader) { + throw new UnauthorizedException('Authorization header is missing'); + } + + // Check if Authorization header format is correct + if (!authHeader.startsWith('Bearer ')) { + throw new UnauthorizedException( + 'Invalid authorization header format. Expected: Bearer ' + ); + } + + // Check for JWT validation errors + if (err || !user) { + if (info?.name === 'TokenExpiredError') { + throw new UnauthorizedException('Token has expired'); + } + if (info?.name === 'JsonWebTokenError') { + throw new UnauthorizedException('Invalid token'); + } + if (info?.name === 'NotBeforeError') { + throw new UnauthorizedException('Token not active'); + } + + throw new UnauthorizedException('Authentication failed'); + } + + return user; + } +} From 863c35274c8e1dfd874e74ff7eb0c969b030ca29 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:26:20 +0100 Subject: [PATCH 34/42] Implement JWT strategy for validating tokens in Authorization headers (#15) --- .../src/userAuth/strategies/jwt.strategy.ts | 36 +++++++++++++------ 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/backend/src/userAuth/strategies/jwt.strategy.ts b/backend/src/userAuth/strategies/jwt.strategy.ts index d99aef8..3cf38d0 100644 --- a/backend/src/userAuth/strategies/jwt.strategy.ts +++ b/backend/src/userAuth/strategies/jwt.strategy.ts @@ -1,17 +1,33 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; import { ExtractJwt, Strategy } from 'passport-jwt'; import { ConfigService } from '@nestjs/config'; +export interface JwtPayload { + sub: string; + email: string; + iat: number; + exp: number; +} + @Injectable() export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private configService: ConfigService) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - secretOrKey: configService.get('JWT_SECRET'), - }); - } - async validate(payload: any) { - return { userId: payload.sub, email: payload.email }; - } + constructor(private configService: ConfigService) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: configService.get('JWT_SECRET'), + }); + } + + async validate(payload: JwtPayload) { + if (!payload.sub || !payload.email) { + throw new UnauthorizedException('Invalid token payload'); + } + + return { + userId: payload.sub, + email: payload.email, + }; + } } From d4e551dbe18d8e4be89cf2d48e35e92e46d2e0b7 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:26:20 +0100 Subject: [PATCH 35/42] Add custom exception filter to format auth-related errors for protected routes (#15) --- .../common/filters/auth-exception.filter.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 backend/common/filters/auth-exception.filter.ts diff --git a/backend/common/filters/auth-exception.filter.ts b/backend/common/filters/auth-exception.filter.ts new file mode 100644 index 0000000..1b8e412 --- /dev/null +++ b/backend/common/filters/auth-exception.filter.ts @@ -0,0 +1,23 @@ +import { + ExceptionFilter, + Catch, + ArgumentsHost, + UnauthorizedException, +} from '@nestjs/common'; +import { Response } from 'express'; + +@Catch(UnauthorizedException) +export class AuthExceptionFilter implements ExceptionFilter { + catch(exception: UnauthorizedException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const status = exception.getStatus(); + + response.status(status).json({ + statusCode: status, + message: exception.message, + error: 'Unauthorized', + timestamp: new Date().toISOString(), + }); + } +} From c82faba1a3f031b41cb4444b3bba1683cdec3f24 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:30:25 +0100 Subject: [PATCH 36/42] exported userAuthModule and imported it in pdf --- backend/src/pdf/pdf.module.ts | 2 ++ backend/src/userAuth/auth.module.ts | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/backend/src/pdf/pdf.module.ts b/backend/src/pdf/pdf.module.ts index a57a6c1..5f6383c 100644 --- a/backend/src/pdf/pdf.module.ts +++ b/backend/src/pdf/pdf.module.ts @@ -3,8 +3,10 @@ import { MarkdownToPdfService } from './services/markdown-to-pdf.service'; import { PdfController } from './controllers/pdf.controller'; import { PdfService } from './services/pdf.service'; import { HtmlToPdfService } from './services/html-to-pdf.service'; +import { AuthModule } from './../userAuth/auth.module'; @Module({ + imports: [AuthModule], controllers: [PdfController], providers: [PdfService, HtmlToPdfService, MarkdownToPdfService], exports: [PdfService], diff --git a/backend/src/userAuth/auth.module.ts b/backend/src/userAuth/auth.module.ts index 7e3f203..e5cb02a 100644 --- a/backend/src/userAuth/auth.module.ts +++ b/backend/src/userAuth/auth.module.ts @@ -6,16 +6,26 @@ import { AuthController } from './auth.controller'; import { MailService } from './mail.service'; import { OtpService } from './otp.service'; import { PrismaService } from '../../prisma/prisma.service'; +import { PassportModule } from '@nestjs/passport'; +import { JwtAuthGuard } from './guards/jwt-auth.guard'; @Module({ imports: [ + PassportModule.register({ defaultStrategy: 'jwt' }), JwtModule.register({ secret: 'ACCESS_SECRET', signOptions: { expiresIn: '15m' }, }), ], controllers: [AuthController], - providers: [AuthService, JwtStrategy, MailService, OtpService, PrismaService], - exports: [AuthService], + providers: [ + AuthService, + JwtStrategy, + MailService, + OtpService, + PrismaService, + JwtAuthGuard, + ], + exports: [AuthService, JwtAuthGuard, PassportModule], }) export class AuthModule {} From fcb82dc8f9b977dada06e9c8c32cb8b3e8433f09 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:31:09 +0100 Subject: [PATCH 37/42] commented out error causing module --- .../XXXXXX-create-smart-contract.ts | 122 +++++++++--------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/backend/src/migrations/XXXXXX-create-smart-contract.ts b/backend/src/migrations/XXXXXX-create-smart-contract.ts index 2362dfe..aa10f72 100644 --- a/backend/src/migrations/XXXXXX-create-smart-contract.ts +++ b/backend/src/migrations/XXXXXX-create-smart-contract.ts @@ -1,65 +1,65 @@ -import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'; +// import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'; -export class CreateSmartContractXXXXXX implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.createTable( - new Table({ - name: 'smart_contract', - columns: [ - { - name: 'id', - type: 'uuid', - isPrimary: true, - isGenerated: true, - generationStrategy: 'uuid', - }, - { - name: 'originalAddress', - type: 'varchar', - length: '255', - isUnique: true, - isNullable: false, - }, - { - name: 'shortId', - type: 'varchar', - length: '50', - isUnique: true, - isNullable: false, - }, - { - name: 'createdAt', - type: 'timestamp', - default: 'now()', - }, - { - name: 'expiresAt', - type: 'timestamp', - isNullable: true, - }, - ], - }), - true, - ); +// export class CreateSmartContractXXXXXX implements MigrationInterface { +// public async up(queryRunner: QueryRunner): Promise { +// await queryRunner.createTable( +// new Table({ +// name: 'smart_contract', +// columns: [ +// { +// name: 'id', +// type: 'uuid', +// isPrimary: true, +// isGenerated: true, +// generationStrategy: 'uuid', +// }, +// { +// name: 'originalAddress', +// type: 'varchar', +// length: '255', +// isUnique: true, +// isNullable: false, +// }, +// { +// name: 'shortId', +// type: 'varchar', +// length: '50', +// isUnique: true, +// isNullable: false, +// }, +// { +// name: 'createdAt', +// type: 'timestamp', +// default: 'now()', +// }, +// { +// name: 'expiresAt', +// type: 'timestamp', +// isNullable: true, +// }, +// ], +// }), +// true, +// ); - await queryRunner.createIndex( - 'smart_contract', - new TableIndex({ - name: 'IDX_SMART_CONTRACT_ORIGINAL_ADDRESS', - columnNames: ['originalAddress'], - }), - ); +// await queryRunner.createIndex( +// 'smart_contract', +// new TableIndex({ +// name: 'IDX_SMART_CONTRACT_ORIGINAL_ADDRESS', +// columnNames: ['originalAddress'], +// }), +// ); - await queryRunner.createIndex( - 'smart_contract', - new TableIndex({ - name: 'IDX_SMART_CONTRACT_SHORT_ID', - columnNames: ['shortId'], - }), - ); - } +// await queryRunner.createIndex( +// 'smart_contract', +// new TableIndex({ +// name: 'IDX_SMART_CONTRACT_SHORT_ID', +// columnNames: ['shortId'], +// }), +// ); +// } - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.dropTable('smart_contract'); - } -} +// public async down(queryRunner: QueryRunner): Promise { +// await queryRunner.dropTable('smart_contract'); +// } +// } From d4ff6524ee621715e22f24c914e396b0a69de52e Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:56:05 +0100 Subject: [PATCH 38/42] feat: add PDF generation logging model to database schema - Add PdfGenerationLog model with comprehensive tracking fields - Add PdfInputType enum for HTML/MARKDOWN distinction - Include metadata fields: input size, processing time, success status - Add error logging fields: error message and stack trace - Include request tracing: user agent, IP address, request ID - Add performance indexes for efficient querying - Establish foreign key relationship with User model --- backend/prisma/schema.prisma | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 10bfce1..c823648 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -22,6 +22,7 @@ model User { // Relations toolAccessLogs ToolAccessLog[] shortenedAddresses ShortenedAddress[] + pdfGenerationLogs PdfGenerationLog[] @@map("user") } @@ -97,4 +98,36 @@ model OtpResendLog { success Boolean @default(false) @@map("otp_resend_log") +} + +enum PdfInputType { + HTML + MARKDOWN +} + +model PdfGenerationLog { + id String @id @default(uuid()) + userId String + inputType PdfInputType + inputSize Int // Size in bytes + processingTime Int // Processing time in milliseconds + success Boolean + errorMessage String? @db.Text + errorStack String? @db.Text + inputSnippet String? @db.VarChar(500) // First 500 chars for debugging + outputSize Int? // PDF size in bytes + pageCount Int? // Number of pages generated + userAgent String? @db.VarChar(255) + ipAddress String? @db.VarChar(45) // IPv6 compatible + requestId String? @db.VarChar(100) // For tracing requests + createdAt DateTime @default(now()) + + // Relations + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@index([userId]) + @@index([success]) + @@index([createdAt]) + @@index([inputType]) + @@map("pdf_generation_log") } \ No newline at end of file From 7fc66d0496256eb9d77475a416dbc4a964e4508c Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:56:11 +0100 Subject: [PATCH 39/42] feat: implement comprehensive PDF logging service - Create PdfLogService for database logging operations - Add detailed PDF generation request logging with metadata - Implement user analytics with success rates and performance metrics - Add system-wide analytics for administrative monitoring - Include error pattern analysis for debugging support - Provide safe input snippet creation with sensitive data removal - Add request tracing and performance monitoring capabilities --- backend/src/pdf/services/pdf-log.service.ts | 307 ++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 backend/src/pdf/services/pdf-log.service.ts diff --git a/backend/src/pdf/services/pdf-log.service.ts b/backend/src/pdf/services/pdf-log.service.ts new file mode 100644 index 0000000..4edb8fd --- /dev/null +++ b/backend/src/pdf/services/pdf-log.service.ts @@ -0,0 +1,307 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { PrismaService } from '../../../prisma/prisma.service'; +import { PdfInputType } from '@prisma/client'; + +export interface PdfLogData { + userId: string; + inputType: PdfInputType; + inputSize: number; + processingTime: number; + success: boolean; + errorMessage?: string; + errorStack?: string; + inputSnippet?: string; + outputSize?: number; + pageCount?: number; + userAgent?: string; + ipAddress?: string; + requestId?: string; +} + +export interface PdfLogAnalytics { + totalRequests: number; + successRate: number; + averageProcessingTime: number; + averageInputSize: number; + averageOutputSize: number; + mostCommonInputType: PdfInputType; + recentFailures: Array<{ + id: string; + createdAt: Date; + errorMessage: string; + inputType: PdfInputType; + }>; +} + +@Injectable() +export class PdfLogService { + private readonly logger = new Logger(PdfLogService.name); + + constructor(private readonly prisma: PrismaService) {} + + /** + * Log a PDF generation request + */ + async logPdfGeneration(data: PdfLogData): Promise { + try { + await this.prisma.pdfGenerationLog.create({ + data: { + userId: data.userId, + inputType: data.inputType, + inputSize: data.inputSize, + processingTime: data.processingTime, + success: data.success, + errorMessage: data.errorMessage, + errorStack: data.errorStack, + inputSnippet: data.inputSnippet, + outputSize: data.outputSize, + pageCount: data.pageCount, + userAgent: data.userAgent, + ipAddress: data.ipAddress, + requestId: data.requestId, + }, + }); + + this.logger.log( + `PDF generation logged: ${data.success ? 'SUCCESS' : 'FAILURE'} - User: ${data.userId}, Type: ${data.inputType}, Time: ${data.processingTime}ms` + ); + } catch (error) { + this.logger.error('Failed to log PDF generation', error.stack); + // Don't throw here to avoid disrupting the main PDF generation flow + } + } + + /** + * Get PDF generation analytics for a user + */ + async getUserAnalytics( + userId: string, + days: number = 30 + ): Promise { + const since = new Date(); + since.setDate(since.getDate() - days); + + const logs = await this.prisma.pdfGenerationLog.findMany({ + where: { + userId, + createdAt: { gte: since }, + }, + orderBy: { createdAt: 'desc' }, + }); + + const totalRequests = logs.length; + const successfulRequests = logs.filter((log) => log.success).length; + const successRate = + totalRequests > 0 ? (successfulRequests / totalRequests) * 100 : 0; + + const averageProcessingTime = + totalRequests > 0 + ? logs.reduce((sum, log) => sum + log.processingTime, 0) / totalRequests + : 0; + + const averageInputSize = + totalRequests > 0 + ? logs.reduce((sum, log) => sum + log.inputSize, 0) / totalRequests + : 0; + + const successfulLogs = logs.filter((log) => log.success && log.outputSize); + const averageOutputSize = + successfulLogs.length > 0 + ? successfulLogs.reduce((sum, log) => sum + (log.outputSize || 0), 0) / + successfulLogs.length + : 0; + + const inputTypeCounts = logs.reduce( + (acc, log) => { + acc[log.inputType] = (acc[log.inputType] || 0) + 1; + return acc; + }, + {} as Record + ); + + const mostCommonInputType = + (Object.entries(inputTypeCounts).sort( + ([, a], [, b]) => b - a + )[0]?.[0] as PdfInputType) || PdfInputType.HTML; + + const recentFailures = logs + .filter((log) => !log.success) + .slice(0, 10) + .map((log) => ({ + id: log.id, + createdAt: log.createdAt, + errorMessage: log.errorMessage || 'Unknown error', + inputType: log.inputType, + })); + + return { + totalRequests, + successRate, + averageProcessingTime, + averageInputSize, + averageOutputSize, + mostCommonInputType, + recentFailures, + }; + } + + /** + * Get system-wide analytics (admin only) + */ + async getSystemAnalytics(days: number = 30): Promise< + PdfLogAnalytics & { + topUsers: Array<{ + userId: string; + requestCount: number; + user: { firstName: string; lastName: string; email: string }; + }>; + } + > { + const since = new Date(); + since.setDate(since.getDate() - days); + + const logs = await this.prisma.pdfGenerationLog.findMany({ + where: { createdAt: { gte: since } }, + include: { + user: { select: { firstName: true, lastName: true, email: true } }, + }, + orderBy: { createdAt: 'desc' }, + }); + + // Base analytics + const baseAnalytics = await this.calculateBaseAnalytics(logs); + + // Top users + const userCounts = logs.reduce( + (acc, log) => { + const key = log.userId; + if (!acc[key]) { + acc[key] = { userId: log.userId, requestCount: 0, user: log.user }; + } + acc[key].requestCount++; + return acc; + }, + {} as Record + ); + + const topUsers = Object.values(userCounts) + .sort((a, b) => b.requestCount - a.requestCount) + .slice(0, 10); + + return { ...baseAnalytics, topUsers }; + } + + /** + * Get error patterns for debugging + */ + async getErrorPatterns(days: number = 7): Promise< + Array<{ + errorMessage: string; + count: number; + lastOccurrence: Date; + inputTypes: PdfInputType[]; + }> + > { + const since = new Date(); + since.setDate(since.getDate() - days); + + const errorLogs = await this.prisma.pdfGenerationLog.findMany({ + where: { + success: false, + createdAt: { gte: since }, + errorMessage: { not: null }, + }, + select: { + errorMessage: true, + inputType: true, + createdAt: true, + }, + orderBy: { createdAt: 'desc' }, + }); + + const errorPatterns = errorLogs.reduce( + (acc, log) => { + const key = log.errorMessage!; + if (!acc[key]) { + acc[key] = { + errorMessage: key, + count: 0, + lastOccurrence: log.createdAt, + inputTypes: new Set(), + }; + } + acc[key].count++; + acc[key].inputTypes.add(log.inputType); + if (log.createdAt > acc[key].lastOccurrence) { + acc[key].lastOccurrence = log.createdAt; + } + return acc; + }, + {} as Record + ); + + return Object.values(errorPatterns) + .map((pattern) => ({ + ...pattern, + inputTypes: Array.from(pattern.inputTypes), + })) + .sort((a, b) => b.count - a.count); + } + + private async calculateBaseAnalytics(logs: any[]): Promise { + const totalRequests = logs.length; + const successfulRequests = logs.filter((log) => log.success).length; + const successRate = + totalRequests > 0 ? (successfulRequests / totalRequests) * 100 : 0; + + const averageProcessingTime = + totalRequests > 0 + ? logs.reduce((sum, log) => sum + log.processingTime, 0) / totalRequests + : 0; + + const averageInputSize = + totalRequests > 0 + ? logs.reduce((sum, log) => sum + log.inputSize, 0) / totalRequests + : 0; + + const successfulLogs = logs.filter((log) => log.success && log.outputSize); + const averageOutputSize = + successfulLogs.length > 0 + ? successfulLogs.reduce((sum, log) => sum + (log.outputSize || 0), 0) / + successfulLogs.length + : 0; + + const inputTypeCounts = logs.reduce( + (acc, log) => { + acc[log.inputType] = (acc[log.inputType] || 0) + 1; + return acc; + }, + {} as Record + ); + + const mostCommonInputType = + (Object.entries(inputTypeCounts).sort( + ([, a], [, b]) => b - a + )[0]?.[0] as PdfInputType) || PdfInputType.HTML; + + const recentFailures = logs + .filter((log) => !log.success) + .slice(0, 10) + .map((log) => ({ + id: log.id, + createdAt: log.createdAt, + errorMessage: log.errorMessage || 'Unknown error', + inputType: log.inputType, + })); + + return { + totalRequests, + successRate, + averageProcessingTime, + averageInputSize, + averageOutputSize, + mostCommonInputType, + recentFailures, + }; + } +} From 175e69491fa52bb80da6f6ae8498dd2074995311 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:56:11 +0100 Subject: [PATCH 40/42] feat: integrate comprehensive logging into PDF generation controller - Add detailed request logging with unique request IDs - Implement safe input snippet creation for debugging - Add comprehensive error logging with stack traces - Include request metadata tracking (user agent, IP address) - Add new analytics endpoints for users and administrators - Implement processing time measurement and performance logging - Add error pattern analysis endpoint for system monitoring --- backend/src/pdf/controllers/pdf.controller.ts | 218 ++++++++++++++++-- 1 file changed, 205 insertions(+), 13 deletions(-) diff --git a/backend/src/pdf/controllers/pdf.controller.ts b/backend/src/pdf/controllers/pdf.controller.ts index f010866..190edc8 100644 --- a/backend/src/pdf/controllers/pdf.controller.ts +++ b/backend/src/pdf/controllers/pdf.controller.ts @@ -9,6 +9,8 @@ import { UsePipes, UseGuards, Req, + Get, + Query, } from '@nestjs/common'; import { Response } from 'express'; import { PdfService } from '../services/pdf.service'; @@ -17,12 +19,18 @@ import { AuthenticatedRequest, JwtAuthGuard, } from 'src/userAuth/guards/jwt-auth.guard'; +import { PdfLogService } from '../services/pdf-log.service'; +import { PdfInputType } from '@prisma/client'; +import { v4 as uuidv4 } from 'uuid'; @Controller('pdf') export class PdfController { private readonly logger = new Logger(PdfController.name); - constructor(private readonly pdfService: PdfService) {} + constructor( + private readonly pdfService: PdfService, + private readonly pdfLogService: PdfLogService + ) {} @Post('generate') @UseGuards(JwtAuthGuard) @@ -32,43 +40,109 @@ export class PdfController { @Res() res: Response, @Req() req: AuthenticatedRequest ) { + const requestId = uuidv4(); + const startTime = Date.now(); + const userId = req.user.userId; + + // Extract request metadata + const userAgent = req.headers['user-agent']; + const ipAddress = + req.ip || + req.connection.remoteAddress || + (req.headers['x-forwarded-for'] as string); + + let inputType: PdfInputType; + let inputContent: string; + let inputSize: number; + try { // Validate that exactly one field is provided generatePdfDto.validate(); + // Determine input type and content + if (generatePdfDto.html) { + inputType = PdfInputType.HTML; + inputContent = generatePdfDto.html; + } else { + inputType = PdfInputType.MARKDOWN; + inputContent = generatePdfDto.markdown!; + } + + inputSize = Buffer.byteLength(inputContent, 'utf8'); + + const inputSnippet = this.createSafeInputSnippet(inputContent); + + this.logger.log( + `[${requestId}] Starting PDF generation - User: ${userId}, Type: ${inputType}, Size: ${inputSize} bytes` + ); + let result; - const startTime = Date.now(); if (generatePdfDto.html) { - this.logger.log('Generating PDF from HTML'); + this.logger.log(`[${requestId}] Generating PDF from HTML`); result = await this.pdfService.generateFromHtml(generatePdfDto.html); } else if (generatePdfDto.markdown) { - this.logger.log('Generating PDF from Markdown'); + this.logger.log(`[${requestId}] Generating PDF from Markdown`); result = await this.pdfService.generateFromMarkdown( generatePdfDto.markdown ); } - const totalTime = Date.now() - startTime; + const processingTime = Date.now() - startTime; this.logger.log( - `PDF generated successfully - Size: ${result.metadata.size} bytes, ` + - `Pages: ${result.metadata.pages}, Time: ${totalTime}ms` + `[${requestId}] PDF generated successfully - Size: ${result!.metadata.size} bytes, ` + + `Pages: ${result!.metadata.pages}, Time: ${processingTime}ms` ); - // Set response headers + await this.pdfLogService.logPdfGeneration({ + userId, + inputType, + inputSize, + processingTime, + success: true, + inputSnippet, + outputSize: result!.metadata.size, + pageCount: result!.metadata.pages, + userAgent, + ipAddress, + requestId, + }); + res.set({ 'Content-Type': 'application/pdf', - 'Content-Length': result.metadata.size.toString(), + 'Content-Length': result!.metadata.size.toString(), 'Content-Disposition': 'inline; filename="document.pdf"', 'Cache-Control': 'no-cache, no-store, must-revalidate', - 'X-PDF-Pages': result.metadata.pages.toString(), - 'X-Generation-Time': result.metadata.generationTime.toString(), + 'X-PDF-Pages': result!.metadata.pages.toString(), + 'X-Generation-Time': result!.metadata.generationTime.toString(), + 'X-Request-ID': requestId, }); - res.status(HttpStatus.OK).send(result.buffer); + res.status(HttpStatus.OK).send(result!.buffer); } catch (error) { - this.logger.error('PDF generation failed', error.stack); + const processingTime = Date.now() - startTime; + + this.logger.error( + `[${requestId}] PDF generation failed after ${processingTime}ms`, + error.stack + ); + + await this.pdfLogService.logPdfGeneration({ + userId, + inputType: inputType!, + inputSize: inputSize || 0, + processingTime, + success: false, + errorMessage: error.message, + errorStack: error.stack, + inputSnippet: inputSize + ? this.createSafeInputSnippet(inputContent!) + : undefined, + userAgent, + ipAddress, + requestId, + }); if ( error.message.includes('required') || @@ -78,6 +152,7 @@ export class PdfController { error: 'Validation failed', message: error.message, statusCode: HttpStatus.BAD_REQUEST, + requestId, }); } else if ( error.message.includes('parsing') || @@ -87,14 +162,131 @@ export class PdfController { error: 'Input processing failed', message: error.message, statusCode: HttpStatus.BAD_REQUEST, + requestId, }); } else { res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error: 'PDF generation failed', message: 'An internal server error occurred during PDF generation', statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + requestId, }); } } } + + @Get('analytics') + @UseGuards(JwtAuthGuard) + async getUserAnalytics( + @Req() req: AuthenticatedRequest, + @Query('days') days?: string + ) { + const userId = req.user.id; + const daysParsed = days ? parseInt(days, 10) : 30; + + if (daysParsed < 1 || daysParsed > 365) { + return { + error: 'Days parameter must be between 1 and 365', + statusCode: HttpStatus.BAD_REQUEST, + }; + } + + try { + const analytics = await this.pdfLogService.getUserAnalytics( + userId, + daysParsed + ); + return { + data: analytics, + period: `${daysParsed} days`, + statusCode: HttpStatus.OK, + }; + } catch (error) { + this.logger.error('Failed to fetch user analytics', error.stack); + return { + error: 'Failed to fetch analytics', + message: 'An error occurred while retrieving analytics data', + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + }; + } + } + + @Get('analytics/system') + @UseGuards(JwtAuthGuard) + async getSystemAnalytics( + @Req() req: AuthenticatedRequest, + @Query('days') days?: string + ) { + const daysParsed = days ? parseInt(days, 10) : 30; + + if (daysParsed < 1 || daysParsed > 365) { + return { + error: 'Days parameter must be between 1 and 365', + statusCode: HttpStatus.BAD_REQUEST, + }; + } + + try { + const analytics = await this.pdfLogService.getSystemAnalytics(daysParsed); + return { + data: analytics, + period: `${daysParsed} days`, + statusCode: HttpStatus.OK, + }; + } catch (error) { + this.logger.error('Failed to fetch system analytics', error.stack); + return { + error: 'Failed to fetch system analytics', + message: 'An error occurred while retrieving system analytics data', + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + }; + } + } + + @Get('analytics/errors') + @UseGuards(JwtAuthGuard) + async getErrorPatterns(@Query('days') days?: string) { + const daysParsed = days ? parseInt(days, 10) : 7; + + if (daysParsed < 1 || daysParsed > 30) { + return { + error: 'Days parameter must be between 1 and 30', + statusCode: HttpStatus.BAD_REQUEST, + }; + } + + try { + const errorPatterns = + await this.pdfLogService.getErrorPatterns(daysParsed); + return { + data: errorPatterns, + period: `${daysParsed} days`, + statusCode: HttpStatus.OK, + }; + } catch (error) { + this.logger.error('Failed to fetch error patterns', error.stack); + return { + error: 'Failed to fetch error patterns', + message: 'An error occurred while retrieving error pattern data', + statusCode: HttpStatus.INTERNAL_SERVER_ERROR, + }; + } + } + + /** + * Safe snippet of input content for logging + * Removes potential sensitive information and limits length + */ + private createSafeInputSnippet(content: string): string { + let safe = content + .replace( + /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, + '[EMAIL]' + ) + .replace(/https?:\/\/[^\s]+/g, '[URL]') + .replace(/\b\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}\b/g, '[CARD]') + .replace(/\b\d{3}-\d{2}-\d{4}\b/g, '[SSN]'); + + return safe.length > 500 ? safe.substring(0, 500) + '...' : safe; + } } From 5dfb6438d6c5008434e2cf7b126645d24d61542b Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:56:12 +0100 Subject: [PATCH 41/42] feat: integrate PDF logging service into module configuration - Add PdfLogService to PDF module providers - Import PrismaModule for database access - Export PdfLogService for potential use in other modules - Update module dependencies for logging functionality --- backend/src/pdf/pdf.module.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/backend/src/pdf/pdf.module.ts b/backend/src/pdf/pdf.module.ts index 5f6383c..fe4a663 100644 --- a/backend/src/pdf/pdf.module.ts +++ b/backend/src/pdf/pdf.module.ts @@ -3,12 +3,19 @@ import { MarkdownToPdfService } from './services/markdown-to-pdf.service'; import { PdfController } from './controllers/pdf.controller'; import { PdfService } from './services/pdf.service'; import { HtmlToPdfService } from './services/html-to-pdf.service'; +import { PdfLogService } from './services/pdf-log.service'; import { AuthModule } from './../userAuth/auth.module'; +import { PrismaModule } from './../../prisma/prisma.module'; @Module({ - imports: [AuthModule], + imports: [AuthModule, PrismaModule], controllers: [PdfController], - providers: [PdfService, HtmlToPdfService, MarkdownToPdfService], - exports: [PdfService], + providers: [ + PdfService, + HtmlToPdfService, + MarkdownToPdfService, + PdfLogService, + ], + exports: [PdfService, PdfLogService], }) export class PdfModule {} From b928c2e3d8cebacffc515b5c3a4bb76e4fdc95c1 Mon Sep 17 00:00:00 2001 From: clintjeff2 Date: Wed, 4 Jun 2025 20:57:15 +0100 Subject: [PATCH 42/42] Fixed error: imported request before creating authentication request extension --- backend/src/userAuth/guards/jwt-auth.guard.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/userAuth/guards/jwt-auth.guard.ts b/backend/src/userAuth/guards/jwt-auth.guard.ts index b8eb578..fc5484e 100644 --- a/backend/src/userAuth/guards/jwt-auth.guard.ts +++ b/backend/src/userAuth/guards/jwt-auth.guard.ts @@ -4,6 +4,7 @@ import { UnauthorizedException, } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; +import { Request } from 'express'; import { Observable } from 'rxjs'; export interface AuthenticatedRequest extends Request {