From 0181d0ce91fbe5cf2a21861b28ce504bfe3d7a07 Mon Sep 17 00:00:00 2001 From: VaibhavArora314 Date: Fri, 31 May 2024 23:13:13 +0530 Subject: [PATCH] code refactoring, welcome email added, profile section, otp verification in profile section --- backend/package-lock.json | 282 +----------------- backend/package.json | 2 +- backend/prisma/schema.prisma | 1 + backend/src/helpers/mail/otpMailBody.ts | 71 +++++ backend/src/helpers/mail/sendOtpMail.ts | 20 ++ backend/src/helpers/mail/sendWelcomeMail.ts | 88 ++++++ backend/src/helpers/sendMail.ts | 93 ------ backend/src/routes/post/controller.ts | 27 +- backend/src/routes/user/controller.ts | 306 ++++++++++---------- backend/src/routes/user/route.ts | 22 +- backend/src/routes/user/zodSchema.ts | 4 + frontend/src/App.tsx | 3 - frontend/src/components/Navbar.tsx | 8 + frontend/src/components/PostCard.tsx | 41 +++ frontend/src/pages/NewPost.tsx | 37 ++- frontend/src/pages/Otp.jsx | 11 - frontend/src/pages/Post.tsx | 4 +- frontend/src/pages/Posts.tsx | 18 +- frontend/src/pages/Profile.tsx | 119 +++++++- frontend/src/pages/Signup.tsx | 133 ++++----- frontend/src/types.ts | 10 +- 21 files changed, 647 insertions(+), 653 deletions(-) create mode 100644 backend/src/helpers/mail/otpMailBody.ts create mode 100644 backend/src/helpers/mail/sendOtpMail.ts create mode 100644 backend/src/helpers/mail/sendWelcomeMail.ts delete mode 100644 backend/src/helpers/sendMail.ts create mode 100644 frontend/src/components/PostCard.tsx delete mode 100644 frontend/src/pages/Otp.jsx diff --git a/backend/package-lock.json b/backend/package-lock.json index 81eb0247..4cf728bb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,6 +14,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", + "@types/nodemailer": "^6.4.15", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", @@ -22,7 +23,6 @@ "express": "^4.19.2", "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.13", - "nodemon": "^3.1.1", "prisma": "^5.14.0", "ts-node": "^10.9.2", "typescript": "^5.4.5", @@ -239,6 +239,14 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.15", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.15.tgz", + "integrity": "sha512-0EBJxawVNjPkng1zm2vopRctuWVCxk34JcIlRuXSf54habUWdz1FB7wHDqOqvDa8Mtpt0Q3LTXQkAs2LNyK5jQ==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", @@ -344,18 +352,6 @@ "node": ">=8" } }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", @@ -402,17 +398,6 @@ "node": ">= 10.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -445,17 +430,6 @@ "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -487,29 +461,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/chownr": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", @@ -763,17 +714,6 @@ "node": ">= 0.10.0" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -834,19 +774,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -912,17 +839,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -934,14 +850,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "engines": { - "node": ">=4" - } - }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -1050,11 +958,6 @@ "node": ">=0.10.0" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -1077,25 +980,6 @@ "node": ">= 0.10" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1104,25 +988,6 @@ "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1379,54 +1244,6 @@ "node": ">=6.0.0" } }, - "node_modules/nodemon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.1.tgz", - "integrity": "sha512-k43xGaDtaDIcufn0Fc6fTtsdKSkV/hQzoQFigNH//GaKta28yoKVYXCnV+KXRqfT/YzsFaQU9VdeEG+HEyxr6A==", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", @@ -1441,14 +1258,6 @@ "node": ">=6" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -1517,17 +1326,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/prisma": { "version": "5.14.0", "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.14.0.tgz", @@ -1555,11 +1353,6 @@ "node": ">= 0.10" } }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==" - }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -1609,17 +1402,6 @@ "node": ">= 6" } }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1759,17 +1541,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1810,17 +1581,6 @@ "node": ">=8" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -1837,17 +1597,6 @@ "node": ">=10" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1856,14 +1605,6 @@ "node": ">=0.6" } }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, "node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", @@ -1935,11 +1676,6 @@ "node": ">=14.17" } }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" - }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", diff --git a/backend/package.json b/backend/package.json index 13aebdd1..9a8a988d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.6", + "@types/nodemailer": "^6.4.15", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "cors": "^2.8.5", @@ -26,7 +27,6 @@ "express": "^4.19.2", "jsonwebtoken": "^9.0.2", "nodemailer": "^6.9.13", - "nodemon": "^3.1.1", "prisma": "^5.14.0", "ts-node": "^10.9.2", "typescript": "^5.4.5", diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 546eccb2..e7a4381d 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -18,6 +18,7 @@ model User { username String email String passwordHash String + verified Boolean @default(false) otp Int? posts Post[] @relation("authorPosts") createdAt DateTime @default(now()) diff --git a/backend/src/helpers/mail/otpMailBody.ts b/backend/src/helpers/mail/otpMailBody.ts new file mode 100644 index 00000000..c8b72809 --- /dev/null +++ b/backend/src/helpers/mail/otpMailBody.ts @@ -0,0 +1,71 @@ +const getOtpMailBody = (otp:number) => { + return ` + + + + + + OTP Verification + + + +
+
+

OTP Verification

+
+
+

Hello,

+

Thank you for using style share. Please use the following OTP (One Time Password) to verify your email address:

+
${otp}
+

This OTP is valid for 10 minutes. Please do not share it with anyone.

+
+ +
+ + + ` +} + +export default getOtpMailBody; \ No newline at end of file diff --git a/backend/src/helpers/mail/sendOtpMail.ts b/backend/src/helpers/mail/sendOtpMail.ts new file mode 100644 index 00000000..c5ab8cf1 --- /dev/null +++ b/backend/src/helpers/mail/sendOtpMail.ts @@ -0,0 +1,20 @@ +import nodemailer from "nodemailer"; +import getOtpMailBody from "./otpMailBody"; + +export const sendVerificationEmail = async (email: string, otp: number) => { + let transporter = nodemailer.createTransport({ + service: "Gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }); + + let info = await transporter.sendMail({ + from: '"Style Share" ', + to: email, + subject: "Email Verification", + text: `Your OTP for email verification is ${otp}`, + html: getOtpMailBody(otp), + }); +}; diff --git a/backend/src/helpers/mail/sendWelcomeMail.ts b/backend/src/helpers/mail/sendWelcomeMail.ts new file mode 100644 index 00000000..4cea56ac --- /dev/null +++ b/backend/src/helpers/mail/sendWelcomeMail.ts @@ -0,0 +1,88 @@ +import nodemailer from "nodemailer"; + +export const sendWelcomeEmail = async (email: string, username: string) => { + let transporter = nodemailer.createTransport({ + service: "Gmail", + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_PASS, + }, + }); + + let info = await transporter.sendMail({ + from: '"Style Share" ', + to: email, + subject: "Welcome to Style Share!", + text: `Welcome to Style Share, ${username}!`, + html: getWelcomeMailBody(username), + }); +}; + +const getWelcomeMailBody = (username: string) => { + return ` + + + + + + Welcome to Style Share + + + +
+
+

Welcome to Style Share!

+
+
+

Hello ${username},

+

Thank you for signing up for Style Share. We are excited to have you on board!

+

If this account does not belong to you, please reset your password immediately from our website.

+
+ +
+ + + `; +}; + +export default getWelcomeMailBody; diff --git a/backend/src/helpers/sendMail.ts b/backend/src/helpers/sendMail.ts deleted file mode 100644 index 32715c6a..00000000 --- a/backend/src/helpers/sendMail.ts +++ /dev/null @@ -1,93 +0,0 @@ -// import nodemailer from 'nodemailer'; -// @ts-nocheck -import nodemailer from "nodemailer"; -// nodemailer = require('nodemailer'); - -export const sendVerificationEmail = async (email: string, otp: number) => { -// console.log("Email: ", process.env.EMAIL_USER, process.env.EMAIL_PASS) - let transporter = nodemailer.createTransport({ - service: 'Gmail', - auth: { - user: process.env.EMAIL_USER, - pass: process.env.EMAIL_PASS, - }, - }); - - let info = await transporter.sendMail({ - from: '"Style Share" ', - to: email, - subject: "Email Verification", - text: `Your OTP for email verification is ${otp}`, - // This html is a simple html template for the email body - html: ` - - - - - - OTP Verification - - - -
-
-

OTP Verification

-
-
-

Hello,

-

Thank you for signing up. To complete your registration, please use the following OTP (One Time Password) to verify your email address:

-
${otp}
-

This OTP is valid for 10 minutes. Please do not share it with anyone.

-
- -
- - - - ` - }); - -// console.log("Message sent: %s", info.messageId); -}; diff --git a/backend/src/routes/post/controller.ts b/backend/src/routes/post/controller.ts index f5cdb884..09eca147 100644 --- a/backend/src/routes/post/controller.ts +++ b/backend/src/routes/post/controller.ts @@ -13,15 +13,34 @@ export const createPostController = async ( if (!userId) { return res.status(403).json({ - error: "Invalid user", + error: { message: "Invalid user" }, + }); + } + + const user = await prisma.user.findFirst({ + where: { + id: userId, + }, + select: { + verified: true, + }, + }); + + if (!user?.verified) { + return res.status(403).json({ + error: { message: "User is not verified!" }, }); } const result = createPostSchema.safeParse(payload); if (!result.success) { + const formattedError: any = {}; + result.error.errors.forEach((e) => { + formattedError[e.path[0]] = e.message; + }); return res.status(411).json({ - error: result.error.errors, + error: { ...formattedError, message: "" }, }); } @@ -57,7 +76,9 @@ export const createPostController = async ( }); } catch (error) { return res.status(500).json({ - error: "An unexpected exception occurred!", + error: { + message: "An unexpected exception occurred!", + }, }); } }; diff --git a/backend/src/routes/user/controller.ts b/backend/src/routes/user/controller.ts index f29c1345..54fd02ff 100644 --- a/backend/src/routes/user/controller.ts +++ b/backend/src/routes/user/controller.ts @@ -1,12 +1,16 @@ import { Request, Response } from "express"; import prisma from "../../db"; -import { signinBodySchema, signupBodySchema } from "./zodSchema"; +import { + otpVerificationSchema, + signinBodySchema, + signupBodySchema, +} from "./zodSchema"; import { createHash, validatePassword } from "../../helpers/hash"; import { createJWT } from "../../helpers/jwt"; import { UserAuthRequest } from "../../helpers/types"; import crypto from "crypto"; -import { sendVerificationEmail } from "../../helpers/sendMail"; -import { z } from "zod"; +import { sendVerificationEmail } from "../../helpers/mail/sendOtpMail"; +import { sendWelcomeEmail } from "../../helpers/mail/sendWelcomeMail"; export const userSignupController = async (req: Request, res: Response) => { try { @@ -27,169 +31,55 @@ export const userSignupController = async (req: Request, res: Response) => { const existingUser = await prisma.user.findFirst({ where: { - OR: [ - { - email: data.email, - }, - { - username: data.username, - }, - ], + email: data.email, }, }); if (existingUser) { - if (!existingUser.otp) { - return res.status(411).json({ - error: { - message: "Username or email already in use.", - }, - }); - - } - } - - - - const passwordHash = await createHash(data.password); - - const otp = crypto.randomInt(100000, 999999); // Generate a 6-digit OTP - - let user; - if(!existingUser){ - user = await prisma.user.create({ - data: { - email: data.email, - passwordHash, - username: data.username, - // @ts-ignore - otp, - }, - select: { - id: true, - email: true, - username: true, + return res.status(411).json({ + error: { + message: "Email already in use.", }, }); } - else if(existingUser && existingUser.otp){ - user = await prisma.user.update({ - where: { - id: existingUser.id - }, - data: { - otp: otp, - passwordHash, - username: data.username, - }, - select: { - id: true, - email: true, - username: true, - } - }) - } - - - // Send OTP to user's email - await sendVerificationEmail(user!.email, otp); - + const passwordHash = await createHash(data.password); - res.status(201).json({ - message: "User created Successfully.", - user, - }); - } catch (error) { - console.log(error) - return res.status(500).json({ - error: { - message: "An unexpected exception occurred! Brooo", - display: error + const user = await prisma.user.create({ + data: { + email: data.email, + passwordHash, + username: data.username, }, - }); - } -}; - -// import prisma from "../../db"; - -const otpVerificationSchema = z.object({ - userId: z.string(), - otp: z.number(), - username: z.string(), -}); - -export const verifyOtpController = async (req: Request, res: Response) => { - try { - const payload = req.body; - const result = otpVerificationSchema.safeParse(payload); - - if (!result.success) { - const formattedError: any = {}; - result.error.errors.forEach((e) => { - formattedError[e.path[0]] = e.message; - }); - return res.status(400).json({ - error: { ...formattedError, message: "Validation error." }, - }); - } - - const { userId, otp, username } = result.data; - - const user = await prisma.user.findUnique({ - where: { id: userId }, select: { id: true, - otp: true, - createdAt: true, + email: true, + username: true, }, }); - if (!user) { - return res.status(404).json({ - error: { message: "User not found." }, - }); - } + await sendWelcomeEmail(user.email, user.username); - if (user.otp !== otp) { - return res.status(400).json({ - error: { message: "Invalid OTP." }, - }); - } - - const otpAge = Date.now() - new Date(user.createdAt).getTime(); - const otpExpiry = 10 * 60 * 1000; // 10 minutes - - if (otpAge > otpExpiry) { - return res.status(400).json({ - error: { message: "OTP has expired." }, - }); - } const token = createJWT({ - id: user!.id, - username: username, - }); - await prisma.user.update({ - where: { id: userId }, - data: { - otp: null, - }, + id: user.id, + username: user.username, }); - return res.status(200).json({ - message: "Email verified successfully.", + res.status(201).json({ + message: "User created Successfully.", + user, token, }); } catch (error) { - console.error("OTP verification error:", error); + console.log(error); return res.status(500).json({ - error: { message: "An unexpected error occurred." }, + error: { + message: "An unexpected exception occurred!", + }, }); } }; - - export const userSigninController = async (req: Request, res: Response) => { try { const payload = req.body; @@ -212,7 +102,6 @@ export const userSigninController = async (req: Request, res: Response) => { email: data.email, }, select: { - otp: true, id: true, username: true, email: true, @@ -228,15 +117,6 @@ export const userSigninController = async (req: Request, res: Response) => { }); } - if (user.otp) { - console.log("Email really not verified") - return res.status(411).json({ - error: { - message: "Email not verified", - }, - }); - } - const matchPassword = await validatePassword( data.password, user.passwordHash @@ -245,7 +125,7 @@ export const userSigninController = async (req: Request, res: Response) => { if (!matchPassword) { return res.status(411).json({ error: { - message: "No such user exists", + message: "Wrong Password", }, }); } @@ -283,6 +163,7 @@ export const userProfileController = async ( email: true, username: true, posts: true, + verified: true, }, }); @@ -296,3 +177,128 @@ export const userProfileController = async ( user, }); }; + +export const generateOtpController = async ( + req: UserAuthRequest, + res: Response +) => { + try { + const userId = req.userId; + + const user = await prisma.user.findFirst({ + where: { + id: userId, + }, + select: { + id: true, + email: true, + username: true, + posts: true, + verified: true, + }, + }); + + if (!user) { + return res.status(411).json({ + error: "Invalid token", + }); + } + + if (user.verified) { + return res.status(411).json({ + message: "User already verified", + }); + } + + const otp = crypto.randomInt(100000, 999999); // Generate a 6-digit OTP + + await prisma.user.update({ + where: { + id: userId, + }, + data: { + otp: otp, + } + }); + + await sendVerificationEmail(user.email, otp); + + res.status(201).json({ + message: "OTP sent Successfully.", + }); + } catch (error) { + console.error("OTP generation error:", error); + return res.status(500).json({ + error: { message: "An unexpected error occurred." }, + }); + } +}; + +export const verifyOtpController = async ( + req: UserAuthRequest, + res: Response +) => { + try { + const payload = req.body; + const result = otpVerificationSchema.safeParse(payload); + + if (!result.success) { + const formattedError: any = {}; + result.error.errors.forEach((e) => { + formattedError[e.path[0]] = e.message; + }); + return res.status(400).json({ + error: { ...formattedError, message: "Validation error." }, + }); + } + + const { otp } = result.data; + const usedId = req.userId; + + const user = await prisma.user.findUnique({ + where: { id: usedId }, + select: { + id: true, + otp: true, + createdAt: true, + updatedAt: true + }, + }); + + if (!user) { + return res.status(404).json({ + error: { message: "User not found." }, + }); + } + + if (user.otp !== otp) { + return res.status(400).json({ + error: { message: "Invalid OTP." }, + }); + } + + const otpAge = Date.now() - new Date(user.updatedAt).getTime(); + const otpExpiry = 10 * 60 * 1000; // 10 minutes + + if (otpAge > otpExpiry) { + return res.status(400).json({ + error: { message: "OTP has expired." }, + }); + } + await prisma.user.update({ + where: { id: usedId }, + data: { + otp: null, + verified: true + }, + }); + + return res.status(200).json({ + }); + } catch (error) { + console.error("OTP verification error:", error); + return res.status(500).json({ + error: { message: "An unexpected error occurred." }, + }); + } +}; diff --git a/backend/src/routes/user/route.ts b/backend/src/routes/user/route.ts index 24f1253c..1258849b 100644 --- a/backend/src/routes/user/route.ts +++ b/backend/src/routes/user/route.ts @@ -1,15 +1,23 @@ import { Router } from "express"; -import { userProfileController, userSigninController, userSignupController, verifyOtpController } from "./controller"; -import authMiddleware from "../../middleware/auth" +import { + generateOtpController, + userProfileController, + userSigninController, + userSignupController, + verifyOtpController, +} from "./controller"; +import authMiddleware from "../../middleware/auth"; const userRouter = Router(); -userRouter.post('/signup', userSignupController) +userRouter.post("/signup", userSignupController); -userRouter.post('/signin', userSigninController) +userRouter.post("/signin", userSigninController); -userRouter.post('/verify', verifyOtpController) +userRouter.post("/generate-otp", authMiddleware, generateOtpController); -userRouter.get('/me', authMiddleware, userProfileController); +userRouter.post("/verify-otp", authMiddleware, verifyOtpController); -export default userRouter; \ No newline at end of file +userRouter.get("/me", authMiddleware, userProfileController); + +export default userRouter; diff --git a/backend/src/routes/user/zodSchema.ts b/backend/src/routes/user/zodSchema.ts index 6ea69134..01869d69 100644 --- a/backend/src/routes/user/zodSchema.ts +++ b/backend/src/routes/user/zodSchema.ts @@ -19,3 +19,7 @@ export const signinBodySchema = zod.object({ .min(8, { message: "Password too short!" }) .max(30, { message: "Password too long!" }), }); + +export const otpVerificationSchema = zod.object({ + otp: zod.number(), +}); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ac1d0847..5c73538f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,8 +12,6 @@ import { RecoilRoot } from "recoil"; import NonAuthenticatedRoute from "./components/NonAuthenticatedRoute"; import AuthenticatedRoute from "./components/AuthenticatedRoute"; import Profile from "./pages/Profile"; -// @ts-expect-error -import OTP from "./pages/Otp.jsx"; import React from "react"; import Loader from "./components/Loader"; // import axios from "axios"; @@ -29,7 +27,6 @@ function App() {
} /> - } /> } /> } /> { New Post +
  • + + Profile + +
  • - {/* */}
  • + {otpSent && ( +
    + setOtp(e.target.value)} + placeholder="Enter OTP" + className="mt-2 p-2 border rounded text-gray-800" + /> + +
    + )} + {verificationError && ( +
    {verificationError}
    + )} + + )} + + {user?.posts.map(post => )} + + ); +}; -export default Profile \ No newline at end of file +export default Profile; diff --git a/frontend/src/pages/Signup.tsx b/frontend/src/pages/Signup.tsx index 2d6a413d..99e4685d 100644 --- a/frontend/src/pages/Signup.tsx +++ b/frontend/src/pages/Signup.tsx @@ -4,26 +4,25 @@ import { Link, useNavigate } from "react-router-dom"; import { useSetRecoilState } from "recoil"; import { tokenState } from "../store/atoms/auth"; import { AiOutlineEyeInvisible, AiOutlineEye } from "react-icons/ai"; -import zxcvbn, { ZXCVBNResult } from 'zxcvbn'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faCircle } from '@fortawesome/free-solid-svg-icons'; +import zxcvbn, { ZXCVBNResult } from "zxcvbn"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCheck, faCircle } from "@fortawesome/free-solid-svg-icons"; const Signup = () => { const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); - const [passwordStrength, setPasswordStrength] = useState(null); + const [passwordStrength, setPasswordStrength] = useState( + null + ); const [error, setError] = useState({ username: "", email: "", password: "", message: "", }); - const [userId, setUserId] = useState(null); - const [otpStr, setOtpStr] = useState(""); - const [token, setToken] = useState(""); - + const setTokenState = useSetRecoilState(tokenState); const navigate = useNavigate(); @@ -32,21 +31,21 @@ const Signup = () => { setPassword(newPassword); setPasswordStrength(zxcvbn(newPassword)); }; - + const strengthMeterColor = (score: number) => { switch (score) { case 0: - return 'bg-red-500'; + return "bg-red-500"; case 1: - return 'bg-yellow-500'; + return "bg-yellow-500"; case 2: - return 'bg-yellow-300'; + return "bg-yellow-300"; case 3: - return 'bg-green-300'; + return "bg-green-300"; case 4: - return 'bg-green-500'; + return "bg-green-500"; default: - return 'bg-gray-500'; + return "bg-gray-500"; } }; @@ -59,46 +58,22 @@ const Signup = () => { password, }); console.log(response); - setUserId(response.data?.user?.id); - } catch (e) { - const axiosError = e as AxiosError<{ - error: { - message: string; - }; - }>; - setError((prevError) => { - if (axiosError?.response?.data?.error) - return axiosError?.response?.data?.error as typeof prevError; - prevError.message = "An unexpected error occurred"; - return prevError; - }); - } - }; - - const handleOtpSubmit = async (e: React.MouseEvent) => { - e.preventDefault(); - try { - const response = await axios.post("/api/v1/user/verify", { - userId, - otp: parseInt(otpStr), - username: username - }); - console.log(response); - setToken(response.data?.token); setTokenState(response.data?.token); - localStorage.setItem("token", token || ""); - navigate('/app'); + localStorage.setItem("token", response.data?.token || ""); + navigate("/app"); } catch (e) { const axiosError = e as AxiosError<{ error: { message: string; }; }>; - setError((prevError) => { + + setError((e) => { if (axiosError?.response?.data?.error) - return axiosError?.response?.data?.error as typeof prevError; - prevError.message = "An unexpected error occurred"; - return prevError; + return axiosError?.response?.data?.error as typeof e; + + e.message = "An unexpected error occurred"; + return e; }); } }; @@ -149,7 +124,10 @@ const Signup = () => {

    -