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
+
+
+
+
+
+
+
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
+
+
+
+
+
+
+
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
-
-
-
-
-
-
-
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
+
+
{filteredPosts.map((post) => (
-
-
{post.title}
-
{post.description.length > 100 ? `${post.description.slice(0, 100)}...` : post.description}
-
By: {post.author.username}
-
- {post.tags.map((tag, index) => (
- {tag}
- ))}
-
-
Read more
-
+
))}
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index a09aa97d..d74acdaa 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -1,7 +1,118 @@
+import { useEffect, useState } from "react";
+import { IUser } from "../types";
+import axios from "axios";
+import { useRecoilValue } from "recoil";
+import { tokenState } from "../store/atoms/auth";
+import Loader from "../components/Loader";
+import PostCard from "../components/PostCard";
+
const Profile = () => {
+ const [user, setUser] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState("");
+ const [otpSent, setOtpSent] = useState(false);
+ const [otp, setOtp] = useState("");
+ const [verificationError, setVerificationError] = useState("");
+ const token = useRecoilValue(tokenState);
+
+ useEffect(() => {
+ const fetchUser = async () => {
+ try {
+ const response = await axios.get('/api/v1/user/me', {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ setUser(response.data.user);
+ setLoading(false);
+ } catch (error) {
+ setError('Failed to fetch user details');
+ setLoading(false);
+ }
+ };
+
+ fetchUser();
+ }, [token]);
+
+ const handleGenerateOtp = async () => {
+ try {
+ await axios.post('/api/v1/user/generate-otp', {}, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+ setOtpSent(true);
+ setVerificationError("");
+ } catch (error) {
+ setVerificationError('Failed to generate OTP');
+ }
+ };
+
+ const handleVerifyOtp = async () => {
+ try {
+ await axios.post('/api/v1/user/verify-otp', { otp:Number(otp) }, {
+ headers: {
+ Authorization: `Bearer ${token}`
+ }
+ });
+
+ setUser(u => {
+ if (!u) return u;
+
+ u.verified = true;
+ return u
+ });
+ setOtpSent(false);
+ setOtp("");
+ setVerificationError("");
+ } catch (error) {
+ setVerificationError('Failed to verify OTP');
+ }
+ };
+
+ if (loading) {
+ return ;
+ }
+
+ if (error) {
+ return {error}
;
+ }
+
return (
- Profile
- )
-}
+
+
Username: {user?.username}
+
Email: {user?.email}
+
+
{user?.verified ? "Verified" : "User is not verified"}
+
+ {!user?.verified && (
+
+
+ {otpSent ? "Regenerate OTP" : "Verify Email"}
+
+ {otpSent && (
+
+ setOtp(e.target.value)}
+ placeholder="Enter OTP"
+ className="mt-2 p-2 border rounded text-gray-800"
+ />
+
+ Verify OTP
+
+
+ )}
+ {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 = () => {
-