From 418ee1baafff7041104995a4fd991d71be176591 Mon Sep 17 00:00:00 2001 From: ajinkya8010 Date: Mon, 18 Aug 2025 16:41:22 +0530 Subject: [PATCH] Add transaction model, controller, and routes with validation for expense category --- server/app.js | 2 + server/controllers/transaction.controllers.js | 40 +++++++++++++++++++ server/models/transaction.models.js | 29 ++++++++++++++ server/routers/transaction.routers.js | 17 ++++++++ 4 files changed, 88 insertions(+) create mode 100644 server/controllers/transaction.controllers.js create mode 100644 server/models/transaction.models.js create mode 100644 server/routers/transaction.routers.js diff --git a/server/app.js b/server/app.js index 8033926..3307243 100644 --- a/server/app.js +++ b/server/app.js @@ -15,6 +15,8 @@ app.get("/is-up", (req, res) => { // All the routes here import userRoutes from "./routers/user.routers.js"; +import transactionRoutes from "./routers/transaction.route.js"; app.use("/api/v1/users", userRoutes); +app.use("/api/transactions", transactionRoutes); export default app; diff --git a/server/controllers/transaction.controllers.js b/server/controllers/transaction.controllers.js new file mode 100644 index 0000000..225836f --- /dev/null +++ b/server/controllers/transaction.controllers.js @@ -0,0 +1,40 @@ +import { Transaction } from "../models/transaction.models.js"; +import ApiError from "../utils/ApiError.js"; +import ApiResponse from "../utils/ApiResponse.js"; +import asyncHandler from "../utils/asyncHandler.js"; + +// Create a new transaction +export const createTransaction = asyncHandler(async (req, res) => { + const { amount, category, type, date, note, goalId } = req.body; + + // If type is Expense, category is required + if (type === "Expense" && (!category || category.trim() === "")) { + throw new ApiError(400, "Category is required for Expense transactions."); + } + + const transaction = await Transaction.create({ + user: req.user._id, + amount, + category, + type, + date, + note, + goalId, + }); + + res.status(201).json(new ApiResponse(201, transaction, "Transaction created")); +}); + +// Get all transactions for the logged-in user +export const getTransactions = asyncHandler(async (req, res) => { + const transactions = await Transaction.find({ user: req.user._id }).sort({ date: -1 }); + res.json(new ApiResponse(200, transactions)); +}); + +// Delete a transaction by ID +export const deleteTransaction = asyncHandler(async (req, res) => { + const { id } = req.params; + const transaction = await Transaction.findOneAndDelete({ _id: id, user: req.user._id }); + if (!transaction) throw new ApiError(404, "Transaction not found"); + res.json(new ApiResponse(200, transaction, "Transaction deleted")); +}); \ No newline at end of file diff --git a/server/models/transaction.models.js b/server/models/transaction.models.js new file mode 100644 index 0000000..4affa3b --- /dev/null +++ b/server/models/transaction.models.js @@ -0,0 +1,29 @@ +import mongoose from "mongoose"; + +const transactionSchema = new mongoose.Schema( + { + user: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }, + amount: { type: Number, required: true }, + category: { + type: String, + validate: { + validator: function (v) { + // If type is "Expense", category is required (not empty) + if (this.type === "Expense") { + return typeof v === "string" && v.trim().length > 0; + } + // If type is "Income", category can be empty or undefined + return true; + }, + message: "Category is required for Expense transactions.", + }, + }, + type: { type: String, enum: ["Income", "Expense"], required: true }, + date: { type: String, required: true }, // e.g. "DD/MM/YYYY" + note: { type: String, default: "" }, + goalId: { type: mongoose.Schema.Types.ObjectId, ref: "Goal" }, // optional + }, + { timestamps: true } +); + +export const Transaction = mongoose.model("Transaction", transactionSchema); \ No newline at end of file diff --git a/server/routers/transaction.routers.js b/server/routers/transaction.routers.js new file mode 100644 index 0000000..b8d2b83 --- /dev/null +++ b/server/routers/transaction.routers.js @@ -0,0 +1,17 @@ +import express from "express"; +import { verifyUser } from "../middlewares/auth.middleware.js"; +import { + createTransaction, + getTransactions, + deleteTransaction, +} from "../controllers/transaction.controller.js"; + +const router = express.Router(); + +router.use(verifyUser); + +router.post("/", createTransaction); +router.get("/", getTransactions); +router.delete("/:id", deleteTransaction); + +export default router; \ No newline at end of file