Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 65 additions & 44 deletions Backend/src/controllers/walletController.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// import { count } from "node:console";
import { depositToWallet, withdrawFromWallet, transferFunds, getTransactionHistory, getWalletByUserId , getAllWallets} from "../services/walletService.js";
import { depositToWallet, withdrawFromWallet, transferFunds, getTransactionHistory, getWalletByUserId, getAllWallets } from "../services/walletService.js";

export const deposit = async (req, res) => {
const { userId }=req.user;
const { userId } = req.user;
const { amount, description } = req.body;
const idempotencyKey = req.headers['idempotency-key'];

try {
const uId = parseInt(userId);
if (!uId ) {
if (!uId) {
return res.status(400).json({
success: false,
error: "Invalid input: user Id is required."
Expand All @@ -17,10 +17,10 @@ export const deposit = async (req, res) => {
const parsedAmount = parseFloat(amount);

if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) {
return res.status(400).json({
success: false,
error: "Invalid amount. Please provide a positive number."
});
return res.status(400).json({
success: false,
error: "Invalid amount. Please provide a positive number."
});
}

const result = await depositToWallet(uId, parsedAmount, description, idempotencyKey);
Expand All @@ -37,20 +37,20 @@ export const deposit = async (req, res) => {
});

} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
}
};

export const withdraw = async (req, res) => {
const {userId}=req.user;
const { userId } = req.user;
const { amount, description } = req.body;
const idempotencyKey = req.headers['idempotency-key'];
try {
const uId = parseInt(userId);
if (!uId ) {
if (!uId) {
return res.status(400).json({
success: false,
error: "Invalid input: user Id is required."
Expand All @@ -59,10 +59,10 @@ export const withdraw = async (req, res) => {
const parsedAmount = parseFloat(amount);

if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) {
return res.status(400).json({
success: false,
error: "Invalid amount. Please provide a positive number."
});
return res.status(400).json({
success: false,
error: "Invalid amount. Please provide a positive number."
});
}

const result = await withdrawFromWallet(uId, parsedAmount, description, idempotencyKey);
Expand All @@ -78,15 +78,15 @@ export const withdraw = async (req, res) => {
alreadyProcessed: result.alreadyProcessed || false
});
} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
}
};

export const transfer = async (req, res) => {
const {userId:senderId} = req.user;
const { userId: senderId } = req.user;
const { receiverId, amount, description } = req.body;
const idempotencyKey = req.headers['idempotency-key'];

Expand All @@ -104,8 +104,8 @@ export const transfer = async (req, res) => {
}
const parsedAmount = parseFloat(amount);
if (!parsedAmount || isNaN(parsedAmount) || parsedAmount <= 0) {
return res.status(400).json({
success: false,
return res.status(400).json({
success: false,
error: "Invalid amount. The transferred amount must be greater than zero."
});
}
Expand All @@ -121,11 +121,11 @@ export const transfer = async (req, res) => {
alreadyProcessed: result.alreadyProcessed || false
});
} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
}
};

export const getHistory = async (req, res) => {
Expand All @@ -134,29 +134,41 @@ export const getHistory = async (req, res) => {
try {
const skip = parseInt(req.query.skip) || 0;
const take = Math.min(parseInt(req.query.take) || 10, 50);
let type = req.query.type;
if (type) {

type = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
}
const uId = parseInt(userId);
if (!uId ) {
if (!uId) {
return res.status(400).json({
success: false,
error: "Invalid input: user Id is required."
});
}
const history = await getTransactionHistory(uId, skip, take);

const filters = {
startDate: req.query.startDate,
endDate: req.query.endDate,
type: type,
};

const history = await getTransactionHistory(uId, skip, take, filters);
res.status(200).json({
success: true,
count: history.length,
data: history
});
} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
}
};

export const getBalance = async (req, res) => {
const { userId } = req.user;
const { userId } = req.user;
try {
const uId = parseInt(userId);
if (!uId) {
Expand All @@ -178,32 +190,41 @@ export const getBalance = async (req, res) => {
recentTransactions: wallet.transactions
});
} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
}
};

export const getAllWalletsForAdmin = async (req, res) => {
try {
const skip = parseInt(req.query.skip) || 0;
const take = Math.min(parseInt(req.query.take) || 10, 50);
const wallets = await getAllWallets(skip, take);

const minB = req.query.minBalance;
const maxB = req.query.maxBalance;

const filters = {
minBalance: (minB !== undefined && minB !== '') ? parseFloat(minB) : undefined,
maxBalance: (maxB !== undefined && maxB !== '') ? parseFloat(maxB) : undefined,
};

const wallets = await getAllWallets(skip, take, filters);

if (!wallets || wallets.length === 0) {
return res.status(404).json({ success: false, error: "No Wallets found" });
}

res.status(200).json({
success: true,
count:wallets.length,
count: wallets.length,
data: wallets
});
} catch (error) {
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
return res.status(error.statusCode || 500).json({
success: false,
error: error.message
});
}
};
Expand Down
140 changes: 137 additions & 3 deletions Backend/src/middlewares/walletMiddleware.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,144 @@
import { TransactionType } from "../data/constants.js";

export const validateIdempotency = (req, res, next) => {
const idempotencyKey = req.headers['idempotency-key'];
if (!idempotencyKey) {
return res.status(400).json({
return res.status(400).json({
success: false,
error: "Transaction cannot be processed without a unique request ID."
error: "Transaction cannot be processed without a unique request ID."
});
}
next();
};
};

export const validateWalletFilters = (req, res, next) => {
const minB = req.query.minBalance;
const maxB = req.query.maxBalance;
const skip = req.query.skip;
const take = req.query.take;



if (skip !== undefined) {
const skipInt = parseInt(skip);
if (isNaN(skipInt) || skipInt < 0) {
return res.status(400).json({
success: false,
error: "Invalid skip value. Pagination starting index cannot be negative."
});
}
}


// 2. Validate take
if (take) {
const takeInt = parseInt(take);
if (isNaN(takeInt) || takeInt < 0 || takeInt > 100) {
return res.status(400).json({
success: false,
error: "Invalid take value. Must be between 0 and 100."
});
}
req.query.take = takeInt;
}

// Validate minBalance
if (minB !== undefined && minB !== '' && (isNaN(parseFloat(minB)) || parseFloat(minB) < 0)) {
return res.status(400).json({
success: false,
error: "Invalid minBalance value. Must be a non-negative number."
});
}
// Validate maxBalance
if (maxB !== undefined && maxB !== '' && (isNaN(parseFloat(maxB)) || parseFloat(maxB) < 0)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var !== '' check is added for minB & maxB and not for skip & take
while all are supposed to be numerical values.

return res.status(400).json({
success: false,
error: "Invalid maxBalance value. Must be a non-negative number."
});
}
// Validate minBalance <= maxBalance
if (
minB !== undefined && minB !== '' &&
maxB !== undefined && maxB !== '' &&
parseFloat(minB) > parseFloat(maxB)
) {
return res.status(400).json({
success: false,
error: "minBalance cannot be greater than maxBalance."
});
}
next();
};


export const validateTransactionFilters = (req, res, next) => {
const { skip,take,startDate, endDate, type } = req.query;

if (skip !== undefined) {
const skipInt = parseInt(skip);
if (isNaN(skipInt) || skipInt < 0) {
return res.status(400).json({
success: false,
error: "Invalid skip value. Pagination starting index cannot be negative."
});
}
}


// 2. Validate take
if (take) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider extracting pagination validation into its own middleware.

Right now skip and take are validated in both validateWalletFilters and validateTransactionFilters with the same logic.

This could be a good opportunity to keep the code DRY and maintain a single source of truth for pagination rules.

A small refactor, like creating a validatePagination middleware, would let you reuse it across any route that needs pagination.

const takeInt = parseInt(take);
if (isNaN(takeInt) || takeInt < 0 || takeInt > 100) {
return res.status(400).json({
success: false,
error: "Invalid take value. Must be between 0 and 100."
});
}
req.query.take = takeInt;
}


if (startDate) {
const start = new Date(startDate);
if (isNaN(start.getTime())) {
return res.status(400).json({
success: false,
error: "Invalid startDate format. Use YYYY-MM-DD format."
});
}
}
if (endDate) {
const end = new Date(endDate);
if (isNaN(end.getTime())) {
return res.status(400).json({
success: false,
error: "Invalid endDate format. Use YYYY-MM-DD format."
});
}
}
// Validate date range
if (startDate && endDate) {
const start = new Date(startDate);
const end = new Date(endDate);
if (start > end) {
return res.status(400).json({
success: false,
error: "startDate must be before endDate."
});
}
}
// Validate transaction type if provided
if (type) {
const formattedType = type.charAt(0).toUpperCase() + type.slice(1).toLowerCase();
const validTypes = Object.values(TransactionType);

if (!validTypes.includes(formattedType)) {
return res.status(400).json({
success: false,
error: `Invalid transaction type. Allowed values: ${validTypes.join(', ')}`
});
}
req.query.type = formattedType;
}
next();
};
Loading