diff --git a/package-lock.json b/package-lock.json index 720e01f..b91faee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1467,18 +1467,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", @@ -2129,9 +2117,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2189,19 +2177,32 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", - "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.14.0", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -2847,6 +2848,19 @@ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", "license": "MIT" }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -2913,6 +2927,16 @@ "node": ">=10" } }, + "node_modules/@paralleldrive/cuid2": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz", + "integrity": "sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, "node_modules/@pdf-lib/standard-fonts": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz", @@ -4824,24 +4848,26 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "node_modules/axios-retry": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-3.9.1.tgz", - "integrity": "sha512-8PJDLJv7qTTMMwdnbMvrLYuvB47M81wRtxQmEdV5w4rgbTXTt+vtPkXwajOfOdSyv/wZICJOC+/UhXH4aQ/R+w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", + "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", "license": "Apache-2.0", "dependencies": { - "@babel/runtime": "^7.15.4", "is-retry-allowed": "^2.2.0" + }, + "peerDependencies": { + "axios": "0.x || 1.x" } }, "node_modules/babel-jest": { @@ -4971,28 +4997,19 @@ } }, "node_modules/backblaze-b2": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-1.7.0.tgz", - "integrity": "sha512-8cVsKkXspuM1UeLI8WWSWw2JHfB7/IvqTtzvwhHqqhNyqcYl8iZ2lFpeuXGKcFA1TiSRlgALXWFJ9eKG6+3ZPg==", + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/backblaze-b2/-/backblaze-b2-1.7.1.tgz", + "integrity": "sha512-ns+oIO0NO5ysIIcs4xn2PfG34GlkGFLUckDWfQcsaCE2THDNRWzc5lWjR3LUrq6DA0E5KUGOhgkAs/q+QrgpNw==", "license": "MIT", "dependencies": { - "axios": "^0.21.1", - "axios-retry": "^3.1.9", + "axios": "^1.9.0", + "axios-retry": "^4.5.0", "lodash": "^4.17.21" }, "engines": { "node": ">=10.0" } }, - "node_modules/backblaze-b2/node_modules/axios": { - "version": "0.21.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -5201,9 +5218,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -6609,9 +6626,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7227,14 +7244,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -7242,16 +7260,19 @@ } }, "node_modules/formidable": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.2.tgz", - "integrity": "sha512-Jqc1btCy3QzRbJaICGwKcBfGWuLADRerLzDqi2NwSt/UkXLsHJw2TVResiaoBufHVHy9aSgClOHCeJsSsFLTbg==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { + "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", - "hexoid": "^2.0.0", "once": "^1.4.0" }, + "engines": { + "node": ">=14.0.0" + }, "funding": { "url": "https://ko-fi.com/tunnckoCore/commissions" } @@ -7525,9 +7546,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -7644,16 +7665,6 @@ "node": ">= 0.4" } }, - "node_modules/hexoid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-2.0.0.tgz", - "integrity": "sha512-qlspKUK7IlSQv2o+5I7yhUd7TxlOG2Vr5LTa3ve2XSNVKAL/n/u/7KLvKmFNimomDIKvZFXWHv0T12mv7rT8Aw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -8151,9 +8162,9 @@ } }, "node_modules/jake/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -11050,12 +11061,6 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, "node_modules/require-addon": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.1.0.tgz", @@ -12161,9 +12166,9 @@ } }, "node_modules/swagger-jsdoc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12276,9 +12281,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", - "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", "devOptional": true, "license": "MIT", "dependencies": { @@ -12345,9 +12350,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/prisma/migrations/20250808020526_init/migration.sql b/prisma/migrations/20250808020526_init/migration.sql new file mode 100644 index 0000000..505d6a6 --- /dev/null +++ b/prisma/migrations/20250808020526_init/migration.sql @@ -0,0 +1,73 @@ +/* + Warnings: + + - The primary key for the `UserVolunteer` table will be changed. If it partially fails, the table could be left without primary key constraint. + - You are about to drop the column `id` on the `UserVolunteer` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "Certificate" DROP CONSTRAINT "Certificate_volunteerId_fkey"; + +-- DropForeignKey +ALTER TABLE "CertificateDownloadLog" DROP CONSTRAINT "CertificateDownloadLog_certificateId_fkey"; + +-- DropForeignKey +ALTER TABLE "NFT" DROP CONSTRAINT "NFT_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "NFT" DROP CONSTRAINT "NFT_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "Project" DROP CONSTRAINT "Project_organizationId_fkey"; + +-- DropForeignKey +ALTER TABLE "UserVolunteer" DROP CONSTRAINT "UserVolunteer_userId_fkey"; + +-- DropForeignKey +ALTER TABLE "UserVolunteer" DROP CONSTRAINT "UserVolunteer_volunteerId_fkey"; + +-- DropForeignKey +ALTER TABLE "Volunteer" DROP CONSTRAINT "Volunteer_projectId_fkey"; + +-- DropForeignKey +ALTER TABLE "escrows" DROP CONSTRAINT "escrows_user_id_fkey"; + +-- DropIndex +DROP INDEX "UserVolunteer_userId_volunteerId_key"; + +-- AlterTable +ALTER TABLE "Project" ADD COLUMN "status" TEXT NOT NULL DEFAULT 'active'; + +-- AlterTable +ALTER TABLE "User" ADD COLUMN "verificationTokenExpires" TIMESTAMP(3); + +-- AlterTable +ALTER TABLE "UserVolunteer" DROP CONSTRAINT "UserVolunteer_pkey", +DROP COLUMN "id", +ADD COLUMN "hoursContributed" DOUBLE PRECISION NOT NULL DEFAULT 0, +ADD CONSTRAINT "UserVolunteer_pkey" PRIMARY KEY ("userId", "volunteerId"); + +-- AlterTable +ALTER TABLE "Volunteer" ADD COLUMN "maxVolunteers" INTEGER NOT NULL DEFAULT 10; + +-- CreateTable +CREATE TABLE "Message" ( + "id" TEXT NOT NULL, + "content" TEXT NOT NULL, + "sentAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "readAt" TIMESTAMP(3), + "senderId" TEXT NOT NULL, + "receiverId" TEXT NOT NULL, + "volunteerId" TEXT NOT NULL, + + CONSTRAINT "Message_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "Message_senderId_idx" ON "Message"("senderId"); + +-- CreateIndex +CREATE INDEX "Message_receiverId_idx" ON "Message"("receiverId"); + +-- CreateIndex +CREATE INDEX "Message_volunteerId_idx" ON "Message"("volunteerId"); diff --git a/src/modules/auth/presentation/controllers/Auth.controller.stub.ts b/src/modules/auth/presentation/controllers/Auth.controller.stub.ts deleted file mode 100644 index b60d9ff..0000000 --- a/src/modules/auth/presentation/controllers/Auth.controller.stub.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Request, Response } from "express"; - -/** - * Stub controller for Auth functionality - * This replaces the original controller that referenced deleted services - * TODO: Implement proper auth controller using new modular architecture - */ -class AuthController { - async register(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } - - async login(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } - - async resendVerificationEmail(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } - - async verifyEmail(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } - - async verifyWallet(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } - - async validateWalletFormat(req: Request, res: Response) { - res.status(501).json({ - message: "Auth service temporarily disabled during migration", - error: "Service migration in progress" - }); - } -} - -export default new AuthController(); \ No newline at end of file diff --git a/src/modules/auth/presentation/controllers/Auth.controller.ts b/src/modules/auth/presentation/controllers/Auth.controller.ts new file mode 100644 index 0000000..1ba7a6d --- /dev/null +++ b/src/modules/auth/presentation/controllers/Auth.controller.ts @@ -0,0 +1,172 @@ +import { Request, Response } from "express"; + +// imports for DTO validator +import { plainToInstance } from "class-transformer"; +import { validate } from "class-validator"; + +// Necessary DTOs +import { RegisterDto } from "../../dto/register.dto"; +import { LoginDto } from "../../dto/login.dto"; +import { ResendVerificationDTO } from "../../dto/resendVerificationDTO"; +import { + VerifyWalletDto, + ValidateWalletFormatDto, +} from "../../dto/wallet-validation.dto"; + +// Use cases +import { PrismaUserRepository } from "../../../user/repositories/PrismaUserRepository"; +import { SendVerificationEmailUseCase } from "../../use-cases/send-verification-email.usecase"; +import { ResendVerificationEmailUseCase } from "../../use-cases/resend-verification-email.usecase"; +import { VerifyEmailUseCase } from "../../use-cases/verify-email.usecase"; +import { ValidateWalletFormatUseCase } from "../../use-cases/wallet-format-validation.usecase"; +import { VerifyWalletUseCase } from "../../use-cases/verify-wallet.usecase"; + +const userRepository = new PrismaUserRepository(); +const sendVerificationEmailUseCase = new SendVerificationEmailUseCase( + userRepository +); +const resendVerificationEmailUseCase = new ResendVerificationEmailUseCase( + userRepository +); +const verifyEmailUseCase = new VerifyEmailUseCase(userRepository); +const validateWalletFormatUseCase = new ValidateWalletFormatUseCase(); +const verifyWalletUseCase = new VerifyWalletUseCase(); + +// DTO validator +async function validateOr400( + Cls: new () => T, + payload: unknown, + res: Response +): Promise { + const dto = plainToInstance(Cls, payload); + const errors = await validate(dto as object, { + whitelist: true, + forbidNonWhitelisted: true, + }); + + // dto not verified, throw a Bad Request + if (errors.length) { + res.status(400).json({ message: "Validation failed", errors }); + return; + } + + return dto; +} + +const register = async (req: Request, res: Response) => { + const dto = await validateOr400(RegisterDto, req.body, res); + if (!dto) return; + + try { + // Send verification email to provided address + await sendVerificationEmailUseCase.execute({ email: dto.email }); + res.status(200).json({ message: "Verification email sent" }); + } catch (err) { + const message = + err instanceof Error ? err.message : "Failed to send verification email"; + const status = message === "User not found" ? 400 : 500; + res.status(status).json({ error: message }); + } +}; + +const login = async (req: Request, res: Response) => { + const dto = await validateOr400(LoginDto, req.body, res); + if (!dto) return; + + // TODO: Implement Wallet auth logic as a use case + res.status(501).json({ + message: "Login service temporarily disabled", + error: "Wallet auth logic not implemented yet", + }); +}; + +const resendVerificationEmail = async (req: Request, res: Response) => { + const dto = await validateOr400(ResendVerificationDTO, req.body, res); + if (!dto) return; + + try { + // Resends verification email to provided address + await resendVerificationEmailUseCase.execute({ email: dto.email }); + res.status(200).json({ message: "Verification email resent" }); + } catch (err) { + const message = + err instanceof Error + ? err.message + : "Failed to resend verification email"; + const status = message === "User not found" ? 404 : 500; + res.status(status).json({ error: message }); + } +}; + +const verifyEmail = async (req: Request, res: Response) => { + const tokenParam = + typeof req.params.token === "string" ? req.params.token : undefined; + const tokenQuery = + typeof req.query.token === "string" + ? (req.query.token as string) + : undefined; + const token = tokenParam || tokenQuery; + + // if token is not given in the request + if (!token) { + res.status(400).json({ + success: false, + message: "Token in URL is required", + verified: false, + }); + return; + } + + try { + // Verifies email using use case + const result = await verifyEmailUseCase.execute({ token }); + const status = result.success ? 200 : 400; + res.status(status).json(result); + } catch { + res.status(400).json({ + success: false, + message: "Invalid or expired verification token", + verified: false, + }); + } +}; + +const verifyWallet = async (req: Request, res: Response) => { + const dto = await validateOr400(VerifyWalletDto, req.body, res); + if (!dto) return; + + try { + const result = await verifyWalletUseCase.execute(dto); + const status = result.verified ? 200 : 400; + res.status(status).json(result); + } catch (err) { + const message = + err instanceof Error ? err.message : "Wallet verification failed"; + res.status(500).json({ error: message }); + } +}; + +const validateWalletFormat = async (req: Request, res: Response) => { + const dto = await validateOr400(ValidateWalletFormatDto, req.body, res); + if (!dto) return; + + try { + // Validates wallet format using use case + const result = await validateWalletFormatUseCase.execute(dto); + const status = result.valid ? 200 : 400; + res.status(status).json(result); + } catch (err) { + const message = + err instanceof Error ? err.message : "Wallet format validation failed"; + res.status(500).json({ error: message }); + } +}; + +export default { + register, + login, + resendVerificationEmail, + verifyEmail, + verifyWallet, + validateWalletFormat, +}; diff --git a/src/modules/auth/use-cases/verify-wallet.usecase.ts b/src/modules/auth/use-cases/verify-wallet.usecase.ts new file mode 100644 index 0000000..a615ee4 --- /dev/null +++ b/src/modules/auth/use-cases/verify-wallet.usecase.ts @@ -0,0 +1,84 @@ +import { Keypair, StrKey, Horizon } from "@stellar/stellar-sdk"; +import { VerifyWalletDto } from "../dto/wallet-validation.dto"; +import { horizonConfig } from "../../../config/horizon.config"; + +type WalletVerificationResult = { + verified: boolean; + walletAddress: string; + error?: string; +}; + +export class VerifyWalletUseCase { + async execute(input: VerifyWalletDto): Promise { + const { walletAddress, signature, message } = input; + + // Validate public key format + if (!StrKey.isValidEd25519PublicKey(walletAddress)) { + return { + verified: false, + walletAddress, + error: "Invalid Stellar public key", + }; + } + + // Check that account exists on Horizon network before signature verification + try { + const server = new Horizon.Server(horizonConfig.url, { + allowHttp: horizonConfig.url.startsWith("http://"), + }); + await server.accounts().accountId(walletAddress).call(); + } catch (err: unknown) { + type HttpError = { response?: { status?: number } }; + const httpErr = err as HttpError; + + // If account not found on network, error + if (httpErr.response?.status === 404) { + return { + verified: false, + walletAddress, + error: "Account not found on Stellar network", + }; + } + return { + verified: false, + walletAddress, + error: err instanceof Error ? err.message : "Horizon query failed", + }; + } + + // Decode signature (expect base64) + let sig: Buffer; + try { + sig = Buffer.from(signature, "base64"); + } catch { + return { + verified: false, + walletAddress, + error: "Invalid signature encoding (base64)", + }; + } + if (!sig || sig.length === 0) { + return { verified: false, walletAddress, error: "Empty signature" }; + } + + const data = Buffer.from(message, "utf8"); + const keypair = Keypair.fromPublicKey(walletAddress); + + try { + const keypairVerification = keypair.verify(data, sig); + return keypairVerification + ? { verified: true, walletAddress } + : { + verified: false, + walletAddress, + error: "Signature verification failed", + }; + } catch (err) { + return { + verified: false, + walletAddress, + error: err instanceof Error ? err.message : "Verification error", + }; + } + } +} diff --git a/src/modules/auth/use-cases/wallet-format-validation.usecase.ts b/src/modules/auth/use-cases/wallet-format-validation.usecase.ts new file mode 100644 index 0000000..eb89ebd --- /dev/null +++ b/src/modules/auth/use-cases/wallet-format-validation.usecase.ts @@ -0,0 +1,26 @@ +import { plainToInstance } from "class-transformer"; +import { validate } from "class-validator"; +import { ValidateWalletFormatDto } from "../dto/wallet-validation.dto"; + +type WalletFormatValidationResult = { + valid: boolean; + errors?: string[]; +}; + +export class ValidateWalletFormatUseCase { + async execute(input: unknown): Promise { + const dto = plainToInstance(ValidateWalletFormatDto, input); + const errors = await validate(dto as object, { + whitelist: true, + forbidNonWhitelisted: true, + }); + + if (errors.length) { + const messages = errors.flatMap((e) => + Object.values(e.constraints ?? {}) + ); + return { valid: false, errors: messages }; + } + return { valid: true }; + } +} diff --git a/src/modules/certificate/infrastructure/utils/pdfGenerator.ts b/src/modules/certificate/infrastructure/utils/pdfGenerator.ts index 54cc931..e15b200 100644 --- a/src/modules/certificate/infrastructure/utils/pdfGenerator.ts +++ b/src/modules/certificate/infrastructure/utils/pdfGenerator.ts @@ -1,7 +1,7 @@ import { PDFDocument, rgb, StandardFonts } from "pdf-lib"; import fs from "fs"; import path from "path"; -import { generateQRCode } from "./qrGenerator"; +// import { generateQRCode } from "./qrGenerator"; //Function not found, commented out import { format } from "date-fns"; export async function generateCertificate({ diff --git a/src/routes/authRoutes.ts b/src/routes/authRoutes.ts index bcb67a8..6336aeb 100644 --- a/src/routes/authRoutes.ts +++ b/src/routes/authRoutes.ts @@ -1,5 +1,5 @@ import { Router } from "express"; -import AuthController from "../modules/auth/presentation/controllers/Auth.controller.stub"; +import AuthController from "../modules/auth/presentation/controllers/Auth.controller"; // import authMiddleware from "../middleware/authMiddleware"; // Temporarily disabled const router = Router(); @@ -9,7 +9,7 @@ router.get("/health", (req, res) => { res.json({ status: "Auth module is available", module: "auth" }); }); -// Public routes (using stub controller during migration) +// Public routes (now using functional controller) router.post("/register", AuthController.register); router.post("/login", AuthController.login);