Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d7e2bfe
login and navbar updates
L1ghtRay Sep 26, 2025
768ac7a
Admin route created
Adithyan19 Sep 26, 2025
f638156
combined sreedeep's code
Adithyan19 Sep 26, 2025
85acf12
corrected user model
Adithyan19 Sep 26, 2025
39a47be
users properly fetched at the frontend
Adithyan19 Sep 26, 2025
7c55e52
all other data fetching routes of admin implemented
Adithyan19 Sep 26, 2025
a8495e6
combined Jobin's donation code
Adithyan19 Sep 29, 2025
69a5be1
corrected errors while cloning MC branch
jobin2005 Sep 29, 2025
83824d0
Linked donor id with donation
jobin2005 Sep 30, 2025
7533111
added condition field in db and connected
jobin2005 Sep 30, 2025
77f72f2
Corrected date validation and price validation, and preference valida…
jobin2005 Sep 30, 2025
ba2a942
Merge branch 'Sree' into MC
SreedeepCode Oct 1, 2025
433afb3
Merge pull request #12 from L1ghtRay/MC
SreedeepCode Oct 1, 2025
78669ef
Merge pull request #13 from L1ghtRay/Jobin
SreedeepCode Oct 1, 2025
a034bff
ensureAuthenticated added
L1ghtRay Oct 1, 2025
cbccf60
Merge branch 'Sree' of https://github.com/L1ghtRay/reGive into Sree
L1ghtRay Oct 1, 2025
d3c5c31
feat: Completed dynamic homepage, refactored item/home routes into se…
aziyah12 Oct 1, 2025
938bf07
code formatting, added footer, added return-to-redirects in case of n…
L1ghtRay Oct 1, 2025
697a379
file size valiadtion in donation
jobin2005 Oct 1, 2025
bf4ba52
Merge remote-tracking branch 'origin/Jobin' into Sree
L1ghtRay Oct 1, 2025
d9aecf4
Merge remote-tracking branch 'origin/Azi' into Sree
L1ghtRay Oct 1, 2025
3b98f4c
delete uploads
L1ghtRay Oct 1, 2025
add0391
code formatting
L1ghtRay Oct 1, 2025
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
45 changes: 45 additions & 0 deletions backend/controllers/admincontroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import express from "express";
import user from "../models/users.js";
import item from "../models/items.js";
import transactions from "../models/transaction.js";
import reports from "../models/reports.js";

export const FetchUsers = async (req, res) => {
try {
const users = await user.find();
res.status(200).json(users);
} catch (error) {
console.error("Error fetching users:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};

export const FetchItems = async (req, res) => {
try {
const items = await item.find();
res.status(200).json(items);
} catch (error) {
console.error("Error fetching items:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};

export const FetchTransactions = async (req, res) => {
try {
const items = await transactions.find();
res.status(200).json(items);
} catch (error) {
console.error("Error fetching items:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};

export const FetchReports = async (req, res) => {
try {
const items = await reports.find();
res.status(200).json(items);
} catch (error) {
console.error("Error fetching items:", error);
res.status(500).json({ message: "Internal Server Error" });
}
};
197 changes: 197 additions & 0 deletions backend/controllers/donationcontroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/*
1. Since no categories are assigned in the db yet the category search cannot be performed
2. Also commented the the res.user since validation is not yet started we cannot use that
3. Since no category id is present i added random category id
*/
import Category from "../models/categories.js";
import Item from "../models/items.js";

export const donateItem = async (req, res) => {
try {
console.log("req.body:", req.body);
console.log("req.files:", req.files);

if (!req.user) {
return res.status(401).json({
error: "Unauthorized",
message: "User not authenticated",
});
}


const {
itemTitle,
description,
category,
subcategory,
condition,
location,
availableUntil,
urgentDonation,
isPaid,
price,
contactMethods,
} = req.body;

// Validate required fields
if (!itemTitle || !itemTitle.trim()) {
return res.status(400).json({
error: "Validation Error",
message: "Item title is required",
});
}

if (!category || !category.trim()) {
return res.status(400).json({
error: "Validation Error",
message: "Category is required",
});
}

if (!location || !location.trim()) {
return res.status(400).json({
error: "Validation Error",
message: "Location is required",
});
}


// Find category document
const categoryDoc = await Category.findOne({
name: category.trim().replace(/\r?\n/g, " "),
});

if (!categoryDoc) {
return res.status(400).json({
error: "Invalid Category",
message: "The selected category does not exist",
});
}

//Find Donor



// // Validate subcategory if provided
// if (
// subcategory &&
// categoryDoc.subcategories &&
// categoryDoc.subcategories.length > 0
// ) {
// const validSubcategory = categoryDoc.subcategories.includes(
// subcategory.trim()
// );
// if (!validSubcategory) {
// return res.status(400).json({
// error: "Invalid Subcategory",
// message: "The selected subcategory is not valid for this category",
// });
// }
// }

// Parse and validate price if it's a paid donation
let finalPrice = 0;
if (isPaid === "yes" || isPaid === true) {
finalPrice = parseFloat(price);
if (isNaN(finalPrice) || finalPrice < 0) {
return res.status(400).json({
error: "Validation Error",
message: "Please provide a valid price",
});
}
}

// Handle contact methods - ensure it's always an array
let contactMethodsArray = [];
if (contactMethods) {
if (Array.isArray(contactMethods)) {
contactMethodsArray = contactMethods;
} else {
contactMethodsArray = [contactMethods];
}
}


//validating date
// Validate availableUntil
let finalDate = null;
if (availableUntil) {
finalDate = new Date(availableUntil);
const today = new Date();
today.setHours(0,0,0,0); // normalize to midnight

if (isNaN(finalDate.getTime())) {
return res.status(400).json({
error: "Validation Error",
message: "Invalid date format for availableUntil"
});
}

if (finalDate < today) {
return res.status(400).json({
error: "Validation Error",
message: "Available until date cannot be in the past"
});
}
}


// Handle image URLs // currently storing file paths, need to be changes to Urls when w store it in cloud
const imageURLs =
req.files && req.files.length > 0
? req.files.map((f) => `/uploads/${f.filename}`)
: [];

// Create the item
const newItem = await Item.create({
name: itemTitle.trim(),
description: description ? description.trim() : "",
pickup: location.trim(),
condition: condition.trim(),
donorId: req.user._id,
isPaid: isPaid === "yes" || isPaid === true,
price: finalPrice,
urgent:
urgentDonation === "on" ||
urgentDonation === "true" ||
urgentDonation === true,
available_until: finalDate || null,
categoryId: categoryDoc._id,
subcategory: subcategory ? subcategory.trim() : "",
preferences: contactMethodsArray,
imageURL: imageURLs,
});

console.log("Item created successfully:", newItem);

return res.status(201).json({
success: true,
message: "Donation created successfully",
item: newItem,
});
} catch (err) {
console.error("Donation error:", err);

// Handle mongoose validation errors
if (err.name === "ValidationError") {
const messages = Object.values(err.errors).map((e) => e.message);
return res.status(400).json({
error: "Validation Error",
message: messages.join(", "),
});
}

// Handle duplicate key errors
if (err.code === 11000) {
return res.status(400).json({
error: "Duplicate Error",
message: "This item already exists",
});
}

return res.status(500).json({
error: "Server Error",
message: "Failed to save donation. Please try again later.",
});
}
};
38 changes: 38 additions & 0 deletions backend/controllers/homeController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import Item from '../models/items.js';

// **Simplified Filter:** Only check for essential display fields (Name, Description).
// This guarantees that any recently donated item with basic text data will show up.
const ESSENTIAL_FILTER = {
name: { $ne: null },
description: { $ne: null },
};

export const getHomePage = async (req, res) => {
try {
// Find the latest 5 items that meet the ESSENTIAL_FILTER
const frequentItems = await Item.find(ESSENTIAL_FILTER)
.sort({ createdAt: -1 }) // Sort by creation date (latest first)
.limit(5) // Limit to 5 items
.select('name description imageURL')
.exec();


// 🎯 DEBUG LINE: Log the number of items found
console.log(`[HOMEPAGE DEBUG] Items Found: ${frequentItems.length}`);

// 🎯 DEBUG LINE: Log the actual items (first item only for brevity)
if (frequentItems.length > 0) {
console.log(`[HOMEPAGE DEBUG] Latest Item Name: ${frequentItems[0].name}`);
}

res.render("home", {
frequentItems: frequentItems
});

} catch (error) {
console.error("Error fetching essential items for homepage:", error);
res.render("home", {
frequentItems: []
});
}
};
54 changes: 54 additions & 0 deletions backend/controllers/itemController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import express from "express";
import mongoose from "mongoose";
import User from "../models/users.js";
import Item from '../models/items.js';
import Category from '../models/categories.js';


export const getSearchResults = async (req, res) => {
const query = req.query.q; // e.g., "Stationery" or "baby doll"

if (!query) {
return res.render("../frontend/views/listing.ejs", { items: [] });
}


const regexQuery = new RegExp(query, 'i');
let items = [];

try {
// --- STEP 1: Search by Category Name ---
const category = await Category.findOne({ name: regexQuery });

if (category) {
// If the search query is a CATEGORY name (e.g., "Stationery"),
// return ALL items belonging to that category ID.
items = await Item.find({ categoryId: category._id })
.populate('categoryId', 'name')
.populate('donorId', 'name email')
.exec();
}

// --- STEP 2: Fallback Search (Item Name or Subcategory) ---
if (items.length === 0) {
// Search across item name, description, AND subcategory.
items = await Item.find({
$or: [
{ name: regexQuery },
{ description: regexQuery },
{ subcategory: regexQuery } // <-- Checks the subcategory field!
]
})
.populate('categoryId', 'name')
.populate('donorId', 'name email')
.exec();
}

// 3. Render the results page
res.render("itemListing.ejs", { items });

} catch (error) {
console.error("Search error:", error);
res.status(500).send("Error performing search.");
}
};
6 changes: 6 additions & 0 deletions backend/middleware/adminVerify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export const requireAdmin = (req, res, next) => {
if (req.user.role !== "admin") {
return res.status(403).json({ message: "Forbidden: Admins only" });
}
next();
};
18 changes: 18 additions & 0 deletions backend/middleware/verify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import jwt from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();

export function verifyToken(req, res, next) {
const authHeader = req.headers["authorization"];
const token = authHeader?.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "Access token missing!" });
}
jwt.verify(token, process.env.JWT_SECRET, (err, payload) => {
if (err) {
return res.status(403).json({ message: "Invalid or expired token!" });
}
req.user = payload;
next();
});
}
25 changes: 17 additions & 8 deletions backend/models/admin.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import mongoose from "mongoose";

const adminSchema = new mongoose.Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true, match: [/.+\@.+\..+/, "Please enter a valid email"] },
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
activeRequest: { type: Boolean, default: false }
}, { timestamps: true });
const adminSchema = new mongoose.Schema(
{
name: { type: String, required: true, trim: true },
email: {
type: String,
required: true,
unique: true,
lowercase: true,
match: [/.+\@.+\..+/, "Please enter a valid email"],
},
password: { type: String, required: true },
createdAt: { type: Date, default: Date.now },
activeRequest: { type: Boolean, default: false },
},
{ timestamps: true }
);

const Admin = mongoose.model("Admin", adminSchema, "Admin");
const Admin = mongoose.model("Admin", adminSchema, "admin");
export default Admin;
Loading