diff --git a/.dockerignore b/.dockerignore index 942162e..b512c09 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1 @@ -node_modules -Dockerfile -.dockerignore \ No newline at end of file +node_modules \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..47dd670 --- /dev/null +++ b/app.js @@ -0,0 +1,41 @@ +const express = require("express"); +const cookieParser = require("cookie-parser"); +const monogoSanitize = require("express-mongo-sanitize"); +const morgan = require("morgan"); +const connectDB = require("./src/db/db"); + +// App logic based routes +const userRoute = require("./src/routes/userRoute"); +const groupRoute = require("./src/routes/groupRoute"); + +// Initialize the app +const app = express(); + +// Connect to the database +connectDB(); + +// Middleware to parse JSON and urlEncoded +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); +app.use(monogoSanitize()); + +// Cookie parser middleware +app.use(cookieParser()); + +// HTTP request logger middleware +app.use(morgan("tiny")); + +// Sample route to test the server +app.get("/home", (req, res) => { + res.status(200).json({ + message: "hello visitor!", + }); +}); + +// User route middleware +app.use("/user/new", userRoute); + +// Group create and join route middleware +app.use("/handle/g", groupRoute); + +module.exports = app; diff --git a/index.js b/index.js index bdc22d7..e860fde 100644 --- a/index.js +++ b/index.js @@ -1,59 +1,29 @@ require("dotenv").config(); -const express = require("express"); -const app = express(); -const PORT = process.env.PORT; -const cookieParser = require("cookie-parser"); -const monogoSanitize = require("express-mongo-sanitize"); +const app = require("./app"); const { pollEmailQueue, pollInviteQueue, + pollBarterNotification, } = require("./src/services/emailQueueProcessor"); -const connectDB = require("./src/db/db"); - -var morgan = require("morgan"); - -// call the db connection url -connectDB(); - -// Middleware to parse json and urlEncoded -app.use(express.urlencoded({ extended: true })); -app.use(express.json()); -app.use(monogoSanitize()); - -// cookie parser Middleware -app.use(cookieParser()); - -// HTTP request logger middleware -app.use(morgan("tiny")); - -//sample route to test the server -app.get("/home", (req, res) => { - res.status(200).json({ - message: "hello visitor !", - }); -}); - -// user route -const userRoute = require("./src/routes/userRoute"); - -// user route middleware -app.use("/user/new", userRoute); +const PORT = process.env.PORT; -// poll redis queues +// Start polling the Redis queues pollEmailQueue(); pollInviteQueue(); +pollBarterNotification(); +// Start the server const server = app.listen(PORT, () => { - console.log(`server is running on the port: ${PORT}`); + console.log(`Server is running on port: ${PORT}`); }); -// gracefully close the server (ctrl+c) +// Gracefully close the server on SIGINT (Ctrl+C) process.on("SIGINT", () => { - console.log(`SIGINT signal recieved, closing the server`); + console.log(`SIGINT signal received, closing the server...`); server.close(() => { - console.log(`Closed the HTTP server gracefully..`); + console.log(`Closed the HTTP server gracefully.`); process.exit(1); }); }); diff --git a/package-lock.json b/package-lock.json index c5bf4df..441e483 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,17 +13,15 @@ "axios": "^1.7.4", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.6", - "csv-parser": "^3.0.0", - "disposable-email-domains": "^1.0.62", "dotenv": "^16.4.5", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^7.4.0", - "hashi-vault-js": "^0.4.16", "jimp": "^1.3.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.5.3", "morgan": "^1.10.0", + "node-cron": "^3.0.3", "nodemailer": "^6.9.14", "qrcode": "^1.5.4", "rate-limit-redis": "^4.2.0", @@ -2520,21 +2518,6 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, - "node_modules/csv-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", - "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "csv-parser": "bin/csv-parser" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2604,12 +2587,6 @@ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, - "node_modules/disposable-email-domains": { - "version": "1.0.62", - "resolved": "https://registry.npmjs.org/disposable-email-domains/-/disposable-email-domains-1.0.62.tgz", - "integrity": "sha512-LBQvhRw7mznQTPoyZbsmYeNOZt1pN5aCsx4BAU/3siVFuiM9f2oyKzUaB8v1jbxFjE3aYqYiMo63kAL4pHgfWQ==", - "license": "MIT" - }, "node_modules/dotenv": { "version": "16.4.5", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", @@ -3045,18 +3022,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hashi-vault-js": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/hashi-vault-js/-/hashi-vault-js-0.4.16.tgz", - "integrity": "sha512-5pEQEYGOUP7USJc9m1O0HRG4tX/Pdvx8V4U7FozpceiU0/ECSUhtR8NVTirnSYixLusiQ5HSuvYc+8u4IOFF9w==", - "license": "MIT", - "dependencies": { - "axios": "^1.7.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3438,15 +3403,6 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/mongodb": { "version": "6.8.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.8.0.tgz", @@ -3618,6 +3574,27 @@ "node": ">= 0.6" } }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/node-cron/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/nodemailer": { "version": "6.9.15", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.15.tgz", diff --git a/package.json b/package.json index 264d3a9..7ecc54f 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "redis", "bill split app", "docker", - "project" + "project", + "bill-split" ], "author": "Abhishek Kumar", "license": "ISC", @@ -21,17 +22,15 @@ "axios": "^1.7.4", "bcryptjs": "^2.4.3", "cookie-parser": "^1.4.6", - "csv-parser": "^3.0.0", - "disposable-email-domains": "^1.0.62", "dotenv": "^16.4.5", "express": "^4.19.2", "express-mongo-sanitize": "^2.2.0", "express-rate-limit": "^7.4.0", - "hashi-vault-js": "^0.4.16", "jimp": "^1.3.0", "jsonwebtoken": "^9.0.2", "mongoose": "^8.5.3", "morgan": "^1.10.0", + "node-cron": "^3.0.3", "nodemailer": "^6.9.14", "qrcode": "^1.5.4", "rate-limit-redis": "^4.2.0", diff --git a/src/models/barterModel.js b/src/models/barterModel.js index 93ffd2b..a72f736 100644 --- a/src/models/barterModel.js +++ b/src/models/barterModel.js @@ -50,15 +50,3 @@ barterPaymentsSchema.pre("save", function (next) { const BarterPayment = mongoose.model("BarterPayment", barterPaymentsSchema); module.exports = BarterPayment; - -/* -debtor: The user who owes the amount and proposes a barter. -creditor: The user who is owed the amount and must approve the barter. -groupId: The group in which the barter payment is being made. -amount: The debt amount that the barter is being proposed for. -barterType: The type of barter (e.g., assignment, groceries) based on the amount. -status: The status of the barter proposal (pending, approved, rejected). -settlementDate: The date when the barter is settled (only set if the barter is approved). -timestamps: Auto-generated fields to track when the barter was created and last updated. - -*/ diff --git a/src/models/groupModel.js b/src/models/groupModel.js index 6cde466..c8299ba 100644 --- a/src/models/groupModel.js +++ b/src/models/groupModel.js @@ -38,7 +38,8 @@ const groupSchema = new mongoose.Schema({ ], maxBarterAmount: { type: Number, - default: 0, + min: 0, + required: true, }, createdAt: { type: Date, diff --git a/src/models/userModel.js b/src/models/userModel.js index cca5391..31cab43 100644 --- a/src/models/userModel.js +++ b/src/models/userModel.js @@ -43,6 +43,11 @@ const userSchema = new mongoose.Schema({ ref: "Event", }, ], + rislApetite: { + type: Number, + required: true, + min: 0, + }, preferences: { notifications: { type: Boolean, diff --git a/src/services/emailQueueProcessor.js b/src/services/emailQueueProcessor.js index d212979..f98a4a1 100644 --- a/src/services/emailQueueProcessor.js +++ b/src/services/emailQueueProcessor.js @@ -2,6 +2,7 @@ const { promisify } = require("util"); const redisClient = require("./redisServer"); const nodemailer = require("nodemailer"); const { json } = require("stream/consumers"); +const cron = require("node-cron"); // Using promisify to convert the callback based to promise chains const rpushAsync = promisify(redisClient.rPush).bind(redisClient); @@ -79,13 +80,27 @@ async function processRetryQueue(retryQueueName, mainQueueName) { } } -// Periodic queue polling function -function pollQueues(mainQueueName, retryQueueName, dlqName) { - setInterval(() => processQueue(mainQueueName, retryQueueName, dlqName), 1000); - setInterval(() => processRetryQueue(retryQueueName, mainQueueName), 1000); +// Run cron jobs to poll the queue at specific time +// in this case we will poll the main queue at 12 am +// night and retry queue 2-3 hours later +function pollQueues(mainQueueName, retry_queue, dlqName) { + // Run the job at night(12 AM ) for main queue + cron.schedule("0 2 * * *", () => { + console.log(`Processing main queue at night...12 AM`); + processQueue(mainQueueName, retry_queue, dlqName); + }); + + // Run the job at night(2 AM ) for retry queue + cron.schedule("0 2 * * *", () => { + console.log(`Processing retry queue at night...2 AM`); + processRetryQueue(retry_queue, dlqName); + }); } + module.exports = { pollEmailQueue: () => pollQueues("email_queue", "retry_queue", "email_dlq"), pollInviteQueue: () => pollQueues("invite_queue", "retry_invite_queue", "invite_dlq"), + pollBarterNotification: () => + pollQueues("barter_notification", "retry_queue", "email_dlq"), }; diff --git a/src/services/emailQueueProducer.js b/src/services/emailQueueProducer.js index 02ff40f..8c185f9 100644 --- a/src/services/emailQueueProducer.js +++ b/src/services/emailQueueProducer.js @@ -32,7 +32,22 @@ async function queueInviteEmailSending(mailOptions) { } } +async function queueBarterNotification(mailOptions) { + const job = json.stringify({ + mailOptions, + retries: 0, + }); + try { + await rpushAsync("barter_notification", job); + console.log(`Barter invite emails addded to queue`); + } catch (error) { + console.error(`Error adding emails to barter_notification queue`); + throw new Error(`Error adding emails to barter_notification queue`); + } +} + module.exports = { queueEmailSending, queueInviteEmailSending, + queueBarterNotification, };