├── 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 |
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 |
89 |
Please use the below ${purpose}
91 |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 | --------------------------------------------------------------------------------