├── .gitignore ├── apiList.md ├── homework.md ├── package-lock.json ├── package.json └── src ├── app.js ├── config └── database.js ├── middlewares └── auth.js ├── models ├── chat.js ├── connectionRequest.js ├── payment.js └── user.js ├── routes ├── auth.js ├── chat.js ├── payment.js ├── profile.js ├── request.js └── user.js └── utils ├── constants.js ├── cronjob.js ├── razorpay.js ├── sendEmail.js ├── sesClient.js ├── socket.js └── validation.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /apiList.md: -------------------------------------------------------------------------------- 1 | # DevTinder APIs 2 | 3 | ## authRouter 4 | - POST /signup 5 | - POST /login 6 | - POST /logout 7 | 8 | ## profileRouter 9 | - GET /profile/view 10 | - PATCH /profile/edit 11 | - PATCH /profile/password // Forgot password API 12 | 13 | ## connectionRequestRouter 14 | - POST /request/send/:status/:userId 15 | - POST /request/review/:status/:requestId 16 | 17 | ## userRouter 18 | - GET /user/requests/received 19 | - GET /user/connections 20 | - GET /user/feed - Gets you the profiles of other users on platform 21 | 22 | 23 | Status: ignored, interested, accepeted, rejected 24 | -------------------------------------------------------------------------------- /homework.md: -------------------------------------------------------------------------------- 1 | - Create a repository 2 | - Initialize the repository 3 | - node_modules, package.json, package-lock.json 4 | - Install express 5 | - Create a server 6 | - Listen to port 7777 7 | - Write request handlers for /test , /hello 8 | - Install nodemon and update scripts inside package.json 9 | - What are dependencies 10 | - What is the use of "-g" while npm install 11 | - Difference between caret and tilde ( ^ vs ~ ) 12 | 13 | - initialize git 14 | - .gitignore 15 | - Create a remote repo on github 16 | - Push all code to remote origin 17 | - Play with routes and route extensions ex. /hello, / , hello/2, /xyz 18 | - Order of the routes matter a lot 19 | - Install Postman app and make a workspace/collectio > test API call 20 | - Write logic to handle GET, POST, PATCH, DELETE API Calls and test them on Postman 21 | - Explore routing and use of ?, + , (), * in the routes 22 | - Use of regex in routes /a/ , /.*fly$/ 23 | - Reading the query params in the routes 24 | - Reading the dynamic routes 25 | 26 | - Multiple Route Handlers - Play with the code 27 | - next() 28 | - next function and errors along with res.send() 29 | - app.use("/route", rH, [rH2, rH3], rH4, rh5); 30 | - What is a Middleware? Why do we need it? 31 | - How express JS basically handles requests behind the scenes 32 | - Difference app.use and app.all 33 | - Write a dummy auth middleware for admin 34 | - Write a dummy auth middleware for all user routes, except /user/login 35 | - Error Handling using app.use("/", (err, req, res, next) = {}); 36 | 37 | - Create a free cluster on MongoDB official website (Mongo Atlas) 38 | - Install mongoose library 39 | - Connect your application to the Database "Connection-url"/devTinder 40 | - Call the connectDB function and connect to database before starting application on 7777 41 | - Create a userSchema & user Model 42 | - Create POST /sigup API to add data to database 43 | - Push some documents using API calls from postman 44 | - Error Handling using try , catch 45 | 46 | - JS object vs JSON (difference) 47 | - Add the express.json middleware to your app 48 | - Make your signup API dynamic to recive data from the end user 49 | - User.findOne with duplucate email ids, which object returned 50 | - API- Get user by email 51 | - API - Feed API - GET /feed - get all the users from the database 52 | - API - Get user by ID 53 | - Create a delete user API 54 | - Difference between PATCH and PUT 55 | - API - Update a user 56 | - Explore the Mongoose Documention for Model methods 57 | - What are options in a Model.findOneAndUpdate method, explore more about it 58 | - API - Update the user with email ID 59 | 60 | - Explore schematype options from the documention 61 | - add required, unique, lowercase, min, minLength, trim 62 | - Add default 63 | - Create a custom validate function for gender 64 | - Improve the DB schema - PUT all appropiate validations on each field in Schema 65 | - Add timestamps to the userSchema 66 | - Add API level validation on Patch request & Signup post api 67 | - DATA Sanitizing - Add API validation for each field 68 | - Install validator 69 | - Explore validator library funcation and Use vlidator funcs for password, email, photoURL 70 | - NEVER TRUST req.body 71 | 72 | - Validate data in Signup API 73 | - Install bcrypt package 74 | - Create PasswordHash using bcrypt.hash & save the user is excrupted password 75 | - Create login API 76 | - Compare passwords and throw errors if email or password is invalid 77 | 78 | - install cookie-parser 79 | - just send a dummy cookie to user 80 | - create GET /profile APi and check if you get the cookie back 81 | - install jsonwebtoken 82 | - IN login API, after email and password validation, create e JWT token and send it to user in cookies 83 | - read the cookies inside your profile API and find the logged in user 84 | - userAuth Middleware 85 | - Add the userAuth middle ware in profile API and a new sendConnectionRequest API 86 | - Set the expiry of JWT token and cookies to 7 days 87 | - Create userSchema method to getJWT() 88 | - Create UserSchema method to comparepassword(passwordInputByUser) 89 | 90 | - Explore tinder APIs 91 | - Create a list all API you can think of in Dev Tinder 92 | - Group multiple routes under repective routers 93 | - Read documentation for express.Router 94 | - Create routes folder for managing auth,profile, request routers 95 | - create authRouter, profileRouter, requestRouter 96 | - Import these routers in app.js 97 | - Create POST /logout API 98 | - Create PATCH /profile/edit 99 | - Create PATCH /profile/password API => forgot password API 100 | - Make you validate all data in every POST, PATCH apis 101 | 102 | - Create Connnection Request Schema 103 | - Send Connection Request API 104 | - Proper validation of Data 105 | - Think about ALL corner cases 106 | - $or query $and query in mongoose - https://www.mongodb.com/docs/manual/reference/operator/query-logical/ 107 | - schema.pre("save") function 108 | - Read more about indexes in MongoDB 109 | - Why do we need index in DB? 110 | - What is the advantages and disadvantage of creating? 111 | - Read this arcticle about compond indexes - https://www.mongodb.com/docs/manual/core/indexes/index-types/index-compound/ 112 | - ALWAYS THINK ABOUT CORNER CASES 113 | 114 | 115 | - Write code with proper validations for POST /request/review/:status/:requestId 116 | - Thought process - POST vs GET 117 | - Read about ref and populate https://mongoosejs.com/docs/populate.html 118 | - Create GET /user/requests/received with all the checks 119 | - Create GET GET /user/connections 120 | 121 | - Logic for GET /feed API 122 | - Explore the $nin , $and, $ne and other query operatorators 123 | - Pagination 124 | 125 | 126 | 127 | NOTES: 128 | 129 | /feed?page=1&limit=10 => 1-10 => .skip(0) & .limit(10) 130 | 131 | /feed?page=2&limit=10 => 11-20 => .skip(10) & .limit(10) 132 | 133 | /feed?page=3&limit=10 => 21-30 => .skip(20) & .limit(10) 134 | 135 | /feed?page=4&limit=10 => 21-30 => .skip(20) & .limit(10) 136 | 137 | skip = (page-1)*limit; 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devtinder", 3 | "version": "1.0.0", 4 | "description": "tinder for devs", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node src/app.js", 8 | "dev": "nodemon src/app.js" 9 | }, 10 | "keywords": [ 11 | "nodejs", 12 | "javascript" 13 | ], 14 | "author": "Akshay Saini", 15 | "license": "ISC", 16 | "dependencies": { 17 | "@aws-sdk/client-ses": "^3.716.0", 18 | "bcrypt": "^5.1.1", 19 | "cookie-parser": "^1.4.6", 20 | "cors": "^2.8.5", 21 | "date-fns": "^4.1.0", 22 | "dotenv": "^16.4.7", 23 | "express": "^4.19.2", 24 | "jsonwebtoken": "^9.0.2", 25 | "mongoose": "^8.6.1", 26 | "node-cron": "^3.0.3", 27 | "razorpay": "^2.9.5", 28 | "socket.io": "^4.8.1", 29 | "validator": "^13.12.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const connectDB = require("./config/database"); 3 | const app = express(); 4 | const cookieParser = require("cookie-parser"); 5 | const cors = require("cors"); 6 | const http = require("http"); 7 | 8 | require("dotenv").config(); 9 | 10 | require("./utils/cronjob"); 11 | 12 | app.use( 13 | cors({ 14 | origin: "http://localhost:5173", 15 | credentials: true, 16 | }) 17 | ); 18 | app.use(express.json()); 19 | app.use(cookieParser()); 20 | 21 | const authRouter = require("./routes/auth"); 22 | const profileRouter = require("./routes/profile"); 23 | const requestRouter = require("./routes/request"); 24 | const userRouter = require("./routes/user"); 25 | const paymentRouter = require("./routes/payment"); 26 | const initializeSocket = require("./utils/socket"); 27 | const chatRouter = require("./routes/chat"); 28 | 29 | app.use("/", authRouter); 30 | app.use("/", profileRouter); 31 | app.use("/", requestRouter); 32 | app.use("/", userRouter); 33 | app.use("/", paymentRouter); 34 | app.use("/", chatRouter); 35 | 36 | const server = http.createServer(app); 37 | initializeSocket(server); 38 | 39 | connectDB() 40 | .then(() => { 41 | console.log("Database connection established..."); 42 | server.listen(process.env.PORT, () => { 43 | console.log("Server is successfully listening on port 7777..."); 44 | }); 45 | }) 46 | .catch((err) => { 47 | console.error("Database cannot be connected!!"); 48 | }); 49 | -------------------------------------------------------------------------------- /src/config/database.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectDB = async () => { 4 | console.log(process.env.DB_CONNECTION_SECRET); 5 | await mongoose.connect(process.env.DB_CONNECTION_SECRET); 6 | }; 7 | 8 | module.exports = connectDB; 9 | -------------------------------------------------------------------------------- /src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const User = require("../models/user"); 3 | 4 | const userAuth = async (req, res, next) => { 5 | try { 6 | const { token } = req.cookies; 7 | if (!token) { 8 | return res.status(401).send("Please Login!"); 9 | } 10 | 11 | const decodedObj = await jwt.verify(token, process.env.JWT_SECRET); 12 | 13 | const { _id } = decodedObj; 14 | 15 | const user = await User.findById(_id); 16 | if (!user) { 17 | throw new Error("User not found"); 18 | } 19 | 20 | req.user = user; 21 | next(); 22 | } catch (err) { 23 | res.status(400).send("ERROR: " + err.message); 24 | } 25 | }; 26 | 27 | module.exports = { 28 | userAuth, 29 | }; 30 | -------------------------------------------------------------------------------- /src/models/chat.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const messageSchema = new mongoose.Schema( 4 | { 5 | senderId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "User", 8 | required: true, 9 | }, 10 | text: { 11 | type: String, 12 | required: true, 13 | }, 14 | }, 15 | { timestamps: true } 16 | ); 17 | 18 | const chatSchema = new mongoose.Schema({ 19 | participants: [ 20 | { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, 21 | ], 22 | messages: [messageSchema], 23 | }); 24 | 25 | const Chat = mongoose.model("Chat", chatSchema); 26 | 27 | module.exports = { Chat }; 28 | -------------------------------------------------------------------------------- /src/models/connectionRequest.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const connectionRequestSchema = new mongoose.Schema( 4 | { 5 | fromUserId: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | ref: "User", 8 | required: true, 9 | }, 10 | toUserId: { 11 | type: mongoose.Schema.Types.ObjectId, 12 | ref: "User", 13 | required: true, 14 | }, 15 | status: { 16 | type: String, 17 | required: true, 18 | enum: { 19 | values: ["ignored", "interested", "accepted", "rejected"], 20 | message: `{VALUE} is incorrect status type`, 21 | }, 22 | }, 23 | }, 24 | { timestamps: true } 25 | ); 26 | 27 | // ConnectionRequest.find({fromUserId: 273478465864786587, toUserId: 273478465864786587}) 28 | 29 | connectionRequestSchema.index({ fromUserId: 1, toUserId: 1 }); 30 | 31 | connectionRequestSchema.pre("save", function (next) { 32 | const connectionRequest = this; 33 | // Check if the fromUserId is same as toUserId 34 | if (connectionRequest.fromUserId.equals(connectionRequest.toUserId)) { 35 | throw new Error("Cannot send connection request to yourself!"); 36 | } 37 | next(); 38 | }); 39 | 40 | const ConnectionRequestModel = new mongoose.model( 41 | "ConnectionRequest", 42 | connectionRequestSchema 43 | ); 44 | 45 | module.exports = ConnectionRequestModel; 46 | -------------------------------------------------------------------------------- /src/models/payment.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const paymentSchema = new mongoose.Schema( 4 | { 5 | userId: { 6 | type: mongoose.Types.ObjectId, 7 | ref: "User", 8 | required: true, 9 | }, 10 | paymentId: { 11 | type: String, 12 | }, 13 | orderId: { 14 | type: String, 15 | required: true, 16 | }, 17 | status: { 18 | type: String, 19 | required: true, 20 | }, 21 | amount: { 22 | type: Number, 23 | required: true, 24 | }, 25 | currency: { 26 | type: String, 27 | required: true, 28 | }, 29 | receipt: { 30 | type: String, 31 | required: true, 32 | }, 33 | notes: { 34 | firstName: { 35 | type: String, 36 | }, 37 | lastName: { 38 | type: String, 39 | }, 40 | membershipType: { 41 | type: String, 42 | }, 43 | }, 44 | }, 45 | { timestamps: true } 46 | ); 47 | 48 | module.exports = mongoose.model("Payment", paymentSchema); 49 | -------------------------------------------------------------------------------- /src/models/user.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const validator = require("validator"); 3 | const jwt = require("jsonwebtoken"); 4 | const bcrypt = require("bcrypt"); 5 | 6 | const userSchema = new mongoose.Schema( 7 | { 8 | firstName: { 9 | type: String, 10 | required: true, 11 | minLength: 4, 12 | maxLength: 50, 13 | }, 14 | lastName: { 15 | type: String, 16 | }, 17 | emailId: { 18 | type: String, 19 | lowercase: true, 20 | required: true, 21 | unique: true, 22 | trim: true, 23 | validate(value) { 24 | if (!validator.isEmail(value)) { 25 | throw new Error("Invalid email address: " + value); 26 | } 27 | }, 28 | }, 29 | password: { 30 | type: String, 31 | required: true, 32 | validate(value) { 33 | if (!validator.isStrongPassword(value)) { 34 | throw new Error("Enter a Strong Password: " + value); 35 | } 36 | }, 37 | }, 38 | age: { 39 | type: Number, 40 | min: 18, 41 | }, 42 | gender: { 43 | type: String, 44 | enum: { 45 | values: ["male", "female", "other"], 46 | message: `{VALUE} is not a valid gender type`, 47 | }, 48 | // validate(value) { 49 | // if (!["male", "female", "others"].includes(value)) { 50 | // throw new Error("Gender data is not valid"); 51 | // } 52 | // }, 53 | }, 54 | isPremium: { 55 | type: Boolean, 56 | default: false, 57 | }, 58 | membershipType: { 59 | type: String, 60 | }, 61 | photoUrl: { 62 | type: String, 63 | default: "https://geographyandyou.com/images/user-profile.png", 64 | validate(value) { 65 | if (!validator.isURL(value)) { 66 | throw new Error("Invalid Photo URL: " + value); 67 | } 68 | }, 69 | }, 70 | about: { 71 | type: String, 72 | default: "This is a default about of the user!", 73 | }, 74 | skills: { 75 | type: [String], 76 | }, 77 | }, 78 | { 79 | timestamps: true, 80 | } 81 | ); 82 | 83 | userSchema.methods.getJWT = async function () { 84 | const user = this; 85 | 86 | const token = await jwt.sign({ _id: user._id }, "DEV@Tinder$790", { 87 | expiresIn: "7d", 88 | }); 89 | 90 | return token; 91 | }; 92 | 93 | userSchema.methods.validatePassword = async function (passwordInputByUser) { 94 | const user = this; 95 | const passwordHash = user.password; 96 | 97 | const isPasswordValid = await bcrypt.compare( 98 | passwordInputByUser, 99 | passwordHash 100 | ); 101 | 102 | return isPasswordValid; 103 | }; 104 | 105 | module.exports = mongoose.model("User", userSchema); 106 | -------------------------------------------------------------------------------- /src/routes/auth.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const authRouter = express.Router(); 3 | 4 | const { validateSignUpData } = require("../utils/validation"); 5 | const User = require("../models/user"); 6 | const bcrypt = require("bcrypt"); 7 | 8 | authRouter.post("/signup", async (req, res) => { 9 | try { 10 | // Validation of data 11 | validateSignUpData(req); 12 | 13 | const { firstName, lastName, emailId, password } = req.body; 14 | 15 | // Encrypt the password 16 | const passwordHash = await bcrypt.hash(password, 10); 17 | console.log(passwordHash); 18 | 19 | // Creating a new instance of the User model 20 | const user = new User({ 21 | firstName, 22 | lastName, 23 | emailId, 24 | password: passwordHash, 25 | }); 26 | 27 | const savedUser = await user.save(); 28 | const token = await savedUser.getJWT(); 29 | 30 | res.cookie("token", token, { 31 | expires: new Date(Date.now() + 8 * 3600000), 32 | }); 33 | 34 | res.json({ message: "User Added successfully!", data: savedUser }); 35 | } catch (err) { 36 | res.status(400).send("ERROR : " + err.message); 37 | } 38 | }); 39 | 40 | authRouter.post("/login", async (req, res) => { 41 | try { 42 | const { emailId, password } = req.body; 43 | 44 | const user = await User.findOne({ emailId: emailId }); 45 | if (!user) { 46 | throw new Error("Invalid credentials"); 47 | } 48 | const isPasswordValid = await user.validatePassword(password); 49 | 50 | if (isPasswordValid) { 51 | const token = await user.getJWT(); 52 | 53 | res.cookie("token", token, { 54 | expires: new Date(Date.now() + 8 * 3600000), 55 | }); 56 | res.send(user); 57 | } else { 58 | throw new Error("Invalid credentials"); 59 | } 60 | } catch (err) { 61 | res.status(400).send("ERROR : " + err.message); 62 | } 63 | }); 64 | 65 | authRouter.post("/logout", async (req, res) => { 66 | res.cookie("token", null, { 67 | expires: new Date(Date.now()), 68 | }); 69 | res.send("Logout Successful!!"); 70 | }); 71 | 72 | module.exports = authRouter; 73 | -------------------------------------------------------------------------------- /src/routes/chat.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { userAuth } = require("../middlewares/auth"); 3 | const { Chat } = require("../models/chat"); 4 | 5 | const chatRouter = express.Router(); 6 | 7 | chatRouter.get("/chat/:targetUserId", userAuth, async (req, res) => { 8 | const { targetUserId } = req.params; 9 | const userId = req.user._id; 10 | 11 | try { 12 | let chat = await Chat.findOne({ 13 | participants: { $all: [userId, targetUserId] }, 14 | }).populate({ 15 | path: "messages.senderId", 16 | select: "firstName lastName", 17 | }); 18 | if (!chat) { 19 | chat = new Chat({ 20 | participants: [userId, targetUserId], 21 | messages: [], 22 | }); 23 | await chat.save(); 24 | } 25 | res.json(chat); 26 | } catch (err) { 27 | console.error(err); 28 | } 29 | }); 30 | 31 | module.exports = chatRouter; 32 | -------------------------------------------------------------------------------- /src/routes/payment.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { userAuth } = require("../middlewares/auth"); 3 | const paymentRouter = express.Router(); 4 | const razorpayInstance = require("../utils/razorpay"); 5 | const Payment = require("../models/payment"); 6 | const User = require("../models/user"); 7 | const { membershipAmount } = require("../utils/constants"); 8 | const { 9 | validateWebhookSignature, 10 | } = require("razorpay/dist/utils/razorpay-utils"); 11 | 12 | paymentRouter.post("/payment/create", userAuth, async (req, res) => { 13 | try { 14 | const { membershipType } = req.body; 15 | const { firstName, lastName, emailId } = req.user; 16 | 17 | const order = await razorpayInstance.orders.create({ 18 | amount: membershipAmount[membershipType] * 100, 19 | currency: "INR", 20 | receipt: "receipt#1", 21 | notes: { 22 | firstName, 23 | lastName, 24 | emailId, 25 | membershipType: membershipType, 26 | }, 27 | }); 28 | 29 | // Save it in my database 30 | console.log(order); 31 | 32 | const payment = new Payment({ 33 | userId: req.user._id, 34 | orderId: order.id, 35 | status: order.status, 36 | amount: order.amount, 37 | currency: order.currency, 38 | receipt: order.receipt, 39 | notes: order.notes, 40 | }); 41 | 42 | const savedPayment = await payment.save(); 43 | 44 | // Return back my order details to frontend 45 | res.json({ ...savedPayment.toJSON(), keyId: process.env.RAZORPAY_KEY_ID }); 46 | } catch (err) { 47 | return res.status(500).json({ msg: err.message }); 48 | } 49 | }); 50 | 51 | paymentRouter.post("/payment/webhook", async (req, res) => { 52 | try { 53 | console.log("Webhook Called"); 54 | const webhookSignature = req.get("X-Razorpay-Signature"); 55 | console.log("Webhook Signature", webhookSignature); 56 | 57 | const isWebhookValid = validateWebhookSignature( 58 | JSON.stringify(req.body), 59 | webhookSignature, 60 | process.env.RAZORPAY_WEBHOOK_SECRET 61 | ); 62 | 63 | if (!isWebhookValid) { 64 | console.log("INvalid Webhook Signature"); 65 | return res.status(400).json({ msg: "Webhook signature is invalid" }); 66 | } 67 | console.log("Valid Webhook Signature"); 68 | 69 | // Udpate my payment Status in DB 70 | const paymentDetails = req.body.payload.payment.entity; 71 | 72 | const payment = await Payment.findOne({ orderId: paymentDetails.order_id }); 73 | payment.status = paymentDetails.status; 74 | await payment.save(); 75 | console.log("Payment saved"); 76 | 77 | const user = await User.findOne({ _id: payment.userId }); 78 | user.isPremium = true; 79 | user.membershipType = payment.notes.membershipType; 80 | console.log("User saved"); 81 | 82 | await user.save(); 83 | 84 | // Update the user as premium 85 | 86 | // if (req.body.event == "payment.captured") { 87 | // } 88 | // if (req.body.event == "payment.failed") { 89 | // } 90 | 91 | // return success response to razorpay 92 | 93 | return res.status(200).json({ msg: "Webhook received successfully" }); 94 | } catch (err) { 95 | return res.status(500).json({ msg: err.message }); 96 | } 97 | }); 98 | 99 | paymentRouter.get("/premium/verify", userAuth, async (req, res) => { 100 | const user = req.user.toJSON(); 101 | console.log(user); 102 | if (user.isPremium) { 103 | return res.json({ ...user }); 104 | } 105 | return res.json({ ...user }); 106 | }); 107 | 108 | module.exports = paymentRouter; 109 | -------------------------------------------------------------------------------- /src/routes/profile.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const profileRouter = express.Router(); 3 | 4 | const { userAuth } = require("../middlewares/auth"); 5 | const { validateEditProfileData } = require("../utils/validation"); 6 | 7 | profileRouter.get("/profile/view", userAuth, async (req, res) => { 8 | try { 9 | const user = req.user; 10 | 11 | res.send(user); 12 | } catch (err) { 13 | res.status(400).send("ERROR : " + err.message); 14 | } 15 | }); 16 | 17 | profileRouter.patch("/profile/edit", userAuth, async (req, res) => { 18 | try { 19 | if (!validateEditProfileData(req)) { 20 | throw new Error("Invalid Edit Request"); 21 | } 22 | 23 | const loggedInUser = req.user; 24 | 25 | Object.keys(req.body).forEach((key) => (loggedInUser[key] = req.body[key])); 26 | 27 | await loggedInUser.save(); 28 | 29 | res.json({ 30 | message: `${loggedInUser.firstName}, your profile updated successfuly`, 31 | data: loggedInUser, 32 | }); 33 | } catch (err) { 34 | res.status(400).send("ERROR : " + err.message); 35 | } 36 | }); 37 | 38 | module.exports = profileRouter; 39 | -------------------------------------------------------------------------------- /src/routes/request.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const requestRouter = express.Router(); 3 | 4 | const { userAuth } = require("../middlewares/auth"); 5 | const ConnectionRequest = require("../models/connectionRequest"); 6 | const User = require("../models/user"); 7 | 8 | const sendEmail = require("../utils/sendEmail"); 9 | 10 | requestRouter.post( 11 | "/request/send/:status/:toUserId", 12 | userAuth, 13 | async (req, res) => { 14 | try { 15 | const fromUserId = req.user._id; 16 | const toUserId = req.params.toUserId; 17 | const status = req.params.status; 18 | 19 | const allowedStatus = ["ignored", "interested"]; 20 | if (!allowedStatus.includes(status)) { 21 | return res 22 | .status(400) 23 | .json({ message: "Invalid status type: " + status }); 24 | } 25 | 26 | const toUser = await User.findById(toUserId); 27 | if (!toUser) { 28 | return res.status(404).json({ message: "User not found!" }); 29 | } 30 | 31 | const existingConnectionRequest = await ConnectionRequest.findOne({ 32 | $or: [ 33 | { fromUserId, toUserId }, 34 | { fromUserId: toUserId, toUserId: fromUserId }, 35 | ], 36 | }); 37 | if (existingConnectionRequest) { 38 | return res 39 | .status(400) 40 | .send({ message: "Connection Request Already Exists!!" }); 41 | } 42 | 43 | const connectionRequest = new ConnectionRequest({ 44 | fromUserId, 45 | toUserId, 46 | status, 47 | }); 48 | 49 | const data = await connectionRequest.save(); 50 | 51 | // const emailRes = await sendEmail.run( 52 | // "A new friend request from " + req.user.firstName, 53 | // req.user.firstName + " is " + status + " in " + toUser.firstName 54 | // ); 55 | // console.log(emailRes); 56 | 57 | res.json({ 58 | message: 59 | req.user.firstName + " is " + status + " in " + toUser.firstName, 60 | data, 61 | }); 62 | } catch (err) { 63 | res.status(400).send("ERROR: " + err.message); 64 | } 65 | } 66 | ); 67 | 68 | requestRouter.post( 69 | "/request/review/:status/:requestId", 70 | userAuth, 71 | async (req, res) => { 72 | try { 73 | const loggedInUser = req.user; 74 | const { status, requestId } = req.params; 75 | 76 | const allowedStatus = ["accepted", "rejected"]; 77 | if (!allowedStatus.includes(status)) { 78 | return res.status(400).json({ messaage: "Status not allowed!" }); 79 | } 80 | 81 | const connectionRequest = await ConnectionRequest.findOne({ 82 | _id: requestId, 83 | toUserId: loggedInUser._id, 84 | status: "interested", 85 | }); 86 | if (!connectionRequest) { 87 | return res 88 | .status(404) 89 | .json({ message: "Connection request not found" }); 90 | } 91 | 92 | connectionRequest.status = status; 93 | 94 | const data = await connectionRequest.save(); 95 | 96 | res.json({ message: "Connection request " + status, data }); 97 | } catch (err) { 98 | res.status(400).send("ERROR: " + err.message); 99 | } 100 | } 101 | ); 102 | 103 | module.exports = requestRouter; 104 | -------------------------------------------------------------------------------- /src/routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const userRouter = express.Router(); 3 | 4 | const { userAuth } = require("../middlewares/auth"); 5 | const ConnectionRequest = require("../models/connectionRequest"); 6 | const User = require("../models/user"); 7 | 8 | const USER_SAFE_DATA = "firstName lastName photoUrl age gender about skills"; 9 | 10 | // Get all the pending connection request for the loggedIn user 11 | userRouter.get("/user/requests/received", userAuth, async (req, res) => { 12 | try { 13 | const loggedInUser = req.user; 14 | 15 | const connectionRequests = await ConnectionRequest.find({ 16 | toUserId: loggedInUser._id, 17 | status: "interested", 18 | }).populate("fromUserId", USER_SAFE_DATA); 19 | // }).populate("fromUserId", ["firstName", "lastName"]); 20 | 21 | res.json({ 22 | message: "Data fetched successfully", 23 | data: connectionRequests, 24 | }); 25 | } catch (err) { 26 | req.statusCode(400).send("ERROR: " + err.message); 27 | } 28 | }); 29 | 30 | userRouter.get("/user/connections", userAuth, async (req, res) => { 31 | try { 32 | const loggedInUser = req.user; 33 | 34 | const connectionRequests = await ConnectionRequest.find({ 35 | $or: [ 36 | { toUserId: loggedInUser._id, status: "accepted" }, 37 | { fromUserId: loggedInUser._id, status: "accepted" }, 38 | ], 39 | }) 40 | .populate("fromUserId", USER_SAFE_DATA) 41 | .populate("toUserId", USER_SAFE_DATA); 42 | 43 | console.log(connectionRequests); 44 | 45 | const data = connectionRequests.map((row) => { 46 | if (row.fromUserId._id.toString() === loggedInUser._id.toString()) { 47 | return row.toUserId; 48 | } 49 | return row.fromUserId; 50 | }); 51 | 52 | res.json({ data }); 53 | } catch (err) { 54 | res.status(400).send({ message: err.message }); 55 | } 56 | }); 57 | 58 | userRouter.get("/feed", userAuth, async (req, res) => { 59 | try { 60 | const loggedInUser = req.user; 61 | 62 | const page = parseInt(req.query.page) || 1; 63 | let limit = parseInt(req.query.limit) || 10; 64 | limit = limit > 50 ? 50 : limit; 65 | const skip = (page - 1) * limit; 66 | 67 | const connectionRequests = await ConnectionRequest.find({ 68 | $or: [{ fromUserId: loggedInUser._id }, { toUserId: loggedInUser._id }], 69 | }).select("fromUserId toUserId"); 70 | 71 | const hideUsersFromFeed = new Set(); 72 | connectionRequests.forEach((req) => { 73 | hideUsersFromFeed.add(req.fromUserId.toString()); 74 | hideUsersFromFeed.add(req.toUserId.toString()); 75 | }); 76 | 77 | const users = await User.find({ 78 | $and: [ 79 | { _id: { $nin: Array.from(hideUsersFromFeed) } }, 80 | { _id: { $ne: loggedInUser._id } }, 81 | ], 82 | }) 83 | .select(USER_SAFE_DATA) 84 | .skip(skip) 85 | .limit(limit); 86 | 87 | res.json({ data: users }); 88 | } catch (err) { 89 | res.status(400).json({ message: err.message }); 90 | } 91 | }); 92 | module.exports = userRouter; 93 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | const membershipAmount = { 2 | silver: 300, 3 | gold: 700, 4 | }; 5 | 6 | module.exports = { membershipAmount }; 7 | -------------------------------------------------------------------------------- /src/utils/cronjob.js: -------------------------------------------------------------------------------- 1 | const cron = require("node-cron"); 2 | const { subDays, startOfDay, endOfDay } = require("date-fns"); 3 | const sendEmail = require("./sendEmail"); 4 | const ConnectionRequestModel = require("../models/connectionRequest"); 5 | 6 | // This job will run at 8 AM in the morning everyday 7 | cron.schedule("0 8 * * *", async () => { 8 | // Send emails to all people who got requests the previous day 9 | try { 10 | const yesterday = subDays(new Date(), 1); 11 | 12 | const yesterdayStart = startOfDay(yesterday); 13 | const yesterdayEnd = endOfDay(yesterday); 14 | 15 | const pendingRequests = await ConnectionRequestModel.find({ 16 | status: "interested", 17 | createdAt: { 18 | $gte: yesterdayStart, 19 | $lt: yesterdayEnd, 20 | }, 21 | }).populate("fromUserId toUserId"); 22 | 23 | const listOfEmails = [ 24 | ...new Set(pendingRequests.map((req) => req.toUserId.emailId)), 25 | ]; 26 | 27 | console.log(listOfEmails); 28 | 29 | for (const email of listOfEmails) { 30 | // Send Emails 31 | try { 32 | const res = await sendEmail.run( 33 | "New Friend Requests pending for " + email, 34 | "Ther eare so many frined reuests pending, please login to DevTinder.in and accept or reject the reqyests." 35 | ); 36 | console.log(res); 37 | } catch (err) { 38 | console.log(err); 39 | } 40 | } 41 | } catch (err) { 42 | console.error(err); 43 | } 44 | }); 45 | -------------------------------------------------------------------------------- /src/utils/razorpay.js: -------------------------------------------------------------------------------- 1 | const Razorpay = require("razorpay"); 2 | 3 | var instance = new Razorpay({ 4 | key_id: process.env.RAZORPAY_KEY_ID, 5 | key_secret: process.env.RAZORPAY_KEY_SECRET, 6 | }); 7 | 8 | module.exports = instance; 9 | -------------------------------------------------------------------------------- /src/utils/sendEmail.js: -------------------------------------------------------------------------------- 1 | const { SendEmailCommand } = require("@aws-sdk/client-ses"); 2 | const { sesClient } = require("./sesClient.js"); 3 | 4 | const createSendEmailCommand = (toAddress, fromAddress, subject, body) => { 5 | return new SendEmailCommand({ 6 | Destination: { 7 | CcAddresses: [], 8 | ToAddresses: [toAddress], 9 | }, 10 | Message: { 11 | Body: { 12 | Html: { 13 | Charset: "UTF-8", 14 | Data: `