Skip to content

Commit

Permalink
revamped logic for more secure barter system
Browse files Browse the repository at this point in the history
  • Loading branch information
abhiraj-ku committed Oct 5, 2024
1 parent fa48eac commit 2d73a9f
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 667 deletions.
894 changes: 253 additions & 641 deletions package-lock.json

Large diffs are not rendered by default.

9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,20 @@
"@aws-sdk/client-s3": "^3.645.0",
"axios": "^1.7.4",
"bcryptjs": "^2.4.3",
"cookie-parser": "^1.4.6",
"cookie-parser": "^1.0.0",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"express": "^2.5.11",
"express-mongo-sanitize": "^2.2.0",
"express-rate-limit": "^7.4.0",
"express-rate-limit": "^5.5.1",
"jimp": "^1.3.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.5.3",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14",
"qrcode": "^1.5.4",
"rate-limit-redis": "^4.2.0",
"rate-limit-redis": "^2.1.0",
"redis": "^4.7.0",
"speakeasy": "^2.0.0",
"uuid": "^10.0.0",
Expand Down
80 changes: 79 additions & 1 deletion src/controllers/barterSettlement.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ How this Works?
Note: This is completely an Experiment Feature
*/

const fs = require('fs');
const { promisify } = require('util');
const { v4: uuidv4 } = require('uuid');

Expand All @@ -25,12 +25,14 @@ const groupModel = require('../models/groupModel');
// Helpers and emails services
const validateBarterPayInput = require('../helpers/validateBarterPay');
const queueBarterNotification = require('../services/emailQueueProducer');
const uploadMulter = require('../utils/multerConfig');

// Redis functional imports
const BarterPayment = require('../models/barterModel');
const mailOptions = require('../utils/mailOptions');

const redisClient = require('./redisServer');
const uploadToS3 = require('../utils/uploadToS3');
const setAsync = promisify(redisClient.set).bind(redisClient);
const expireAsync = promisify(redisClient.expire).bind(redisClient);
const getAsync = promisify(redisClient.get).bind(redisClient);
Expand Down Expand Up @@ -204,3 +206,79 @@ module.exports.respBarter = async (req, res) => {
res.status(500).json({ message: 'Server error while responding to barter request.' });
}
};

//TODO: Barter POST /api/barter/settleProof
module.exports.settlebarter = async (req, res) => {
const debtorId = req.user._id;
const { barterId } = req.body;

uploadMulter.single('evidence')(req, res, async function (err) {
if (err instanceof multer.MulterError) {
return res.status(400).json({ message: 'File upload error', error: err.message });
} else if (err) {
return res.status(400).json({ message: 'Unsupported file type', error: err.message });
}

if (!req.files) {
return res.status(400).json({ message: 'please upload evidences...' });
}

// get the filepath and name for uploading to s3
const evidencePath = req.file.path;
const filename = req.file.filename;

try {
const s3url = await uploadToS3(evidencePath, filename, debtorId);

// create a new settlement
const bSettlement = await BarterPayment.create({
barterId,
debtorId,
evidence: s3url,
votes: [],
});
await bSettlement.save();

fs.unlinkSync(evidencePath);

res.status(201).json({ message: 'Evidence uploade succesfully for review..' });
} catch (error) {
console.error(`Error settling barter request:`, error);
if (fs.existsSync(evidencePath)) {
fs.unlinkSync(evidencePath);
}
res.status(500).json({ message: 'Server error cannot upload files...' });
}
});
};

//TODO: Barter POST /api/barter/vote
module.exports.voteOnBarter = async (req, res) => {
const { barterId } = req.params;
const vote = req.body;
const userId = req.user._id;

if (!['satisfactory', 'unsatisfactory'].includes(vote)) {
return res.status(400).json({ message: 'Invalid vote. Please provide among the two options' });
}
try {
const bsettlment = await BarterPayment.findOne({ barterId });
if (!bsettlment) {
return res.status(404).json({ message: 'Settment not found' });
}

// check if current user has already votes
const existingVote = bsettlment.votes.find((v) => v.userId.toString() === userId.toString());
if (existingVote) {
return res.status(400).json({ message: 'You have already voted on this barter request.' });
}

// add the user's votes
bsettlment.votes.push({ userId, vote });
await bsettlment.save();
return res.status(200).json({ message: 'Vote cast successfully.', barterRequest });
} catch (error) {
console.log(`Error while casting vote request`, error);
res.status(500).json({ message: 'Server error while voting on barter request.' });
}
};
3 changes: 1 addition & 2 deletions src/db/db.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const mongoose = require("mongoose");

const mongoose = require('mongoose');
// Retry options
// const retryOptions = {
// maxAttempt: 5,
Expand Down
18 changes: 18 additions & 0 deletions src/models/barterModel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
const mongoose = require('mongoose');
const { Schema } = mongoose;

// votes schema
const voteSchema = new Schema({
userId: {
type: Schema.Types.ObjectId,
ref: User,
required: true,
},
vote: {
type: String,
enum: ['satisfactory', 'unsatisfactory'],
required: true,
},
});

const barterPaymentsSchema = new Schema(
{
barterId: {
Expand Down Expand Up @@ -41,6 +55,10 @@ const barterPaymentsSchema = new Schema(
enum: ['none', 'debtor_approved', 'creditor_approved', 'both_approved'],
default: 'none',
},
evidence: {
type: String,
},
votes: [voteSchema],
settlementdate: { type: Date, default: Date.now },
},
{ timestamps: true }
Expand Down
28 changes: 16 additions & 12 deletions src/models/userModel.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const bcrypt = require("bcryptjs");
const mongoose = require("mongoose");
const jwt = require("jsonwebtoken");
const bcrypt = require('bcryptjs');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');

const userSchema = new mongoose.Schema({
name: {
Expand Down Expand Up @@ -31,16 +31,20 @@ const userSchema = new mongoose.Schema({
avatar: {
type: String, // URL to the user's profile picture
},
valuePoints: {
type: Number,
default: 100,
},
groups: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Group",
ref: 'Group',
},
],
events: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Event",
ref: 'Event',
},
],
riskApetite: {
Expand All @@ -55,7 +59,7 @@ const userSchema = new mongoose.Schema({
},
currency: {
type: String,
default: "USD",
default: 'USD',
},
},
badges: [
Expand All @@ -79,7 +83,7 @@ const userSchema = new mongoose.Schema({
});

// decrypt the password before saving using mongoose pre method
userSchema.pre("save", async () => {
userSchema.pre('save', async () => {
if (!this.isModified) return;
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
Expand All @@ -97,20 +101,20 @@ userSchema.methods.comparePassword = async function (userPassword) {
userSchema.methods.createJwtToken = function () {
// Ensure `process.env.JWT_SECRET` is set correctly
if (!process.env.JWT_SECRET) {
throw new Error("JWT_SECRET environment variable is not set.");
throw new Error('JWT_SECRET environment variable is not set.');
}

try {
// Create a JWT token
return jwt.sign(
{ userId: this._id, email: this.email }, // Payload
process.env.JWT_SECRET, // Secret key
{ expiresIn: "1d" } // Token expiration
{ expiresIn: '1d' } // Token expiration
);
} catch (error) {
console.error("Error generating JWT token:", error);
throw new Error("Error generating JWT token");
console.error('Error generating JWT token:', error);
throw new Error('Error generating JWT token');
}
};

module.exports = mongoose.model("User", userSchema);
module.exports = mongoose.model('User', userSchema);
37 changes: 37 additions & 0 deletions src/utils/multerConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const path = require('path');
const fs = require('fs');

const proofsDir = path.join(__dirname, `../../debtor-proofs`);
if (!fs.existsSync(proofsDir)) {
fs.mkdirSync(proofsDir, { recursive: true });
}

const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, proofsDir);
},
filename: function (req, res, file) {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
},
});

// setup multer

const upload = multer({
storage: storage,
limits: 10 * 1024 * 1024,
fileFilter: function (req, res, cb) {
const fileTypes = /jpg|jpeg|png|pdf/;
const mimetype = fileTypes.test(file.mimetype);
const extname = fileTypes.test(path.extname(file.originalname).toLocaleLowerCase());

if (mimetype && extname) {
return cb(null, true);
} else {
cb(new Error('Only images and PDFs are allowed!'));
}
},
});

module.exports = upload;
14 changes: 7 additions & 7 deletions src/utils/uploadToS3.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const { s3Client, PutObjectCommand } = require("@aws-sdk/client-s3");
const fs = require("fs");
const path = require("path");
const { s3Client, PutObjectCommand } = require('@aws-sdk/client-s3');
const fs = require('fs');
const path = require('path');

// Create a S3 client using aws sdk
const s3Client = {
Expand All @@ -13,17 +13,17 @@ const s3Client = {

// Upload to AWS S3 Bucket

async function uploadToS3(filePath, fileName) {
async function uploadToS3(filePath, fileName, userId) {
try {
// Read the file content
const fileContent = fs.readFileSync(filePath);

const s3Params = {
Bucket: process.env.AWS_BUCKET_NAME,
Key: `profile-images/${fileName}`,
Key: `${userId}/profile-images/${fileName}`,
Body: fileContent,
ContentType: `images/jpg`,
ACL: "public-read",
ACL: 'public-read',
};

const command = new PutObjectCommand(s3Params);
Expand All @@ -34,7 +34,7 @@ async function uploadToS3(filePath, fileName) {
// send url of uploaded images to frontend
const s3ProfileImageUrl = `https://${process.env.AWS_S3_BUCKET_NAME}
.s3.${process.env.AWS_REGION}
.amazonaws.com/profile-images/${fileName}`;
.amazonaws.com/${userId}/profile-images/${fileName}`;

// Remove this file from local
fs.unlinkSync(filePath);
Expand Down

0 comments on commit 2d73a9f

Please sign in to comment.