├── CHANGELOG.md ├── .gitignore ├── assets └── pay_mobile_server_advert.png ├── models ├── otp_model.js ├── admin_auth_pin_model.js ├── message_model.js ├── notifications_model.js ├── chat_model.js ├── user_model.js └── transaction_model.js ├── middlewares ├── auth_middleware.js └── admin_middleware.js ├── package.json ├── routes ├── balance_routes.js ├── message_route.js ├── chat_route.js ├── notification_routes.js ├── user_routes.js ├── transactions_route.js └── auth_routes.js ├── index.js ├── utils └── transactions.js └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | pay-mobile-firebase-adminsdk.json 4 | -------------------------------------------------------------------------------- /assets/pay_mobile_server_advert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adedayoniyi/Pay-Mobile-Server/HEAD/assets/pay_mobile_server_advert.png -------------------------------------------------------------------------------- /models/otp_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const otpSchema = mongoose.Schema({ 4 | email: { 5 | type: String, 6 | }, 7 | otp: { 8 | type: String, 9 | }, 10 | expiry: { 11 | type: Number, 12 | }, 13 | }); 14 | const OTPSchema = mongoose.model("OTPSchema", otpSchema); 15 | module.exports = OTPSchema; 16 | -------------------------------------------------------------------------------- /models/admin_auth_pin_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const adminAuthPinSchema = mongoose.Schema({ 4 | admin: { 5 | type: String, 6 | required: true, 7 | unique: true, 8 | }, 9 | pin: { 10 | type: String, 11 | required: true, 12 | unique: true, 13 | }, 14 | }); 15 | const AdminAuthPin = mongoose.model("AdminAuthPin", adminAuthPinSchema); 16 | module.exports = AdminAuthPin; 17 | -------------------------------------------------------------------------------- /models/message_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const messageSchema = mongoose.Schema( 4 | { 5 | sender: { type: String, ref: "User" }, 6 | content: { type: String, trim: true }, 7 | receiver: { type: String, ref: "User" }, 8 | chat: { type: mongoose.Schema.Types.ObjectId, ref: "Chat" }, 9 | readBy: [{ type: mongoose.Schema.Types.ObjectId, ref: "User" }], 10 | }, 11 | { timestamps: true } 12 | ); 13 | 14 | module.exports = mongoose.model("Message", messageSchema); 15 | -------------------------------------------------------------------------------- /models/notifications_model.js: -------------------------------------------------------------------------------- 1 | // const mongoose = require("mongoose"); 2 | 3 | // const notificationsSchema = mongoose.Schema({ 4 | // username: { 5 | // required: true, 6 | // type: String, 7 | // }, 8 | // amount: { 9 | // type: Number, 10 | // required: true, 11 | // }, 12 | // trnxType: { 13 | // type: String, 14 | // }, 15 | // sendersName: { 16 | // type: String, 17 | // }, 18 | // }); 19 | // const Notifications = mongoose.model("Notifications", notificationsSchema); 20 | // module.exports = Notifications; 21 | -------------------------------------------------------------------------------- /middlewares/auth_middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | const auth = async (req, res, next) => { 4 | const token = req.header("x-auth-token"); 5 | if (token) { 6 | try { 7 | const verified = jwt.verify(token, process.env.TOKEN_STRING); 8 | req.user = verified.id; 9 | req.token = token; 10 | next(); 11 | } catch (errs) { 12 | res.status(401).json({ message: "Token not veified,access denied" }); 13 | } 14 | } else { 15 | res.status(401).json({ message: "No auth token, access denied" }); 16 | } 17 | }; 18 | 19 | module.exports = auth; 20 | -------------------------------------------------------------------------------- /models/chat_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const chatSchema = mongoose.Schema( 4 | { 5 | chatName: { 6 | type: String, 7 | trim: true, 8 | }, 9 | sender: { 10 | type: String, 11 | trim: true, 12 | ref: "User", 13 | }, 14 | receiver: { 15 | type: String, 16 | trim: true, 17 | ref: "User", 18 | }, 19 | latestMessage: { 20 | type: String, 21 | ref: "Message", 22 | }, 23 | }, 24 | { timestamps: true } 25 | ); 26 | 27 | const Chat = mongoose.model("Chat", chatSchema); 28 | module.exports = Chat; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pay-mobile-server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "nodemon ./index.js" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "bcryptjs": "^2.4.3", 13 | "cors": "^2.8.5", 14 | "dotenv": "^16.0.3", 15 | "express": "^4.18.2", 16 | "firebase-admin": "^11.9.0", 17 | "jsonwebtoken": "^9.0.0", 18 | "mongoose": "^7.0.2", 19 | "nodemailer": "^6.9.3", 20 | "otp-generator": "^4.0.1", 21 | "socket.io": "^4.6.1", 22 | "uuid": "^9.0.0" 23 | }, 24 | "devDependencies": { 25 | "nodemon": "^2.0.21" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /middlewares/admin_middleware.js: -------------------------------------------------------------------------------- 1 | const User = require("../models/user_model"); 2 | const jwt = require("jsonwebtoken"); 3 | 4 | const admin = async (req, res, next) => { 5 | const token = req.header("x-auth-token"); 6 | if (token) { 7 | try { 8 | const verified = jwt.verify(token, process.env.TOKEN_STRING); 9 | const user = await User.findById(verified.id); 10 | if (user && (user.type === "admin" || user.type === "agent")) { 11 | req.user = verified.id; 12 | req.token = token; 13 | next(); 14 | } else { 15 | res.status(401).json({ message: "Access denied" }); 16 | } 17 | } catch (errs) { 18 | res.status(401).json({ message: "Token not verified, access denied" }); 19 | } 20 | } else { 21 | res.status(401).json({ message: "No auth token, access denied" }); 22 | } 23 | }; 24 | 25 | module.exports = admin; 26 | -------------------------------------------------------------------------------- /routes/balance_routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const auth = require("../middlewares/auth_middleware"); 3 | const User = require("../models/user_model"); 4 | const admin = require("../middlewares/admin_middleware"); 5 | 6 | const balanceRouter = express.Router(); 7 | 8 | balanceRouter.get("/api/balance/:username", auth, async (req, res) => { 9 | try { 10 | const { username } = req.params; 11 | const user = await User.findOne({ username }); 12 | res.status(200).json({ message: Number(user.balance) }); 13 | } catch (err) { 14 | res.status(500).json({ message: err.message }); 15 | } 16 | }); 17 | 18 | balanceRouter.get("/admin/getTotalUserBalance", admin, async (req, res) => { 19 | try { 20 | const totalBalance = await User.aggregate([ 21 | { $group: { _id: null, total: { $sum: "$balance" } } }, 22 | ]); 23 | res.json(totalBalance[0].total); 24 | } catch (err) { 25 | res.status(500).json({ message: err.message }); 26 | } 27 | }); 28 | 29 | module.exports = balanceRouter; 30 | -------------------------------------------------------------------------------- /routes/message_route.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const Chat = require("../models/chat_model"); 3 | const Message = require("../models/message_model"); 4 | const admin = require("../middlewares/admin_middleware"); 5 | const messageRouter = express.Router(); 6 | 7 | messageRouter.get("/chat/:chatId/messages", async (req, res) => { 8 | const { chatId } = req.params; 9 | try { 10 | const messages = await Message.find({ chat: chatId }); 11 | res.status(200).json(messages); 12 | } catch (error) { 13 | res.status(500).json({ error: error.message }); 14 | } 15 | }); 16 | 17 | messageRouter.post("/message", async (req, res) => { 18 | const { sender, content, receiver, chat } = req.body; 19 | try { 20 | const message = new Message({ sender, content, receiver, chat }); 21 | await message.save(); 22 | await Chat.findByIdAndUpdate(chat, { latestMessage: content }); 23 | res.status(201).json(message); 24 | } catch (error) { 25 | res.status(500).json({ error: error.message }); 26 | } 27 | }); 28 | 29 | module.exports = messageRouter; 30 | -------------------------------------------------------------------------------- /models/user_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const userSchema = new mongoose.Schema( 4 | { 5 | fullname: { 6 | required: true, 7 | type: String, 8 | trim: true, 9 | }, 10 | username: { 11 | type: String, 12 | required: true, 13 | trim: true, 14 | unique: true, 15 | }, 16 | email: { 17 | required: true, 18 | type: String, 19 | trim: true, 20 | unique: true, 21 | }, 22 | password: { 23 | required: true, 24 | type: String, 25 | }, 26 | pin: { 27 | required: false, 28 | type: String, 29 | }, 30 | balance: { 31 | type: Number, 32 | required: true, 33 | default: 0, 34 | }, 35 | type: { 36 | type: String, 37 | default: "user", 38 | }, 39 | deviceToken: { 40 | type: String, 41 | default: "", 42 | }, 43 | isVerified: { 44 | type: Boolean, 45 | default: false, 46 | }, 47 | }, 48 | { timestamps: true } 49 | ); 50 | 51 | const User = mongoose.model("User", userSchema); 52 | module.exports = User; 53 | -------------------------------------------------------------------------------- /models/transaction_model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | 3 | const transactionSchema = new mongoose.Schema( 4 | { 5 | trnxType: { 6 | type: String, 7 | required: true, 8 | enum: ["Credit", "Debit", "Wallet Funding"], 9 | }, 10 | purpose: { 11 | type: String, 12 | enum: ["Transfer", "Deposit"], 13 | required: true, 14 | }, 15 | amount: { 16 | type: Number, 17 | required: true, 18 | default: 0.0, 19 | }, 20 | username: { 21 | type: String, 22 | ref: "User", 23 | }, 24 | reference: { 25 | type: String, 26 | required: true, 27 | }, 28 | balanceBefore: { 29 | type: Number, 30 | required: true, 31 | }, 32 | balanceAfter: { 33 | type: Number, 34 | required: true, 35 | }, 36 | fullNameTransactionEntity: { 37 | type: String, 38 | ref: "User", 39 | }, 40 | // sendersName: { 41 | // type: String, 42 | // ref: "User", 43 | // }, 44 | description: { 45 | type: String, 46 | required: true, 47 | }, 48 | }, 49 | { timestamps: true } 50 | ); 51 | 52 | const Transactions = mongoose.model("Transactions", transactionSchema); 53 | module.exports = Transactions; 54 | -------------------------------------------------------------------------------- /routes/chat_route.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const Chat = require("../models/chat_model"); 3 | const User = require("../models/user_model"); 4 | const admin = require("../middlewares/admin_middleware"); 5 | const Message = require("../models/message_model"); 6 | const chatRouter = express.Router(); 7 | 8 | chatRouter.post("/api/chat", async (req, res) => { 9 | const { sender, chatName } = req.body; 10 | const initialMessageContent = 11 | "Welcome to Pay Mobile customer support center, send us a message and we will reply as soon as possible"; 12 | try { 13 | const agents = await User.find({ type: "agent" }); 14 | if (agents.length === 0) { 15 | return res.status(400).json({ message: "No agents found" }); 16 | } 17 | const randomAgent = agents[Math.floor(Math.random() * agents.length)]; 18 | const chat = await Chat.findOne({ 19 | sender: sender, 20 | }); 21 | if (!chat) { 22 | const newChat = new Chat({ 23 | sender, 24 | receiver: randomAgent.username, 25 | chatName, 26 | latestMessage: initialMessageContent, 27 | }); 28 | await newChat.save(); 29 | const initialMessage = new Message({ 30 | sender: randomAgent.username, 31 | content: initialMessageContent, 32 | receiver: sender, 33 | chat: newChat._id, 34 | }); 35 | await initialMessage.save(); 36 | res.status(201).json(newChat); 37 | } else { 38 | console.log("Chat Exists!!"); 39 | res.status(200).json(chat); 40 | } 41 | } catch (error) { 42 | res.status(500).json({ message: error.message }); 43 | } 44 | }); 45 | 46 | chatRouter.get("/admin/getAllChats", admin, async (req, res) => { 47 | try { 48 | const allChats = await Chat.find({}); 49 | res.status(200).json(allChats); 50 | } catch (error) { 51 | res.status(500).json({ error: error.message }); 52 | } 53 | }); 54 | chatRouter.get("/admin/getAgentChat/:username", admin, async (req, res) => { 55 | try { 56 | const { username } = req.params; 57 | const agentChat = await Chat.find({ receiver: username }); 58 | if (agentChat.length == 0) { 59 | return res.status(400).json({ message: "No chats found" }); 60 | } 61 | res.status(200).json(agentChat); 62 | } catch (error) { 63 | res.status(500).json({ error: error.message }); 64 | } 65 | }); 66 | 67 | module.exports = chatRouter; 68 | -------------------------------------------------------------------------------- /routes/notification_routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const firebaseAdmin = require("firebase-admin"); 4 | const User = require("../models/user_model"); 5 | const admin = require("../middlewares/admin_middleware"); 6 | 7 | router.post("/admin/sendPushNotifications", admin, async (req, res) => { 8 | try { 9 | const { title, body } = req.body; 10 | 11 | const usersTokens = await User.find().exec(); 12 | let registrationTokens = usersTokens.map((user) => user.deviceToken); 13 | registrationTokens = registrationTokens.filter( 14 | (token) => token !== null && token !== undefined && token !== "" 15 | ); 16 | 17 | console.log(registrationTokens); 18 | 19 | const messages = { 20 | tokens: registrationTokens, 21 | notification: { 22 | title: title, 23 | body: body, 24 | }, 25 | }; 26 | 27 | firebaseAdmin 28 | .messaging() 29 | .sendEachForMulticast(messages) 30 | .then((response) => { 31 | console.log("Successfully sent messages:", response); 32 | res.status(200).json({ message: "Notifications sent successfully" }); 33 | }) 34 | .catch((error) => { 35 | console.log("Error sending messages:", error); 36 | res.status(500).send("Error sending notifications"); 37 | }); 38 | } catch (e) { 39 | res.status(500).json({ message: e.message }); 40 | } 41 | }); 42 | 43 | 44 | // Route to send push notification to a specific device 45 | router.post("/admin/sendPushNotificationToDevice", admin, async (req, res) => { 46 | try { 47 | const { deviceToken, title, body } = req.body; 48 | 49 | if (!deviceToken) { 50 | return res.status(400).json({ message: "Device token is required" }); 51 | } 52 | 53 | const message = { 54 | token: deviceToken, 55 | notification: { 56 | title: title, 57 | body: body, 58 | }, 59 | }; 60 | 61 | // Send push notification 62 | firebaseAdmin 63 | .messaging() 64 | .send(message) 65 | .then((response) => { 66 | console.log("Successfully sent message:", response); 67 | res.status(200).json({ message: "Notification sent successfully" }); 68 | }) 69 | .catch((error) => { 70 | console.error("Error sending message:", error); 71 | res.status(500).json({ message: "Error sending notification" }); 72 | }); 73 | } catch (e) { 74 | res.status(500).json({ message: e.message }); 75 | } 76 | }); 77 | 78 | 79 | 80 | module.exports = router; 81 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const dotenv = require("dotenv"); 3 | const mongoose = require("mongoose"); 4 | const admin = require("firebase-admin"); 5 | 6 | dotenv.config(); 7 | const authRouter = require("./routes/auth_routes"); 8 | const balanceRouter = require("./routes/balance_routes"); 9 | const transactionRouter = require("./routes/transactions_route"); 10 | const notifications = require("./routes/notification_routes"); 11 | const userRouter = require("./routes/user_routes"); 12 | const cors = require("cors"); 13 | const Message = require("./models/message_model"); 14 | const Chat = require("./models/chat_model"); 15 | const chatRouter = require("./routes/chat_route"); 16 | const messageRouter = require("./routes/message_route"); 17 | 18 | // Decode the base64-encoded service account 19 | const serviceAccountJSON = Buffer.from(process.env.ENCODED_SERVICE_ACCOUNT, "base64").toString("utf8"); 20 | const serviceAccount = JSON.parse(serviceAccountJSON); 21 | 22 | admin.initializeApp({ 23 | credential: admin.credential.cert(serviceAccount), 24 | databaseURL: process.env.databaseURL, 25 | }); 26 | 27 | const app = express(); 28 | app.use(express.json()); 29 | app.use(cors()); 30 | app.use(authRouter); 31 | app.use(transactionRouter); 32 | app.use(balanceRouter); 33 | app.use(notifications); 34 | app.use(userRouter); 35 | app.use(chatRouter); 36 | app.use(messageRouter); 37 | 38 | mongoose 39 | .connect(process.env.DATABASE_URL, { 40 | useNewUrlParser: true, 41 | useUnifiedTopology: true, 42 | }) 43 | .then(() => { 44 | console.log("Successfully connected to MongoDB!"); 45 | }) 46 | .catch((e) => { 47 | console.log("Unable to connect to MongoDB"); 48 | }); 49 | 50 | const PORT = process.env.PORT || 4000; 51 | 52 | const server = app.listen(PORT, "0.0.0.0", () => { 53 | console.log(`Server listening on port ${PORT}`); 54 | }); 55 | 56 | const io = require("socket.io")(server, { 57 | pingTimeout: 120000, 58 | cors: { 59 | origin: process.env.PAY_MOBILE_DATABASE, 60 | }, 61 | }); 62 | 63 | io.on("connection", (socket) => { 64 | socket.on("join", ({ chatId }) => { 65 | socket.join(chatId); 66 | }); 67 | 68 | socket.on("sendMessage", async ({ chatId, sender, content, receiver }) => { 69 | try { 70 | const message = new Message({ sender, content, receiver, chat: chatId }); 71 | await message.save(); 72 | await Chat.findByIdAndUpdate(chatId, { latestMessage: content }); 73 | io.to(chatId).emit("message", message); 74 | } catch (error) { 75 | console.error(error); 76 | } 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /routes/user_routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const User = require("../models/user_model"); 3 | const bcryptjs = require("bcryptjs"); 4 | const AdminAuthPin = require("../models/admin_auth_pin_model"); 5 | const userRouter = express.Router(); 6 | const admin = require("../middlewares/admin_middleware"); 7 | 8 | userRouter.get("/admin/getTotalNumberOfAllUsers", admin, async (req, res) => { 9 | try { 10 | const totalNumberOfUsers = await User.countDocuments({}); 11 | res.status(200).json(totalNumberOfUsers); 12 | } catch (e) { 13 | res.status(500).json({ message: e.message }); 14 | } 15 | }); 16 | 17 | userRouter.get("/admin/getAllUsers", admin, async (req, res) => { 18 | try { 19 | const allUsers = await User.find({}); 20 | res.status(200).json(allUsers); 21 | } catch (e) { 22 | res.status(500).json({ message: e.message }); 23 | } 24 | }); 25 | 26 | // userRouter.delete("/admin/deleteUser/:username", admin, async (req, res) => { 27 | // try { 28 | // const { username } = req.params; 29 | // await User.findOneAndDelete({ username }); 30 | // res.status(200).json({ message: "User deleted successfully" }); 31 | // } catch (e) { 32 | // res.status(500).json({ message: e.message }); 33 | // } 34 | // }); 35 | 36 | userRouter.get("/admin/getAllAdmin", admin, async (req, res) => { 37 | try { 38 | const allAdmins = await User.find({ type: "admin" || "agent" }); 39 | res.status(200).json(allAdmins); 40 | } catch (e) { 41 | res.status(500).json({ message: e.message }); 42 | } 43 | }); 44 | 45 | userRouter.post("/admin/createAdmin", admin, async (req, res) => { 46 | try { 47 | const { fullname, username, email, password, type } = req.body; 48 | const userExists = await User.findOne({ username }); 49 | if (userExists) { 50 | return res.status(400).json({ 51 | message: "Admin or User already exists", 52 | }); 53 | } 54 | const hashedPassword = await bcryptjs.hash(password, 8); 55 | let user = new User({ 56 | fullname: fullname, 57 | username: username, 58 | email: email, 59 | password: hashedPassword, 60 | type: type, 61 | }); 62 | user = await user.save(); 63 | 64 | return res.status(201).json({ 65 | message: "Admin created successfully", 66 | }); 67 | } catch (e) { 68 | return res.status(500).json({ 69 | message: `Unable to create admin. Please try again.\n Error:${e}`, 70 | }); 71 | } 72 | }); 73 | 74 | userRouter.delete("/admin/deleteUser", admin, async (req, res) => { 75 | try { 76 | const { authorizationPin, username } = req.body; 77 | const user = await User.findOne({ username }); 78 | if (!user) { 79 | return res.status(400).json({ message: "User or Admin not found" }); 80 | } 81 | const adminAuthPin = await AdminAuthPin.findOne({ pin: authorizationPin }); 82 | const isMatch = bcryptjs.compare(authorizationPin, adminAuthPin.pin); 83 | if (!isMatch) { 84 | return res 85 | .status(400) 86 | .json({ message: "Incorrect Admin Authorization Pin" }); 87 | } 88 | await User.findOneAndDelete({ username }); 89 | res.status(200).json({ message: "User deleted successfully" }); 90 | } catch (e) { 91 | res.status(500).json({ message: e.message }); 92 | } 93 | }); 94 | 95 | module.exports = userRouter; 96 | -------------------------------------------------------------------------------- /utils/transactions.js: -------------------------------------------------------------------------------- 1 | const Transactions = require("../models/transaction_model"); 2 | const User = require("../models/user_model"); 3 | const admin = require("firebase-admin"); 4 | 5 | const creditAccount = async ({ 6 | amount, 7 | username, 8 | purpose, 9 | reference, 10 | description, 11 | session, 12 | fullNameTransactionEntity, 13 | }) => { 14 | const user = await User.findOne({ username }); 15 | 16 | if (!user) { 17 | return { 18 | statusCode: 404, 19 | message: `The username ${username} isnt recognised`, 20 | }; 21 | } 22 | 23 | const updatedWallet = await User.findOneAndUpdate( 24 | { username }, 25 | { $inc: { balance: amount } }, 26 | { session } 27 | ); 28 | const sendersFullName = await User.findOne({ 29 | username: fullNameTransactionEntity, 30 | }); 31 | const transaction = await Transactions.create( 32 | [ 33 | { 34 | trnxType: "Credit", 35 | purpose, 36 | amount, 37 | username: username, 38 | reference, 39 | balanceBefore: Number(user.balance), 40 | balanceAfter: Number(user.balance) + Number(amount), 41 | description, 42 | fullNameTransactionEntity: sendersFullName.fullname, 43 | }, 44 | ], 45 | { session } 46 | ); 47 | console.log(`Credit Successful`); 48 | const message = { 49 | notification: { 50 | title: "Credit Successful", 51 | body: `You just received ₦ ${amount} from ${fullNameTransactionEntity}`, 52 | }, 53 | token: user.deviceToken, 54 | }; 55 | 56 | admin 57 | .messaging() 58 | .send(message) 59 | .then((response) => { 60 | console.log("Successfully sent message:", response); 61 | }) 62 | .catch((error) => { 63 | console.log("Error sending message:", error); 64 | }); 65 | return { 66 | statusCode: 201, 67 | message: "Credit Successful", 68 | data: { updatedWallet, transaction }, 69 | }; 70 | }; 71 | 72 | const debitAccount = async ({ 73 | amount, 74 | username, 75 | purpose, 76 | reference, 77 | description, 78 | session, 79 | fullNameTransactionEntity, 80 | }) => { 81 | const user = await User.findOne({ username }); 82 | 83 | if (!user) { 84 | return { 85 | statusCode: 404, 86 | message: `The username ${username} isnt recognised`, 87 | }; 88 | } 89 | 90 | if (Number(user.balance) < amount) { 91 | return { 92 | statusCode: 400, 93 | message: `You have an insufficient balance`, 94 | }; 95 | } 96 | 97 | const updatedWallet = await User.findOneAndUpdate( 98 | { username }, 99 | { $inc: { balance: -amount } }, 100 | { session } 101 | ); 102 | const recipientFullName = await User.findOne({ 103 | username: fullNameTransactionEntity, 104 | }); 105 | const transaction = await Transactions.create( 106 | [ 107 | { 108 | trnxType: "Debit", 109 | purpose, 110 | amount, 111 | username: username, 112 | reference, 113 | balanceBefore: Number(user.balance), 114 | balanceAfter: Number(user.balance) - Number(amount), 115 | description, 116 | fullNameTransactionEntity: recipientFullName.fullname, 117 | }, 118 | ], 119 | { session } 120 | ); 121 | console.log(`Debit Successful`); 122 | const message = { 123 | notification: { 124 | title: "Debit", 125 | body: `You just sent ₦ ${amount} to ${fullNameTransactionEntity}`, 126 | }, 127 | token: user.deviceToken, 128 | }; 129 | 130 | admin 131 | .messaging() 132 | .send(message) 133 | .then((response) => { 134 | console.log("Successfully sent message:", response); 135 | }) 136 | .catch((error) => { 137 | console.log("Error sending message:", error); 138 | }); 139 | return { 140 | statusCode: 201, 141 | message: "Debit Successful", 142 | data: { updatedWallet, transaction }, 143 | }; 144 | }; 145 | 146 | module.exports = { 147 | creditAccount, 148 | debitAccount, 149 | }; 150 | -------------------------------------------------------------------------------- /routes/transactions_route.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const transactionRouter = express.Router(); 3 | const mongoose = require("mongoose"); 4 | const { v4 } = require("uuid"); 5 | const Transactions = require("../models/transaction_model"); 6 | const { creditAccount, debitAccount } = require("../utils/transactions"); 7 | const User = require("../models/user_model"); 8 | const auth = require("../middlewares/auth_middleware"); 9 | const admin = require("../middlewares/admin_middleware"); 10 | 11 | transactionRouter.post("/api/transactions/transfer", auth, async (req, res) => { 12 | const session = await mongoose.startSession(); 13 | session.startTransaction(); 14 | try { 15 | // Get the request body data 16 | const { recipientsUsername, sendersUsername, amount, description } = 17 | req.body; 18 | // Generate a reference number 19 | const reference = v4(); 20 | if (!recipientsUsername || !sendersUsername || !amount || !description) { 21 | await session.endSession(); 22 | return res.status(404).json({ 23 | message: `Please provide the following details: ${recipientsUsername},${sendersUsername}, ${amount}, ${description}`, 24 | }); 25 | } 26 | const transferResult = await Promise.all([ 27 | debitAccount({ 28 | amount, 29 | username: sendersUsername, 30 | purpose: "Transfer", 31 | reference, 32 | description, 33 | session, 34 | fullNameTransactionEntity: recipientsUsername, 35 | }), 36 | creditAccount({ 37 | amount, 38 | username: recipientsUsername, 39 | purpose: "Transfer", 40 | reference, 41 | description, 42 | session, 43 | fullNameTransactionEntity: sendersUsername, 44 | }), 45 | ]); 46 | 47 | // Filter out any failed operations 48 | const failedTxns = transferResult.filter( 49 | (result) => result.statusCode !== 201 50 | ); 51 | if (failedTxns.length) { 52 | const errors = failedTxns.map((a) => a.message); 53 | await session.abortTransaction(); 54 | await session.endSession(); 55 | return res.status(409).json({ 56 | message: errors.join(" "), 57 | }); 58 | } 59 | 60 | // If everything is successful, commit the transaction and end the session 61 | await session.commitTransaction(); 62 | await session.endSession(); 63 | 64 | return res.status(201).json({ 65 | message: "Transfer successful", 66 | transferResult, 67 | }); 68 | } catch (err) { 69 | // If there is any error, abort the transaction, end the session and send an error response 70 | await session.abortTransaction(); 71 | await session.endSession(); 72 | 73 | return res.status(500).json({ 74 | message: `Unable to perform transfer. Please try again. \n Error:${err}`, 75 | }); 76 | } 77 | }); 78 | 79 | transactionRouter.get( 80 | "/api/getTransactions/:username", 81 | auth, 82 | async (req, res) => { 83 | try { 84 | const { username } = req.params; 85 | const userTransactions = await Transactions.find({ username: username }); 86 | if (userTransactions.length === 0) { 87 | return res.status(404).json({ message: "No transactions found" }); 88 | } 89 | let showTransactionsFromRecentToLast = userTransactions.reverse(); 90 | res.status(200).json(showTransactionsFromRecentToLast); 91 | } catch (err) { 92 | res.status(500).json({ message: err.message }); 93 | } 94 | } 95 | ); 96 | 97 | transactionRouter.post("/api/fundWallet/:username", auth, async (req, res) => { 98 | try { 99 | const { username } = req.params; 100 | /*please note if this will be used with the flutter app, 101 | only integers are allowed, no decimals allowed or an error will be thrown*/ 102 | const { amount } = req.body; 103 | const reference = v4(); 104 | const user = await User.findOne({ username }); 105 | if (!user) { 106 | return res.status(400).json({ mesage: "User not found!!" }); 107 | } 108 | if (amount > 5000) { 109 | return res 110 | .status(400) 111 | .json({ message: "The maximum amount that can be funded is 5000" }); 112 | } 113 | await User.findOneAndUpdate( 114 | { username }, 115 | { balance: user.balance + amount } 116 | ); 117 | await Transactions.create({ 118 | trnxType: "Wallet Funding", 119 | purpose: "Deposit", 120 | amount: amount, 121 | username: username, 122 | reference: reference, 123 | balanceBefore: Number(user.balance), 124 | balanceAfter: Number(user.balance) + Number(amount), 125 | description: "Wallet Funding", 126 | fullNameTransactionEntity: user.fullname, 127 | }); 128 | res.status(200).json({ message: "Account funded successfully" }); 129 | } catch (err) { 130 | res.status(500).json({ message: err.message }); 131 | } 132 | }); 133 | 134 | transactionRouter.get( 135 | "/admin/getTotalNumberOfTransactions", 136 | admin, 137 | async (req, res) => { 138 | try { 139 | const transactions = await Transactions.countDocuments({}); 140 | res.status(200).json(transactions); 141 | } catch (e) { 142 | res.status(500).json({ message: e.message }); 143 | } 144 | } 145 | ); 146 | 147 | transactionRouter.get( 148 | "/admin/getAllUserTransactions", 149 | admin, 150 | async (req, res) => { 151 | try { 152 | res.header("Access-Control-Allow-Origin", "*"); // allow any origin 153 | const transactions = await Transactions.find({}); 154 | let showTransactionsFromRecentToLast = transactions.reverse(); 155 | res.status(200).json(showTransactionsFromRecentToLast); 156 | } catch (e) { 157 | res.status(500).json({ message: e.message }); 158 | } 159 | } 160 | ); 161 | 162 | transactionRouter.get( 163 | "/admin/getNumberOfWalletFundings", 164 | admin, 165 | async (req, res) => { 166 | try { 167 | res.header("Access-Control-Allow-Origin", "*"); // allow any origin 168 | const transactions = await Transactions.find({ 169 | trnxType: "Wallet Funding", 170 | }); 171 | res.status(200).json(transactions.length); 172 | } catch (e) { 173 | res.status(500).json({ message: e.message }); 174 | } 175 | } 176 | ); 177 | 178 | transactionRouter.get( 179 | "/admin/getNumberOfCreditTransactions", 180 | admin, 181 | async (req, res) => { 182 | try { 183 | res.header("Access-Control-Allow-Origin", "*"); // allow any origin 184 | const transactions = await Transactions.find({ 185 | trnxType: "Credit", 186 | }); 187 | res.status(200).json(transactions.length); 188 | } catch (e) { 189 | res.status(500).json({ message: e.message }); 190 | } 191 | } 192 | ); 193 | 194 | transactionRouter.get( 195 | "/admin/getNumberOfDebitTransactions", 196 | admin, 197 | async (req, res) => { 198 | try { 199 | res.header("Access-Control-Allow-Origin", "*"); // allow any origin 200 | const transactions = await Transactions.find({ 201 | trnxType: "Debit", 202 | }); 203 | res.status(200).json(transactions.length); 204 | } catch (e) { 205 | res.status(500).json({ message: e.message }); 206 | } 207 | } 208 | ); 209 | 210 | module.exports = transactionRouter; 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pay Mobile Server 2 | 3 | Username Transfer Showcase 4 | 5 | ## This is the official Nodejs server code for the Pay Mobile P2P Money Transfer App and the Pay Mobile Web Admin 6 | 7 | ### QUICK START ⚡ 8 | 9 | #### Visit: Pay Mobile Full Stack to access the full stack code of the software (i.e the Web Admin Front End and the Mobile Front End) 10 | 11 | ### You have to create a `.env` for the required environment variables as the one here has been removed 12 | 13 | #### Here are the necessary data 14 | 15 | ```env 16 | DATABASE_URL = Database URL(e.g MongoDB) 17 | PORT = PORT(e.g 4000) 18 | TOKEN_STRING=Random Token String for signing user tokens 19 | databaseURL=Firebase Database URL 20 | OAUTH_REFRESH_TOKEN=Oauth Refresh Token. Get it from the https://developers.google.com/oauthplayground/ 21 | OAUTH_CLIENT_SECRET=Oauth Client Secret from Google Cloud 22 | OAUTH_CLIENT_ID=Oauth Client ID from Google Cloud 23 | GMAIL_PASSWORD=Your Email Password to be used for NodeMailer 24 | EMAIL_ADDRESS=Your Email address to be used for NodeMailer 25 | PAY_MOBILE_DATABASE=Your server URL 26 | ``` 27 | 28 | #### After cloning and updating the necessary `env` data, just run this and you server will be live: 29 | 30 | ```bash 31 | npm run dev 32 | ``` 33 | 34 | ### These are some of the major API endpoints 35 | 36 | #### 1. Methods: `POST` `/api/createUser` This is for user sign up
37 | 38 | Request 39 | 40 | ```json 41 | { 42 | "fullname": "full name here", 43 | "username": "username here", 44 | "email": "email here", 45 | "password": "password here" 46 | } 47 | ``` 48 | 49 | Response 50 | 51 | ```json 52 | 201 Created 53 | { 54 | "message": { 55 | "fullname": "Evan Musk", 56 | "username": "musk", 57 | "email": "evanmusktest@mail.com", 58 | "password": "$2a$08$AVbHVbw5wyr7i6.Sc2GYf.LbreBMPHluf8dI6xxU30IemEwdosII6", 59 | "balance": 0, 60 | "type": "user", 61 | "deviceToken": "", 62 | "isVerified": false, 63 | "_id": "64bee0766503c8619c6f4559", 64 | "createdAt": "2023-07-24T20:35:02.397Z", 65 | "updatedAt": "2023-07-24T20:35:02.397Z", 66 | "__v": 0 67 | } 68 | } 69 | ``` 70 | 71 |
72 | 73 | #### 2. Methods: `POST` `/api/login` This is for user login where a token is generated
74 | 75 | Request 76 | 77 | ```json 78 | { 79 | "username": "username here", 80 | "password": "password here" 81 | } 82 | ``` 83 | 84 | Response 85 | 86 | ```json 87 | 200 OK 88 | { 89 | "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjY0YmVlMDc2NjUwM2M4NjE5YzZmNDU1OSIsImlhdCI6MTY5MDIzMTAyN30.aq3dfWUhQzBlXQHBXizNymQJBzp-c82sRvnVMrI0dOA", 90 | "_id": "64bee0766503c8619c6f4559", 91 | "fullname": "Evan Musk", 92 | "username": "musk", 93 | "email": "evanmusktest@mail.com", 94 | "password": "$2a$08$AVbHVbw5wyr7i6.Sc2GYf.LbreBMPHluf8dI6xxU30IemEwdosII6", 95 | "balance": 0, 96 | "type": "user", 97 | "deviceToken": "", 98 | "isVerified": false, 99 | "createdAt": "2023-07-24T20:35:02.397Z", 100 | "updatedAt": "2023-07-24T20:35:02.397Z", 101 | "__v": 0 102 | } 103 | ``` 104 | 105 |
106 | 107 | #### 3. Methods: `POST` `/api/chat` This is used to create a new chat or return an existing chat
108 | 109 | Request 110 | 111 | ```json 112 | { 113 | "sender": "musk", 114 | "chatName": "Pay Mobile Support" 115 | } 116 | ``` 117 | 118 | Response 119 | 120 | ```json 121 | 201 Created 122 | { 123 | "chatName": "Pay Mobile Support", 124 | "sender": "musk", 125 | "receiver": "welch", 126 | "latestMessage": "Welcome to Pay Mobile customer support center, send us a message and we will reply as soon as possible", 127 | "_id": "64bee3911097c546ad9d71e2", 128 | "createdAt": "2023-07-24T20:48:17.411Z", 129 | "updatedAt": "2023-07-24T20:48:17.411Z", 130 | "__v": 0 131 | } 132 | ``` 133 | 134 |
135 | 136 |
137 | 138 | #### 4. Methods: `POST` `/api/transactions/transfer` This is for sending wallet balance from one user to another
139 | 140 | Request 141 | 142 | ```json 143 | { 144 | "recipientsUsername": "alice", 145 | "sendersUsername": "dayo", 146 | "amount": 35000, 147 | "description": "Welcome Bonus" 148 | } 149 | ``` 150 | 151 | Response 152 | 153 | ```json 154 | 201 Created 155 | { 156 | "message": "Transfer successful", 157 | "transferResult": [ 158 | { 159 | "statusCode": 201, 160 | "message": "Debit Successful", 161 | "data": { 162 | "updatedWallet": { 163 | "_id": "64bdb61473bd766e137fae20", 164 | "fullname": "Adedayo Oriolowo", 165 | "username": "dayo", 166 | "email": "adedayoniyio@gmail.com", 167 | "password": "$2a$08$fyjxrqmXieBNWPQMoWPC1ev3uu99m9S3fapqGN5BbCGi9Rw6ne3rG", 168 | "balance": 867000, 169 | "type": "admin", 170 | "deviceToken": "e4T3NrGQTien_ctRy9HuSk:APA91bGl446xH9fn-e26Kh4cH8mfhkdxLZo-n1DXIx3AkPkLw_Kkaad4DobsZJJL_rq85UNohuzu4CuJQiStIdfY010nV033RAt9OJu18nLeScJsAJSeZNgh6kjHg2AqoCFOSoHNg0DX", 171 | "isVerified": true, 172 | "createdAt": "2023-07-23T23:21:56.876Z", 173 | "updatedAt": "2023-07-24T13:55:42.956Z", 174 | "__v": 0, 175 | "pin": "$2a$08$5oKMEdmNqWQQ3N5EAvhJ0.xP8KcXen0wQ9v2PhG/8u311cEvtDP7a" 176 | }, 177 | "transaction": [ 178 | { 179 | "trnxType": "Debit", 180 | "purpose": "Transfer", 181 | "amount": 35000, 182 | "username": "dayo", 183 | "reference": "c36213f1-b361-4022-878d-fb4e51bf09dc", 184 | "balanceBefore": 867000, 185 | "balanceAfter": 832000, 186 | "fullNameTransactionEntity": "Alice James", 187 | "description": "Welcome Bonus", 188 | "_id": "64bee4ca1097c546ad9d7202", 189 | "createdAt": "2023-07-24T20:53:30.968Z", 190 | "updatedAt": "2023-07-24T20:53:30.968Z", 191 | "__v": 0 192 | } 193 | ] 194 | } 195 | }, 196 | { 197 | "statusCode": 201, 198 | "message": "Credit Successful", 199 | "data": { 200 | "updatedWallet": { 201 | "_id": "647369cd3836b967499f2010", 202 | "fullname": "Alice James", 203 | "username": "alice", 204 | "email": "alicetest@mail.com", 205 | "password": "$2a$08$c9LW1TM3iztI1FPNX2B24ODh9mJyeMsPEZ2EHbeWe/0OqswnXJvCa", 206 | "balance": 10400, 207 | "type": "user", 208 | "createdAt": "2023-05-28T14:48:45.502Z", 209 | "updatedAt": "2023-07-24T13:35:29.547Z", 210 | "__v": 0, 211 | "pin": "$2a$08$BSBApBOBUFLy3ZssBvyB8OW8f45SWMvILcf8hFlQHOcjbq9kIcmma", 212 | "deviceToken": "e4T3NrGQTien_ctRy9HuSk:APA91bGl446xH9fn-e26Kh4cH8mfhkdxLZo-n1DXIx3AkPkLw_Kkaad4DobsZJJL_rq85UNohuzu4CuJQiStIdfY010nV033RAt9OJu18nLeScJsAJSeZNgh6kjHg2AqoCFOSoHNg0DX", 213 | "isVerified": true 214 | }, 215 | "transaction": [ 216 | { 217 | "trnxType": "Credit", 218 | "purpose": "Transfer", 219 | "amount": 35000, 220 | "username": "alice", 221 | "reference": "c36213f1-b361-4022-878d-fb4e51bf09dc", 222 | "balanceBefore": 10400, 223 | "balanceAfter": 45400, 224 | "fullNameTransactionEntity": "Adedayo Oriolowo", 225 | "description": "Welcome Bonus", 226 | "_id": "64bee4ca1097c546ad9d7200", 227 | "createdAt": "2023-07-24T20:53:30.964Z", 228 | "updatedAt": "2023-07-24T20:53:30.964Z", 229 | "__v": 0 230 | } 231 | ] 232 | } 233 | } 234 | ] 235 | } 236 | ``` 237 | 238 |
239 | 240 |
241 | 242 | #### 5. Methods: `POST` `/admin/sendPushNotifications` This is used for sending push notifications to registered users. Only Admins can use this route
243 | 244 | Request 245 | 246 | ```json 247 | { 248 | "title": "Transfers", 249 | "body": "Transfers will be briefly paused due to server maintenance" 250 | } 251 | ``` 252 | 253 | Response 254 | 255 | ```json 256 | 200 OK 257 | { 258 | "message": "Notifications sent successfully" 259 | } 260 | ``` 261 | 262 | ## Check ../routes for more routes 263 | 264 | ### DEPENDENCIES 📦 265 | 266 | 1. bcryptjs 267 | 2. cors 268 | 269 | 3. dotenv 270 | 271 | 4. express 272 | 273 | 5. firebase-admin 274 | 275 | 6. jsonwebtoken 276 | 277 | 7. mongoose 278 | 279 | 8. nodemailer 280 | 281 | 9. otp-generator 282 | 283 | 10. socket.io 284 | 285 | 11. bcryptjs 286 | 287 | 12. uuid 288 | 289 | ## Contributing 290 | 291 | Pull requests are welcome. If you encounter any problem with the app or server, you can open an issue. 292 | 293 | ##### If you liked this project, don't forget to leave a star 🌟. 294 | 295 | ##### Note: As of now, no tests are available 296 | 297 | ## License 298 | 299 | This project is licensed under the MIT License - see the LICENSE file for details. 300 | -------------------------------------------------------------------------------- /routes/auth_routes.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const authRouter = express.Router(); 3 | const bcryptjs = require("bcryptjs"); 4 | const jwt = require("jsonwebtoken"); 5 | const otpGenerator = require("otp-generator"); 6 | const nodemailer = require("nodemailer"); 7 | const OTPSchema = require("../models/otp_model"); 8 | const User = require("../models/user_model"); 9 | const auth = require("../middlewares/auth_middleware"); 10 | const AdminAuthPin = require("../models/admin_auth_pin_model"); 11 | const admin = require("../middlewares/admin_middleware"); 12 | 13 | //POST 14 | authRouter.post("/api/createUser", async (req, res) => { 15 | try { 16 | const { fullname, username, email, password } = req.body; 17 | const userExists = await User.findOne({ username }); 18 | if (userExists) { 19 | return res.status(400).json({ 20 | status: false, 21 | message: "User already exists", 22 | }); 23 | } 24 | const hashedPassword = await bcryptjs.hash(password, 8); 25 | let user = new User({ 26 | fullname, 27 | username, 28 | email, 29 | password: hashedPassword, 30 | }); 31 | user = await user.save(); 32 | return res.status(201).json({ 33 | message: "User created successfully", 34 | message: user, 35 | }); 36 | } catch (e) { 37 | return res.status(500).json({ 38 | message: `Unable to create user. Please try again.\n Error:${e}`, 39 | }); 40 | } 41 | }); 42 | const transporter = nodemailer.createTransport({ 43 | service: 'gmail', // Adjust as needed 44 | auth: { 45 | user: process.env.EMAIL_ADDRESS, 46 | pass: process.env.EMAIL_PASSWORD, 47 | }, 48 | }); 49 | 50 | authRouter.post('/api/sendOtp/:sendPurpose', async (req, res) => { 51 | try { 52 | const expiryDate = Date.now() + 120000; // OTP expires in 2 minutes 53 | const { sendPurpose } = req.params; 54 | const { email } = req.body; 55 | const user = await User.findOne({ email }); 56 | 57 | if (!user) { 58 | return res.status(400).json({ message: 'User With This Email Not Found' }); 59 | } 60 | 61 | const code = otpGenerator.generate(6, { 62 | digits: true, 63 | lowerCaseAlphabets: false, 64 | upperCaseAlphabets: false, 65 | specialChars: false, 66 | }); 67 | 68 | let purpose = ''; 69 | switch (sendPurpose) { 70 | case 'sign-up-verification': 71 | purpose = 'OTP Code To Confirm SignUp'; 72 | break; 73 | case 'forgort-password': 74 | purpose = 'OTP Code To Verify Account (Forgot Password)'; 75 | break; 76 | case 'forgot-pin': 77 | purpose = 'OTP Code To Verify Account (Forgot Pin)'; 78 | break; 79 | default: 80 | purpose = 'No Purpose'; 81 | } 82 | 83 | const mailOptions = { 84 | from: process.env.EMAIL_ADDRESS, 85 | to: email, 86 | subject: purpose, 87 | html: ` 88 | Pay Mobile Logo 89 |

Hi ${user.fullname},

90 |

Please use the below ${purpose}

91 |

${code}

92 |

If you did not initiate this login attempt, we strongly recommend contacting us through the in-app support.

93 | `, 94 | }; 95 | 96 | await OTPSchema.deleteOne({ email, otp: code }); 97 | console.log('Previous OTP deleted successfully'); 98 | 99 | await transporter.sendMail(mailOptions); 100 | await OTPSchema.create({ 101 | email, 102 | otp: code, 103 | expiry: expiryDate, 104 | }); 105 | 106 | console.log(`The OTP code is ${code}`) 107 | 108 | setTimeout(async () => { 109 | await OTPSchema.deleteOne({ email, otp: code }); 110 | console.log('OTP deleted after expiration'); 111 | }, expiryDate - Date.now()); 112 | 113 | return res.status(200).json({ 114 | message: 'OTP has been sent to the provided email.', 115 | }); 116 | } catch (e) { 117 | return res.status(500).json({ 118 | message: `Unknown error occurred: ${e}`, 119 | }); 120 | } 121 | }); 122 | 123 | 124 | authRouter.post("/api/verifyOtp", async (req, res) => { 125 | try { 126 | const { email, otpCode } = req.body; 127 | const otpData = await OTPSchema.findOne({ email }); 128 | if (otpData) { 129 | const rOtp = otpData.otp; 130 | const otpExpiry = otpData.expiry; 131 | console.log(`Otp expiry is: ${otpExpiry}`); 132 | console.log(`OTP code is: ${rOtp}`); 133 | // Check if the current time is before the expiry time 134 | if (Date.now() < otpExpiry) { 135 | if (otpCode == rOtp) { 136 | await User.findOneAndUpdate({ email }, { isVerified: true }); 137 | return res.status(200).json({ 138 | status: "success", 139 | message: "OTP successfully confirmed!.", 140 | }); 141 | } else { 142 | return res 143 | .status(400) 144 | .json({ message: "Wrong OTP code. Please try again" }); 145 | } 146 | } else { 147 | return res.status(400).json({ 148 | status: "failed", 149 | message: "Sorry this otp has expired!", 150 | }); 151 | } 152 | } else { 153 | return res.status(404).json({ 154 | status: "failed", 155 | message: "No otp found for this email!", 156 | }); 157 | } 158 | } catch (e) { 159 | res 160 | .status(500) 161 | .json({ message: "Cannot verify otp at this moment. Try Again" }); 162 | } 163 | }); 164 | 165 | authRouter.post("/api/login", async (req, res) => { 166 | try { 167 | const { username, password, deviceToken } = req.body; 168 | console.log(`Device token: ${deviceToken}`) 169 | const user = await User.findOne({ username }); 170 | if (!user) { 171 | return res.status(409).json({ 172 | status: false, 173 | message: "This user does not exist", 174 | }); 175 | } 176 | const isMatch = await bcryptjs.compare(password, user.password); 177 | if (!isMatch) { 178 | return res.status(409).json({ 179 | status: false, 180 | message: "Incorrect password", 181 | }); 182 | } 183 | const token = await jwt.sign({ id: user._id }, process.env.TOKEN_STRING); 184 | await User.findOneAndUpdate( 185 | { username }, 186 | { deviceToken: deviceToken }, 187 | { new: true } 188 | ); 189 | res.status(201).json({ 190 | token, 191 | ...user._doc, 192 | }); 193 | } catch (e) { 194 | res.status(500).json({ message: e.message }); 195 | } 196 | }); 197 | 198 | authRouter.post("/checkToken", auth, async (req, res) => { 199 | const token = req.header("x-auth-token"); 200 | if (token) { 201 | try { 202 | const { id } = jwt.verify(token, process.env.TOKEN_STRING); 203 | const user = await User.findById(id); 204 | if (user) { 205 | return res.json(true); 206 | } 207 | } catch (e) { 208 | res.status(500).json({ message: e.message }); 209 | } 210 | } 211 | return res.json(false); 212 | }); 213 | 214 | authRouter.get("/", auth, async (req, res) => { 215 | const user = await User.findById(req.user); 216 | res.json({ ...user._doc, token: req.token }); 217 | }); 218 | 219 | // This endpoint should only be used once the forgort password returns a 200 OK 220 | authRouter.post("/api/changePassword/:email", async (req, res) => { 221 | try { 222 | const { email } = req.params; 223 | const { password, confirmPassword } = req.body; 224 | 225 | if (password !== confirmPassword) { 226 | return res.status(400).json({ message: "Passwords do not match" }); 227 | } 228 | 229 | const hashedPassword = await bcryptjs.hash(password, 8); 230 | const updatedUser = await User.findOneAndUpdate( 231 | { email }, 232 | { password: hashedPassword }, 233 | { new: true } 234 | ); 235 | 236 | if (!updatedUser) { 237 | return res.status(404).json({ message: "User not found" }); 238 | } 239 | 240 | res.status(200).json({ message: "Password changed successfully" }); 241 | } catch (e) { 242 | console.error(`Password reset error: ${e.message}`); 243 | res.status(500).json({ message: e.message }); 244 | } 245 | }); 246 | 247 | 248 | authRouter.get("/api/getUsername/:username", auth, async (req, res) => { 249 | try { 250 | const { username } = req.params; 251 | const user = await User.findOne({ username }); 252 | if (!user) { 253 | return res 254 | .status(200) 255 | .json({ message: `${username} username is available` }); 256 | } 257 | res.status(400).json({ message: "This username has been taken" }); 258 | } catch (err) { 259 | res.status(500).json({ message: err.message }); 260 | } 261 | }); 262 | 263 | authRouter.get( 264 | "/api/getUsernameFortransfer/:username", 265 | auth, 266 | async (req, res) => { 267 | try { 268 | const { username } = req.params; 269 | const user = await User.findOne({ username }); 270 | if (user) { 271 | return res.status(200).json({ message: user.fullname }); 272 | } 273 | res 274 | .status(400) 275 | .json({ message: "Invalid username, please check and try again" }); 276 | } catch (err) { 277 | res.status(500).json({ message: err.message }); 278 | } 279 | } 280 | ); 281 | 282 | authRouter.post("/api/createLoginPin/:username", auth, async (req, res) => { 283 | try { 284 | const { username } = req.params; 285 | const { pin } = req.body; 286 | 287 | const user = await User.findOne({ username }); 288 | if (!user) { 289 | return res.status(400).json({ message: `User ${username} is not found` }); 290 | } 291 | const pinEncrypt = await bcryptjs.hash(pin, 8); 292 | await User.findOneAndUpdate({ username }, { pin: pinEncrypt }); 293 | res.status(200).json({ message: "Pin created successfully" }); 294 | } catch (err) { 295 | res.status(500).json({ message: err.message }); 296 | } 297 | }); 298 | 299 | authRouter.post("/api/loginUsingPin/:username", auth, async (req, res) => { 300 | try { 301 | const { username } = req.params; 302 | const { pin } = req.body; 303 | const user = await User.findOne({ username }); 304 | const isPinCorrect = await bcryptjs.compare(pin, user.pin); 305 | if (!isPinCorrect) { 306 | return res.status(400).json({ message: "Incorrect Pin. Try again!" }); 307 | } 308 | res.status(200).json({ message: "Login Successful" }); 309 | } catch (err) { 310 | res.status(500).json({ message: err.message }); 311 | } 312 | }); 313 | 314 | authRouter.post("/api/changePin/:username", auth, async (req, res) => { 315 | try { 316 | const { username } = req.params; 317 | const { oldPin, newPin } = req.body; 318 | const user = await User.findOne({ username }); 319 | const isPinCorrect = await bcryptjs.compare(oldPin, user.pin); 320 | if (!isPinCorrect) { 321 | return res.status(400).json({ message: "Incorrect Pin. Try again!" }); 322 | } 323 | 324 | const encryptNewPin = await bcryptjs.hash(newPin, 8); 325 | await User.findOneAndUpdate( 326 | { username }, 327 | { 328 | pin: encryptNewPin, 329 | } 330 | ); 331 | 332 | res.status(200).json({ message: "Pin changed successfully" }); 333 | } catch (err) { 334 | res.status(500).json({ message: err.message }); 335 | } 336 | }); 337 | 338 | authRouter.post("/admin/loginAdmin", async (req, res) => { 339 | try { 340 | const { email, password } = req.body; 341 | const user = await User.findOne({ email }); 342 | if (!user) { 343 | return res.status(400).json({ message: "User Not Found" }); 344 | } 345 | if (user.type != "admin" && user.type != "agent") { 346 | return res.status(400).json({ message: "Access Denied!" }); 347 | } 348 | const isMatch = await bcryptjs.compare(password, user.password); 349 | if (!isMatch) { 350 | return res.status(400).json({ massage: "Incorrect Password" }); 351 | } 352 | const token = await jwt.sign({ id: user._id }, process.env.TOKEN_STRING); 353 | 354 | res.status(200).json({ 355 | token, 356 | ...user._doc, 357 | }); 358 | } catch (e) { 359 | res.status(500).json({ message: e.message }); 360 | } 361 | }); 362 | 363 | authRouter.post("/admin/createAuthorizationPin", admin, async (req, res) => { 364 | try { 365 | const { adminAuthPin, admin } = req.body; 366 | const hashedPin = await bcryptjs.hash(adminAuthPin, 8); 367 | const newPin = new AdminAuthPin({ 368 | admin: admin, 369 | pin: hashedPin, 370 | }); 371 | await newPin.save(); 372 | res.status(200).json({ message: "Admin Auth Pin Created Successfully" }); 373 | } catch (e) { 374 | res.status(500).json({ message: e.message }); 375 | } 376 | }); 377 | 378 | authRouter.post("/admin/changeAdminAuthPin", admin, async (req, res) => { 379 | try { 380 | const { oldAdminAuthPin, newAdminAuthPin, admin } = req.body; 381 | const adminUsername = await AdminAuthPin.findOne({ admin: admin }); 382 | if (!adminUsername) { 383 | return res.status(400).json({ message: "Admin Not found" }); 384 | } 385 | const isMatch = await bcryptjs.compare(oldAdminAuthPin, adminUsername.pin); 386 | if (!isMatch) { 387 | return res 388 | .status(400) 389 | .json({ message: "Incorrect Old Authorization Pin" }); 390 | } 391 | const hashedPin = await bcryptjs.hash(newAdminAuthPin, 8); 392 | await AdminAuthPin.findOneAndUpdate({ pin: hashedPin }); 393 | res.status(200).json({ message: "Admin Auth Pin Updated Successfully" }); 394 | } catch (e) { 395 | res.status(500).json({ message: e.message }); 396 | } 397 | }); 398 | module.exports = authRouter; 399 | --------------------------------------------------------------------------------