diff --git a/backend/controllers/admincontroller.js b/backend/controllers/admincontroller.js new file mode 100644 index 0000000..d8ef637 --- /dev/null +++ b/backend/controllers/admincontroller.js @@ -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" }); + } +}; diff --git a/backend/controllers/donationcontroller.js b/backend/controllers/donationcontroller.js new file mode 100644 index 0000000..899ecaa --- /dev/null +++ b/backend/controllers/donationcontroller.js @@ -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.", + }); + } +}; diff --git a/backend/controllers/homeController.js b/backend/controllers/homeController.js new file mode 100644 index 0000000..d50fe02 --- /dev/null +++ b/backend/controllers/homeController.js @@ -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: [] + }); + } +}; \ No newline at end of file diff --git a/backend/controllers/itemController.js b/backend/controllers/itemController.js new file mode 100644 index 0000000..aa88cd6 --- /dev/null +++ b/backend/controllers/itemController.js @@ -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."); + } +}; diff --git a/backend/middleware/adminVerify.js b/backend/middleware/adminVerify.js new file mode 100644 index 0000000..7972fd5 --- /dev/null +++ b/backend/middleware/adminVerify.js @@ -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(); +}; diff --git a/backend/middleware/verify.js b/backend/middleware/verify.js new file mode 100644 index 0000000..7ccdc67 --- /dev/null +++ b/backend/middleware/verify.js @@ -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(); + }); +} diff --git a/backend/models/admin.js b/backend/models/admin.js index 726603d..f4725f5 100644 --- a/backend/models/admin.js +++ b/backend/models/admin.js @@ -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; diff --git a/backend/models/categories.js b/backend/models/categories.js index b64cd80..893693c 100644 --- a/backend/models/categories.js +++ b/backend/models/categories.js @@ -1,10 +1,13 @@ import mongoose from "mongoose"; -const categorySchema = new mongoose.Schema({ - name: { type: String, required: true, trim: true }, - image: { type: String, required: true }, - description: { type: String, default: "" } -}, { timestamps: true }); +const categorySchema = new mongoose.Schema( + { + name: { type: String, required: true, trim: true }, + image: { type: String, required: true }, + description: { type: String, default: "" }, + }, + { timestamps: true } +); -const Category = mongoose.model("Category", categorySchema, "categories"); +const Category = mongoose.model("category", categorySchema, "catalog"); export default Category; diff --git a/backend/models/items.js b/backend/models/items.js index 2c58d60..68f9225 100644 --- a/backend/models/items.js +++ b/backend/models/items.js @@ -1,21 +1,48 @@ -import mongoose from "mongoose"; + import mongoose from "mongoose"; -const itemSchema = new mongoose.Schema({ - name: { type: String, required: true, trim: true }, - description: { type: String, required: true }, - imageURL: { type: [String], default: [] }, - tags: { type: [String], default: [] }, - donorId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - isPaid: { type: Boolean, default: false }, - price: { type: Number, default: 0 }, - status: { type: String, enum: ["available", "unavailable", "reserved"], default: "available" }, - pickup: { type: String, required: true }, - uploadedAt: { type: Date, default: Date.now }, - categoryId: { type: mongoose.Schema.Types.ObjectId, ref: "categories", required: true }, - available_until: { type: Date }, - urgent: { type: Boolean, default: false }, - preferences: { type: [String], default: [] } -}, { timestamps: true }); +const itemSchema = new mongoose.Schema( + { + name: { type: String, required: true, trim: true }, + description: { type: String, required: true }, + imageURL: { type: [String], default: [] }, + + donorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: false, + }, + + isPaid: { type: Boolean, default: false }, + price: { type: Number, default: 0 }, + + status: { + type: String, + enum: ["available", "unavailable", "reserved"], + default: "available", + }, + + pickup: { type: String, required: true }, + available_until: { type: Date }, + urgent: { type: Boolean, default: false }, + condition: {type: String, required: true}, + +preferences: { + type: [String], + required: true, + validate: { + validator: (arr) => arr.length > 0, + message: "At least one preference is required" + } +}, + categoryId: { + type: mongoose.Schema.Types.ObjectId, + ref: "category", + required: true, + }, + subcategory: { type: String, required: true }, + }, + { timestamps: true } +); const Item = mongoose.model("Item", itemSchema, "items"); export default Item; diff --git a/backend/models/message.js b/backend/models/message.js index a6e8ccc..ed45f21 100644 --- a/backend/models/message.js +++ b/backend/models/message.js @@ -1,12 +1,27 @@ import mongoose from "mongoose"; -const messageSchema = new mongoose.Schema({ - itemId: { type: mongoose.Schema.Types.ObjectId, ref: "items", required: true }, - senderId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - receiverId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - message: { type: String, required: true, trim: true }, - sentAt: { type: Date, default: Date.now } -}, { timestamps: true }); +const messageSchema = new mongoose.Schema( + { + itemId: { + type: mongoose.Schema.Types.ObjectId, + ref: "items", + required: true, + }, + senderId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + receiverId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + message: { type: String, required: true, trim: true }, + sentAt: { type: Date, default: Date.now }, + }, + { timestamps: true } +); const Message = mongoose.model("Message", messageSchema, "message"); export default Message; diff --git a/backend/models/recommendations.js b/backend/models/recommendations.js index 36952ef..f64b1aa 100644 --- a/backend/models/recommendations.js +++ b/backend/models/recommendations.js @@ -1,10 +1,23 @@ import mongoose from "mongoose"; -const recommendationSchema = new mongoose.Schema({ - itemId: { type: mongoose.Schema.Types.ObjectId, ref: "item", required: true }, - recommendedItemIds: [{ type: mongoose.Schema.Types.ObjectId, ref: "item" }], - generatedAt: { type: Date, default: Date.now } -}, { timestamps: true }); +const recommendationSchema = new mongoose.Schema( + { + itemId: { + type: mongoose.Schema.Types.ObjectId, + ref: "items", + required: true, + }, + recommendedItemIds: [ + { type: mongoose.Schema.Types.ObjectId, ref: "items" }, + ], + generatedAt: { type: Date, default: Date.now }, + }, + { timestamps: true } +); -const Recommendation = mongoose.model("Recommendation", recommendationSchema, "recommendations"); +const Recommendation = mongoose.model( + "Recommendation", + recommendationSchema, + "recommendations" +); export default Recommendation; diff --git a/backend/models/reports.js b/backend/models/reports.js index 7e4361a..7c39b00 100644 --- a/backend/models/reports.js +++ b/backend/models/reports.js @@ -1,12 +1,27 @@ import mongoose from "mongoose"; -const reportSchema = new mongoose.Schema({ - reportedBy: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - itemId: { type: mongoose.Schema.Types.ObjectId, ref: "items", required: true }, - description: { type: String, default: "" }, - status: { type: String, enum: ["pending", "reviewed", "resolved", "rejected"], default: "pending" }, - createdAt: { type: Date, default: Date.now } -}, { timestamps: true }); +const reportSchema = new mongoose.Schema( + { + reportedBy: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + itemId: { + type: mongoose.Schema.Types.ObjectId, + ref: "items", + required: true, + }, + description: { type: String, default: "" }, + status: { + type: String, + enum: ["pending", "reviewed", "resolved", "rejected"], + default: "pending", + }, + createdAt: { type: Date, default: Date.now }, + }, + { timestamps: true } +); const Report = mongoose.model("Report", reportSchema, "reports"); export default Report; diff --git a/backend/models/requests.js b/backend/models/requests.js index 2c7116c..5104c1a 100644 --- a/backend/models/requests.js +++ b/backend/models/requests.js @@ -1,12 +1,27 @@ import mongoose from "mongoose"; -const requestSchema = new mongoose.Schema({ - userId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - itemId: { type: mongoose.Schema.Types.ObjectId, ref: "items", required: true }, - status: { type: String, enum: ["Pending", "Approved", "Rejected"], default: "Pending" }, - requestedAt: { type: Date, default: Date.now }, - message: { type: String, default: "" } -}, { timestamps: true }); +const requestSchema = new mongoose.Schema( + { + userId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + itemId: { + type: mongoose.Schema.Types.ObjectId, + ref: "items", + required: true, + }, + status: { + type: String, + enum: ["Pending", "Approved", "Rejected"], + default: "Pending", + }, + requestedAt: { type: Date, default: Date.now }, + message: { type: String, default: "" }, + }, + { timestamps: true } +); const Request = mongoose.model("Request", requestSchema, "requests"); export default Request; diff --git a/backend/models/transaction.js b/backend/models/transaction.js index 393b21c..d3c35c0 100644 --- a/backend/models/transaction.js +++ b/backend/models/transaction.js @@ -1,15 +1,38 @@ import mongoose from "mongoose"; -const transactionSchema = new mongoose.Schema({ - itemId: { type: mongoose.Schema.Types.ObjectId, ref: "item", required: true }, - donorId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - receiverId: { type: mongoose.Schema.Types.ObjectId, ref: "user", required: true }, - status: { type: String, enum: ["initiated", "completed", "cancelled"], default: "initiated" }, - timestamp: { type: Date, default: Date.now }, - type: { type: String, enum: ["free", "paid"], required: true }, - amount: { type: Number, default: 0 }, - pointsEarned: { type: Number, default: 0 } -}, { timestamps: true }); +const transactionSchema = new mongoose.Schema( + { + itemId: { + type: mongoose.Schema.Types.ObjectId, + ref: "items", + required: true, + }, + donorId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + receiverId: { + type: mongoose.Schema.Types.ObjectId, + ref: "users", + required: true, + }, + status: { + type: String, + enum: ["initiated", "completed", "cancelled"], + default: "initiated", + }, + timestamp: { type: Date, default: Date.now }, + type: { type: String, enum: ["free", "paid"], required: true }, + amount: { type: Number, default: 0 }, + pointsEarned: { type: Number, default: 0 }, + }, + { timestamps: true } +); -const Transaction = mongoose.model("Transaction", transactionSchema, "transactions"); +const Transaction = mongoose.model( + "Transaction", + transactionSchema, + "transactions" +); export default Transaction; diff --git a/backend/models/users.js b/backend/models/users.js index 2db20cd..77c83b6 100644 --- a/backend/models/users.js +++ b/backend/models/users.js @@ -2,13 +2,17 @@ import mongoose from "mongoose"; const userSchema = new mongoose.Schema({ name: { type: String, required: true, trim: true }, + displayName: { type: String, required: true, trim: true }, email: { type: String, required: true, unique: true, lowercase: true, trim: true }, role: { type: String, enum: ["student", "teacher", "admin"], default: "student" }, profile: { type: String, default: "" }, points: { type: Number, default: 0 }, joinedAt: { type: Date, default: Date.now }, - status: { type: String, enum: ["active", "inactive", "banned"], default: "active" } + status: { type: String, enum: ["active", "inactive", "banned"], default: "active" }, + phone: { type: String }, + address: { type: String }, + setupComplete: { type: Boolean, default: false } }, { timestamps: true }); -const User = mongoose.model("User", userSchema, "users"); +const User = mongoose.model("users", userSchema, "users"); export default User; diff --git a/backend/routes/adminroutes.js b/backend/routes/adminroutes.js new file mode 100644 index 0000000..00ed633 --- /dev/null +++ b/backend/routes/adminroutes.js @@ -0,0 +1,17 @@ +import express from "express"; +import { verifyToken } from "../middleware/verify.js"; +import { requireAdmin } from "../middleware/adminVerify.js"; +import { + FetchUsers, + FetchItems, + FetchTransactions, + FetchReports, +} from "../controllers/admincontroller.js"; +const router = express.Router(); + +router.get("/users", FetchUsers); //router.get("/users", verifyToken, requireAdmin, FetchUsers); removed authentication for testing +router.get("/items", FetchItems); +router.get("/transactions", FetchTransactions); +router.get("/reports", FetchReports); + +export default router; diff --git a/backend/routes/authroutes.js b/backend/routes/authroutes.js new file mode 100644 index 0000000..7a8bc97 --- /dev/null +++ b/backend/routes/authroutes.js @@ -0,0 +1 @@ +import express from "express"; diff --git a/backend/routes/donationroutes.js b/backend/routes/donationroutes.js new file mode 100644 index 0000000..a5f10f2 --- /dev/null +++ b/backend/routes/donationroutes.js @@ -0,0 +1,45 @@ +/* +1. Will have to change the image storing part once we have cloud storage +2. Add authentication middleware +*/ +import express from "express"; +import multer from "multer"; +import fs from "fs"; +import path from "path"; +import { verifyToken } from "../middleware/verify.js"; +import { donateItem } from "../controllers/donationcontroller.js"; +const router = express.Router(); + +// Ensure uploads folder exists +const uploadDir = path.join(process.cwd(), "uploads"); +if (!fs.existsSync(uploadDir)) { + fs.mkdirSync(uploadDir, { recursive: true }); +} + +// Multer storage config +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, uploadDir); + }, + filename: (req, file, cb) => { + const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); + cb(null, uniqueSuffix + path.extname(file.originalname)); + }, +}); + +// File filter for images +const fileFilter = (req, file, cb) => { + if (file.mimetype.startsWith("image/")) { + cb(null, true); + } else { + cb(new Error("Only images are allowed"), false); + } +}; + +const upload = multer({ storage, fileFilter , + limits: { fileSize: 5 * 1024 * 1024 } +}); + +router.post("/donate", upload.array("images", 5), donateItem); + +export default router; diff --git a/backend/routes/homeRoutes.js b/backend/routes/homeRoutes.js new file mode 100644 index 0000000..2706b75 --- /dev/null +++ b/backend/routes/homeRoutes.js @@ -0,0 +1,10 @@ + +import express from 'express'; +import { getHomePage } from '../controllers/homeController.js'; + +const router = express.Router(); + +// Maps the root URL (/) to the getHomePage controller +router.get('/', getHomePage); + +export default router; \ No newline at end of file diff --git a/backend/routes/itemroutes.js b/backend/routes/itemroutes.js new file mode 100644 index 0000000..9e7f6c9 --- /dev/null +++ b/backend/routes/itemroutes.js @@ -0,0 +1,95 @@ +import express from "express"; +import mongoose from "mongoose"; + + +import { getSearchResults } from '../controllers/itemController.js'; +const router = express.Router(); +router.get('/search', getSearchResults); +export default router; + + +/* +import express from "express"; +import mongoose from "mongoose"; + +import User from "../models/users.js"; // Target for donorId population +import Item from '../models/items.js'; +import Category from '../models/categories.js'; + +// 1. Initialize the Express Router +const router = express.Router(); + +// Define a filter to ensure linked documents exist (preventing null/undefined errors). +// This excludes items where the donor or category relationship is broken. +const VALIDITY_FILTER = { + donorId: { $ne: null }, + categoryId: { $ne: null } +}; + + +// 2. Define the Search Controller Function +export const getSearchResults = async (req, res) => { + const query = req.query.q; + + if (!query) { + return res.render("../frontend/views/listing.ejs", { items: [] }); + } + + // Convert the search query to a case-insensitive regular expression + const regexQuery = new RegExp(query, 'i'); + let items = []; + + try { + // --- STEP 1: Search by Category Name --- + const category = await Category.findOne({ name: regexQuery }); + + if (category) { + // Find items by category ID AND apply the validity filter + items = await Item.find({ + categoryId: category._id, + ...VALIDITY_FILTER // FIX: Only return valid, non-corrupt items + }) + .populate('categoryId', 'name') + .populate('donorId', 'name email') + .exec(); + } + + // --- STEP 2: Fallback Search (Item Name, Description, or Subcategory) --- + if (items.length === 0) { + + // Define the complex search criteria using $and to enforce validity + const itemSearchCriteria = { + $and: [ + { ...VALIDITY_FILTER }, // FIX: Ensure donorId and categoryId are not null + { $or: [ + { name: regexQuery }, + { description: regexQuery }, + { subcategory: regexQuery } + ]} + ] + }; + + items = await Item.find(itemSearchCriteria) + .populate('categoryId', 'name') + .populate('donorId', 'name email') + .exec(); + } + + // 3. Render the results page + return res.render("../frontend/views/listing.ejs", { items }); + + } catch (error) { + // Log the specific error to the console for real debugging + console.error("Critical Search Execution Error for query:", query, error); + + // Respond with the 500 status + return res.status(500).send("Error performing search."); + } +}; + +// 3. Define the Route using the Controller Function +router.get('/search', getSearchResults); + +// 4. Export the Router +export default router; +*/ \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index b88a7f0..a949cba 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,249 +1,220 @@ -import express from 'express'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import passport from 'passport'; -import session from 'express-session'; -import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; -import connectDB from './config/db.js'; -import Admin from './models/admin.js'; -import Category from './models/categories.js'; -import Item from './models/items.js'; -import Message from './models/message.js'; -import Recommendation from './models/recommendations.js'; -import Report from './models/reports.js'; -import Request from './models/requests.js'; -import Transaction from './models/transaction.js'; -import UserModel from './models/users.js'; +import express from "express"; +import path from "path"; +import { fileURLToPath } from "url"; +import passport from "passport"; +import session from "express-session"; +import { Strategy as GoogleStrategy } from "passport-google-oauth20"; +import connectDB from "./config/db.js"; import dotenv from "dotenv"; -dotenv.config(); +import User from "./models/users.js"; +import adminRoutes from "./routes/adminroutes.js"; +import donationRoutes from "./routes/donationroutes.js"; +import cookieParser from 'cookie-parser'; +import itemRoutes from "./routes/itemroutes.js"; +import homeRoutes from "./routes/homeRoutes.js"; + +dotenv.config(); const app = express(); const port = process.env.PORT || 5000; - const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -app.use(express.json()); +// Define uploadDir BEFORE using it-images +const uploadDir = path.join(__dirname, '..','uploads'); +//Asiya +app.use((req,res,next)=>{ +res.locals.currentUser=req.user ||null; +next(); +}) + +app.use(express.json()); +app.use(express.urlencoded({ extended: true })); +app.use("/uploads", express.static(uploadDir)); app.use(session({ - secret: "secret", - resave: false, - saveUninitialized: true + secret: "secret", + resave: false, + saveUninitialized: true, + cookie: { + httpOnly: true, // prevents client-side JS from reading cookie + secure: false, // must be false on localhost (no HTTPS) + sameSite: "lax" // safe for OAuth redirects on localhost + } })); +app.use(cookieParser()); app.use(passport.initialize()); app.use(passport.session()); -passport.use(new GoogleStrategy({ - clientID: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - callbackURL: process.env.GOOGLE_CALLBACK_URL -}, (accessToken, refreshToken, profile, done) => { - return done(null, profile); -})); +// Make `user` available in all EJS views +app.use((req, res, next) => { + res.locals.user = req.user; // accessible in all EJS templates as `user` + next(); +}); -passport.serializeUser((user, done) => done(null, user)); -passport.deserializeUser((user, done) => done(null, user)); +// Serve static frontend +app.use(express.static(path.join(__dirname, '../frontend'))); // Connect to DB connectDB(); -app.set('view engine', 'ejs'); +passport.use(new GoogleStrategy({ + clientID: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + callbackURL: process.env.GOOGLE_CALLBACK_URL, + passReqToCallback: true +}, async (req, accessToken, refreshToken, profile, done) => { + try { + // console.log(profile); + + let user = await User.findOne({ email: profile._json.email }); + + if (!user) { + user = await User.create({ + name: profile._json.name, + displayName: profile._json.given_name, + email: profile._json.email, + profile: profile._json.picture, + setupComplete: false + }); + } + + return done(null, user); + } catch (err) { + return done(err, null); + } +})); + +passport.serializeUser((user, done) => done(null, user.id)); +passport.deserializeUser(async (id, done) => { + try { + const user = await User.findById(id); + done(null, user); + } catch (err) { + done(err, null); + } +}); + +function ensureAuthenticated(req, res, next) { + if (req.isAuthenticated()) return next(); + // Store original URL in a cookie + res.cookie('returnTo', req.originalUrl, { httpOnly: true }); + return res.redirect('/auth/google'); +} // Login app.get("/auth/google", passport.authenticate('google', { scope: ["profile", "email"] })); app.get("/auth/google/callback", passport.authenticate('google', { failureRedirect: "/" }), (req, res) => { - res.redirect('/user-profile') + console.log("callback BEFORE redirect: sessionID =", req.sessionID, "returnTo =", req.session?.returnTo); + + if (!req.user.setupComplete) { + return res.redirect('/initial-login'); + } + + // Read the redirect URL from the cookie + const redirectUrl = req.cookies?.returnTo || '/'; + + // Clear the cookie after using it + res.clearCookie('returnTo'); + + res.redirect(redirectUrl); }); app.get("/logout", (req, res, next) => { - req.logout(err => { - if (err) { return next(err); } - res.redirect("/") - }); + req.logout(err => { + if (err) { return next(err); } + res.redirect("/") + }); }) -// CREATED FOR TRIAL ONLY (DB CONNECTION CHECK) -app.post("/api/admins", async (req, res) => { - try { - const admin = new Admin(req.body); - await admin.save(); - res.status(201).json({ message: "Admin created successfully", admin }); - } catch (err) { - res.status(400).json({ error: err.message }); - } -}); +app.set('view engine', 'ejs'); -app.post("/api/categories", async (req, res) => { - try { - const category = new Category(req.body); - await category.save(); - res.status(201).json({ message: "Category created successfully", category }); - } catch (err) { - res.status(400).json({ error: err.message }); - } -}); +app.set('views', path.join(__dirname, '../frontend/views')); -app.post("/api/items", async (req, res) => { - try { - const newItem = new Item(req.body); - const savedItem = await newItem.save(); - res.status(201).json(savedItem); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } -}); +// Page Routes -app.post("/api/messages", async (req, res) => { - try { - const newMessage = new Message(req.body); - const savedMessage = await newMessage.save(); - res.status(201).json(savedMessage); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/', (req, res) => { + res.render('home'); }); -app.post("/api/recommendations", async (req, res) => { - try { - const newRecommendation = new Recommendation(req.body); - const savedRecommendation = await newRecommendation.save(); - res.status(201).json(savedRecommendation); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/category', ensureAuthenticated, (req, res) => { + res.render('category'); }); -app.post("/api/reports", async (req, res) => { - try { - const newReport = new Report(req.body); - const savedReport = await newReport.save(); - res.status(201).json(savedReport); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/donate', ensureAuthenticated, (req, res) => { + res.render('donate'); }); -app.post("/api/requests", async (req, res) => { - try { - const newRequest = new Request(req.body); - const savedRequest = await newRequest.save(); - res.status(201).json(savedRequest); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/catalog', (req, res) => { + res.render('catalog'); +}) + +app.get("/admin", ensureAuthenticated, (req, res) => { + res.render("admin"); }); -app.post("/api/transactions", async (req, res) => { - try { - const newTransaction = new Transaction(req.body); - const savedTransaction = await newTransaction.save(); - res.status(201).json(savedTransaction); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/user-profile', ensureAuthenticated, (req, res) => { + res.render('user-profile'); }); -app.post("/api/users", async (req, res) => { - try { - const newUser = new UserModel(req.body); - const savedUser = await newUser.save(); - res.status(201).json(savedUser); - } catch (err) { - console.error(err); - res.status(400).json({ message: err.message }); - } +app.get('/initial-login', (req, res) => { + if (!req.isAuthenticated()) return res.redirect('/'); + if (req.user.setupComplete) return res.redirect('/'); + res.render('initial', { user: req.user }); }); -// +// Post routes -app.use(express.static(path.join(__dirname, '../frontend'))); +app.post('/initial-login', async (req, res) => { + if (!req.isAuthenticated()) return res.redirect('/'); -app.get('/', (req, res) => { - res.render('../frontend/views/home.ejs'); -}); + try { + const { displayName, phone, address } = req.body; -app.get('/category', (req, res) => { - res.render('../frontend/views/category.ejs'); -}); + await User.findByIdAndUpdate(req.user._id, { + displayName, + phone, + address, + setupComplete: true + }); -app.get('/donate', (req, res) => { - res.render('../frontend/views/donate.ejs'); -}); + // Read the cookie again, fallback to home + const redirectUrl = req.cookies.returnTo || '/'; + res.clearCookie('returnTo'); -app.get('/reg', (req, res) => { - res.render('../frontend/views/reg.ejs'); + res.redirect(redirectUrl); + } catch (err) { + console.error("Error during initial setup:", err); + res.status(500).send("Something went wrong during setup."); + } }); -app.get('/user-profile', (req, res) => { - res.render('../frontend/views/user-profile.ejs'); -}); +// API Routes +app.use("/api/admin", adminRoutes); +app.use("/api", donationRoutes); +app.use("/api",itemRoutes) +app.use("/", homeRoutes); -// ... (other static routes remain same) - - -// --- Dummy data for statistics --- -let users = [ - { id: 1, name: 'John Doe', email: 'johndoe@example.com' }, - { id: 2, name: 'Alice Smith', email: 'alice@example.com' }, -]; -let items = [ - { id: 1, name: 'Stationary Set', category: 'Stationary', status: 'Available' }, - { id: 2, name: 'Backpack', category: 'Bags', status: 'Used' }, -]; -let transactions = [ - { id: 1, username: 'John Doe', itemName: 'Stationary Set', date: '2025-08-01', amount: 0 }, - { id: 2, username: 'Alice Smith', itemName: 'Backpack', date: '2025-07-15', amount: 0 }, -]; -let reports = [ - { id: 1, username: 'John Doe', issue: 'Item not as described', reportDate: '2025-08-03' }, -]; - -// --- Admin API --- -app.get('/api/admin/statistics', (req, res) => { - res.json({ - totalUsers: users.length, - totalItems: items.length, - totalTransactions: transactions.length, - totalReports: reports.length, - }); -}); +// for images -app.get('/api/admin/users', (req, res) => res.json(users)); +app.use('/uploads', express.static(uploadDir)); -app.delete('/api/admin/users/:id', (req, res) => { - const id = parseInt(req.params.id); - users = users.filter(user => user.id !== id); - res.status(204).send(); +// Error handling middleware +app.use((err, req, res, next) => { + console.error(err.stack); + res.status(500).json({ error: "Something went wrong!" }); }); -app.get('/api/admin/items', (req, res) => res.json(items)); - -app.delete('/api/admin/items/:id', (req, res) => { - const id = parseInt(req.params.id); - items = items.filter(item => item.id !== id); - res.status(204).send(); +// 404 handler +app.use((req, res) => { + res.status(404).json({ error: "Route not found" }); }); -app.get('/api/admin/transactions', (req, res) => res.json(transactions)); - -app.get('/api/admin/reports', (req, res) => res.json(reports)); - -app.post('/api/admin/reports/:id/resolve', (req, res) => { - const id = parseInt(req.params.id); - reports = reports.filter(report => report.id !== id); - res.status(204).send(); -}); // Start server app.listen(port, () => { - console.log(`Server running at http://localhost:${port}`); -}); + console.log(`Server running at http://localhost:${port}`); +}); \ No newline at end of file diff --git a/frontend/css/searchstyle.css b/frontend/css/searchstyle.css new file mode 100644 index 0000000..0d9061a --- /dev/null +++ b/frontend/css/searchstyle.css @@ -0,0 +1,163 @@ +/* ================================================= */ +/* 1. OVERRIDES FOR THE SEARCH RESULTS PAGE LAYOUT */ +/* ================================================= */ + +/* Override the full-screen background image set in style.css for the results page */ + + + + +.search-nav-wrapper { + /* Get rid of the global body background for the whole page */ + display: flex; + align-items: center; + top: 0; + left: 0; + width: 100%; + justify-content: space-between; + min-height: 105px !important; + padding: 1rem 2rem; + position: fixed; + z-index: 1000; + background-color: rgba(0, 128, 128, 0.85); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + /* Optional: Adjust if you need to pull content up */ +} + + + + +#Back-Button { + /* Base styles */ + display: inline-block !important; + padding: 8px 15px !important; + background-color: #f9f9f9 !important; /* Light background */ + color: teal !important; + font-weight: bold !important; + font-size: 16px !important; + border: 2px solid teal !important; + border-radius: 8px !important; + cursor: pointer; + + /* Resetting conflicting styles */ + font-family: inherit !important; + margin: 0 !important; + transition: background-color 0.2s, box-shadow 0.2s; +} + +#Back-Button:hover { + background-color: teal !important; + color: white !important; + box-shadow: 0 4px 8px rgba(0, 128, 128, 0.4) !important; +} + +/* --- 3. FIX BODY PADDING (Since we removed the nav override) --- */ + +/* The main content needs padding below the new 80px tall wrapper */ +.results-page { + /* Ensure there's enough space under the new fixed/tall top bar */ + padding-top: 100px !important; + /* ... rest of your results-page styles ... */ +} +* after button */ +.results-page { + /* Critical: Set a solid background and padding to clear the fixed navbar */ + background-color: white !important; + padding-top: 100px; + padding-bottom: 50px; + padding-left: 20px; + padding-right: 20px; + min-height: calc(100vh - 50px); + width: 100%; + margin: 0 auto; +} + +/* Ensure the body is no longer covered by the full-screen image for this page */ +body { + background-image: none !important; + background-size: auto !important; + background-attachment: scroll !important; + background-color: #f0f7f7 !important; /* Use a light color theme */ +} + +/* Style the unordered list that holds all search results */ +.results-page ul { + list-style: none; + padding: 0; + margin: 0; +} + +/* Style each individual search result item container */ +.results-page li { + display: flex; + gap: 25px; + align-items: flex-start; + padding: 20px; + margin-bottom: 10px; + background-color: #ffffff; /* White background for the item box */ + border-radius: 12px; + box-shadow: 0 4px 10px rgba(0, 128, 128, 0.15); /* Soft shadow with teal hint */ + border: 1px solid #e0f7fa; /* Light border */ +} + +.results-page li p strong { + /* Style for the strong text within the results */ + min-width: 100px; + display: inline-block; + color: teal; +} + +/* ================================================= */ +/* 2. IMAGE GALLERY (CAROUSEL) STYLES */ +/* ================================================= */ + +.image-gallery { + position: relative; + width: 150px; + height: 150px; + flex-shrink: 0; + overflow: hidden; + border-radius: 8px; + border: 2px solid teal; + box-shadow: 0 2px 5px rgba(0,0,0,0.1); +} + +.gallery-image { + width: 100%; + height: 100%; + object-fit: cover; + display: none; /* Controlled by JS */ +} +.gallery-image.active { + display: block !important; +} + +/* Style for the navigation buttons (Arrows) */ +.prev-button, .next-button { + cursor: pointer; + position: absolute; + top: 50%; + transform: translateY(-50%); + padding: 5px 8px; + color: white; + font-weight: bold; + font-size: 16px; + border: none; + background-color: rgba(0,0,0,0.6); + z-index: 10; + border-radius: 4px; + opacity: 0.9; + transition: opacity 0.2s ease; +} + +.next-button { + right: 5px; +} +.prev-button { + left: 5px; +} + +.prev-button:hover, .next-button:hover { + opacity: 1; + background-color: rgba(0, 128, 128, 0.8); /* Teal hover */ +} \ No newline at end of file diff --git a/frontend/css/style.css b/frontend/css/style.css index 0911c1a..7ada247 100644 --- a/frontend/css/style.css +++ b/frontend/css/style.css @@ -45,13 +45,13 @@ min-width: 150px; z-index: 1000; min-height: 220px; - + /* Animation properties */ opacity: 0; visibility: hidden; transform: translateY(-10px); transition: all 0.3s ease; - + } .profile-container.active .dropdown-menu { @@ -64,14 +64,15 @@ display: block; padding: 12px 20px; text-decoration: none; - color: rgba(13, 126, 126, 0.85)!important; + color: rgba(13, 126, 126, 0.85) !important; border-bottom: 1px solid #eee; transition: background-color 0.2s ease; - font-weight:500 ; + font-weight: 500; } .dropdown-item:hover { - background-color:rgba(6, 42, 42, 0.1);; + background-color: rgba(6, 42, 42, 0.1); + ; } .dropdown-item:last-child { @@ -79,419 +80,501 @@ } .dropdown-item.logout { - color:#000000!important;; - font-weight:bold; + color: #000000 !important; + ; + font-weight: bold; } .dropdown-item.logout:hover { background-color: #f0f0f0; } + .welcome-text { - color: white; + color: white; font-size: 20px; font-weight: bold; } /* General styles */ * { - box-sizing: border-box; - font-family: Montserrat; - margin: 0; - padding: 0; + box-sizing: border-box; + font-family: Montserrat; + margin: 0; + padding: 0; } html { - height: 100%; - overflow: hidden; + height: 100%; + overflow: hidden; } body { - margin: 0; - padding: 0; - width: 100%; - height: 100%; - overflow: auto; - overscroll-behavior: none; - background-image: url("../img/bg.jpeg"); - background-size: 100vw 100vh; - background-repeat: no-repeat; - background-attachment: fixed; + margin: 0; + padding: 0; + width: 100%; + height: 100%; + overflow: auto; + overscroll-behavior: none; + background-image: url("../img/bg.jpeg"); + background-size: cover; + background-repeat: no-repeat; + background-color: rgba(0, 128, 128, 0.75); + background-blend-mode: soft-light; + background-attachment: fixed; } .container { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 3em; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 3em; } nav { - display: flex; - align-items: center; - top: 0; - left: 0; - width: 100%; - justify-content: space-between; - padding: 1rem 2rem; - position: fixed; - z-index: 1000; - background-color: rgba(0, 128, 128, 0.85); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + display: flex; + align-items: center; + top: 0; + left: 0; + width: 100%; + justify-content: space-between; + padding: 1rem 2rem; + position: fixed; + z-index: 1000; + background-color: rgba(0, 128, 128, 0.85); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); } #logo { - display: flex; - width: 75px; - height: 75px; - background-color: azure; - border-radius: 10px; - align-items: center; - justify-content: center; + display: flex; + width: 75px; + height: 75px; + background-color: azure; + border-radius: 10px; + align-items: center; + justify-content: center; } #links { - display: flex; - flex-direction: row; - gap: 20px; - margin-right: 10px; - align-items: center; + display: flex; + flex-direction: row; + gap: 20px; + margin-right: 10px; + align-items: center; } -#links a, #links button { - font-family: Montserrat; - color: azure; - font-size: 20px; - font-weight: bold; - text-decoration: none; +#links a, +#links button { + font-family: Montserrat; + color: azure; + font-size: 20px; + font-weight: bold; + text-decoration: none; } #links a:hover { - color: white; + color: white; } #links #login { - color: teal; - height: fit-content; - width: fit-content; - padding: 0.5em; - background-color: azure; - border-radius: 0.5rem; + color: teal; + height: fit-content; + width: fit-content; + padding: 0.5em; + background-color: azure; + border-radius: 0.5rem; } #links #login:hover { - color: rgb(0, 100, 100); - background-color: white; + color: rgb(0, 100, 100); + background-color: white; } #donate { - display: flex; - flex-direction: column; - gap: 20px; - top: 0; - left: 0; - height: 80vh; - width: 100%; - background-color: rgba(0, 128, 128, 0.75); - box-sizing: border-box; - align-items: center; - justify-content: center; + display: flex; + flex-direction: column; + gap: 20px; + top: 0; + left: 0; + height: 80vh; + width: 100%; + /* background-color: rgba(0, 128, 128, 0.75); */ + box-sizing: border-box; + align-items: center; + justify-content: center; } #donate span { - font-family: Homemade Apple; - color: azure; - font-weight: bold; - font-size: 48px; + font-family: Homemade Apple; + color: azure; + font-weight: bold; + font-size: 48px; } #donate-btn { - color: teal; - border-radius: 1rem; - border-width: 0; - padding: 0.5em; - font-size: 32px; - font-family: Montserrat; - font-weight: bold; - background-color: azure; + color: teal; + border-radius: 1rem; + border-width: 0; + padding: 0.5em; + font-size: 32px; + font-family: Montserrat; + font-weight: bold; + background-color: azure; } #donate-btn:hover { - color: rgb(0, 100, 100); - background-color: white; + color: rgb(0, 100, 100); + background-color: white; } #small-catalog { - display: flex; - flex-direction: column; - align-items: center; - padding: 30px; - gap: 20px; - width: 100vw; - height: fit-content; - background-color: azure; + display: flex; + flex-direction: column; + align-items: center; + padding: 30px; + gap: 20px; + width: 100vw; + height: fit-content; + background-color: azure; } #small-catalog span { - margin-top: 1em; - margin-bottom: 0.5em; - color: teal; - font-weight: bold; - font-size: 40px; + margin-top: 1em; + margin-bottom: 0.5em; + color: teal; + font-weight: bold; + font-size: 40px; } #listings { - height: 100%; - width: 100%; - padding: 1em; + height: 100%; + width: 100%; + padding: 1em; } #list { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); - gap: 1rem; - justify-content: center; - list-style: none; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1rem; + justify-content: center; + list-style: none; + justify-items: center; } .item { - display: flex; - flex-direction: column; - background-color: white; - height: fit-content; - width: auto; - max-width: 300px; - padding: 1rem; - border-radius: 1rem; - border: 3px solid teal; - gap: 0.3em; + display: flex; + flex-direction: column; + background-color: white; + height: fit-content; + width: auto; + max-width: 300px; + padding: 1rem; + border-radius: 1rem; + border: 3px solid teal; + gap: 0.3em; } .item img { - aspect-ratio: 1 / 1; - overflow: hidden; - min-width: 100px; - border-radius: 0.7rem; - border: 2px solid teal + aspect-ratio: 1 / 1; + overflow: hidden; + min-width: 100px; + border-radius: 0.7rem; + border: 2px solid teal } .item.focused { - box-shadow: 0 0 20px rgba(0, 128, 128, 0.7); - transform: scale(1.05); - transition: all 0.3s ease; - z-index: 10; + box-shadow: 0 0 20px rgba(0, 128, 128, 0.7); + transform: scale(1.05); + transition: all 0.3s ease; + z-index: 10; } #small-catalog .item .item-name, #small-catalog .item .item-desc { - font-weight: 600; - overflow: hidden; - margin: 0; + font-weight: 600; + overflow: hidden; + margin: 0; } #small-catalog .item .item-name { - color: teal; - font-size: 24px; + color: teal; + font-size: 24px; } #small-catalog .item .item-desc { - color: rgb(0, 93, 93); - font-size: 14px; + color: rgb(0, 93, 93); + font-size: 14px; } footer { - background-color: teal; - color: azure; - padding: 3em; - padding-top: 2em; + background-color: teal; + color: azure; + padding: 3em; + padding-top: 2em; +} + +/* Footer Base */ +.footer { + background-color: teal; + color: #fff; + padding: 40px 20px 20px; + font-family: sans-serif; +} + +.footer-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 30px; + max-width: 1200px; + margin: auto; +} + +.footer h3, +.footer h4 { + margin-bottom: 15px; + color: #fff; +} + +.footer p, +.footer li, +.footer a { + color: #ccc; + font-size: 14px; + text-decoration: none; +} + +.footer a:hover { + color: #1abc9c; +} + +.footer-links ul { + list-style: none; + padding: 0; +} + +.footer-links li { + margin-bottom: 10px; +} + +.footer-social .social-icons { + display: flex; + gap: 10px; +} + +.footer-social .social { + font-size: 20px; + display: inline-block; + padding: 8px; + background-color: teal; + border-radius: 50%; + transition: 0.3s; +} + +.footer-social .social:hover { + background-color: #1abc9c; +} + +.footer-bottom { + text-align: center; + margin-top: 30px; + border-top: 1px solid #444; + padding-top: 15px; + font-size: 13px; + color: #888; +} + +/* Responsive padding */ +@media(max-width: 600px) { + .footer { + padding: 30px 10px 15px; + } } #userprof { - padding-top: 150px; - padding-bottom: 75px; - justify-content: center; - background-color: rgba(0, 128, 128, 0.5); + padding-top: 150px; + padding-bottom: 75px; + justify-content: center; + background-color: rgba(0, 128, 128, 0.5); } .profile-section { - background: white; - padding: 24px; - border-radius: 12px; - width: 700px; - box-shadow: 0 2px 8px rgba(0, 128, 128, 0.10); - border: 2px solid teal; + background: white; + padding: 24px; + border-radius: 12px; + width: 700px; + box-shadow: 0 2px 8px rgba(0, 128, 128, 0.10); + border: 2px solid teal; } .profile-section h2 { - color: teal; - margin-bottom: 18px; - font-weight: bold; + color: teal; + margin-bottom: 18px; + font-weight: bold; } .profile-basic { - display: flex; - align-items: center; - gap: 24px; + display: flex; + align-items: center; + gap: 24px; } .profile-basic .avatar { - width: 80px; - height: 80px; - border-radius: 50%; - object-fit: cover; - border: 2px solid teal; + width: 80px; + height: 80px; + border-radius: 50%; + object-fit: cover; + border: 2px solid teal; } .profile-basic div>div { - color: teal; + color: teal; } .profile-basic button { - background: teal; - color: white; - border: none; - border-radius: 6px; - padding: 8px 16px; - font-weight: bold; - margin-top: 12px; - cursor: pointer; + background: teal; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: bold; + margin-top: 12px; + cursor: pointer; } .profile-basic button:hover { - background: #008080cc; + background: #008080cc; } .profile-stats { - display: flex; - gap: 40px; - font-size: 1.1em; - margin-top: 10px; + display: flex; + gap: 40px; + font-size: 1.1em; + margin-top: 10px; } .profile-stats div { - color: teal; + color: teal; } .listings { - display: flex; - flex-direction: column; - gap: 18px; + display: flex; + flex-direction: column; + gap: 18px; } .listing-item { - display: flex; - gap: 18px; - align-items: center; - background: #f9f9f9; - padding: 16px; - border-radius: 8px; - border: 1.5px solid teal; + display: flex; + gap: 18px; + align-items: center; + background: #f9f9f9; + padding: 16px; + border-radius: 8px; + border: 1.5px solid teal; } .listing-item img { - width: 60px; - height: 60px; - object-fit: cover; - border-radius: 8px; - border: 2px solid teal; + width: 60px; + height: 60px; + object-fit: cover; + border-radius: 8px; + border: 2px solid teal; } .listing-item button { - background: teal; - color: white; - border: none; - border-radius: 6px; - padding: 6px 14px; - font-weight: bold; - margin-right: 8px; - margin-top: 8px; - cursor: pointer; + background: teal; + color: white; + border: none; + border-radius: 6px; + padding: 6px 14px; + font-weight: bold; + margin-right: 8px; + margin-top: 8px; + cursor: pointer; } .listing-item button:hover { - background: #008080cc; + background: #008080cc; } .donation-history { - width: 100%; - border-collapse: collapse; - margin-top: 12px; + width: 100%; + border-collapse: collapse; + margin-top: 12px; } .donation-history th, .donation-history td { - color: teal; - border: 1px solid teal; - padding: 8px 12px; - text-align: left; + color: teal; + border: 1px solid teal; + padding: 8px 12px; + text-align: left; } .donation-history th { - background: #e0f7fa; + background: #e0f7fa; } .feedback-list { - display: flex; - flex-direction: column; - gap: 14px; - margin-top: 10px; + display: flex; + flex-direction: column; + gap: 14px; + margin-top: 10px; } .feedback-item { - background: #f9f9f9; - padding: 12px; - border-radius: 8px; - border: 1.5px solid teal; + background: #f9f9f9; + padding: 12px; + border-radius: 8px; + border: 1.5px solid teal; } .settings-list button:hover { - background: #008080cc; - cursor: pointer; - font-weight: bold; - padding: 8px 16px; - border-radius: 6px; - border: none; - color: white; - background: teal; + background: #008080cc; + cursor: pointer; + font-weight: bold; + padding: 8px 16px; + border-radius: 6px; + border: none; + color: white; + background: teal; } .settings-list button { - color: teal; + color: teal; } .feedback-item strong, .stars { - color: teal; + color: teal; } .feedback-item strong, .stars { - color: teal; + color: teal; } .stars { - color: #FFD700; - font-size: 1.2em; + color: #FFD700; + font-size: 1.2em; } .settings-list { - display: flex; - gap: 16px; - margin-top: 10px; + display: flex; + gap: 16px; + margin-top: 10px; } .settings-list button { - background: teal; - color: white; - border: none; - border-radius: 6px; - padding: 8px 16px; - font-weight: bold; - cursor: pointer; + background: teal; + color: white; + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: bold; + cursor: pointer; } .settings-list button:hover { - background: #008080cc; -} - + background: #008080cc; +} \ No newline at end of file diff --git a/frontend/img/logo.png b/frontend/img/logo.png new file mode 100644 index 0000000..5d26b93 Binary files /dev/null and b/frontend/img/logo.png differ diff --git a/frontend/js/admin.js b/frontend/js/admin.js index da9fd2f..dc63999 100644 --- a/frontend/js/admin.js +++ b/frontend/js/admin.js @@ -1,22 +1,26 @@ -document.addEventListener('DOMContentLoaded', () => { - fetch('/api/admin/statistics') - .then(res => res.json()) - .then(data => { - document.getElementById('totalUsers').textContent = data.totalUsers; - document.getElementById('totalItems').textContent = data.totalItems; - document.getElementById('totalTransactions').textContent = data.totalTransactions; - document.getElementById('totalReports').textContent = data.totalReports; +// What ever is in this is temporary only for testing purposes +// This file will be modified once the admin dashboard is fully functional +// This file is just to test if the frontend can fetch data from the backend +document.addEventListener("DOMContentLoaded", () => { + fetch("/api/admin/statistics") + .then((res) => res.json()) + .then((data) => { + document.getElementById("totalUsers").textContent = data.totalUsers; + document.getElementById("totalItems").textContent = data.totalItems; + document.getElementById("totalTransactions").textContent = + data.totalTransactions; + document.getElementById("totalReports").textContent = data.totalReports; }); - fetch('/api/admin/users') - .then(res => res.json()) - .then(users => { - const tbody = document.getElementById('userTableBody'); - tbody.innerHTML = ''; - users.forEach(user => { - const tr = document.createElement('tr'); + fetch("/api/admin/users") + .then((res) => res.json()) + .then((users) => { + const tbody = document.getElementById("userTableBody"); + tbody.innerHTML = ""; + users.forEach((user) => { + const tr = document.createElement("tr"); tr.innerHTML = ` - ${user.id} + ${user._id} ${user.name} ${user.email} @@ -24,26 +28,27 @@ document.addEventListener('DOMContentLoaded', () => { `; tbody.appendChild(tr); }); - tbody.addEventListener('click', e => { - if (e.target.classList.contains('delete-user-btn')) { + tbody.addEventListener("click", (e) => { + if (e.target.classList.contains("delete-user-btn")) { const id = e.target.dataset.id; - fetch('/api/admin/users/' + id, { method: 'DELETE' }) - .then(() => e.target.closest('tr').remove()); + fetch("/api/admin/users/" + id, { method: "DELETE" }).then(() => + e.target.closest("tr").remove() + ); } }); }); - fetch('/api/admin/items') - .then(res => res.json()) - .then(items => { - const tbody = document.getElementById('itemTableBody'); - tbody.innerHTML = ''; - items.forEach(item => { - const tr = document.createElement('tr'); + fetch("/api/admin/items") + .then((res) => res.json()) + .then((items) => { + const tbody = document.getElementById("itemTableBody"); + tbody.innerHTML = ""; + items.forEach((item) => { + const tr = document.createElement("tr"); tr.innerHTML = ` - ${item.id} + ${item._id} ${item.name} - ${item.category} + ${item.categoryId} ${item.status} @@ -51,54 +56,56 @@ document.addEventListener('DOMContentLoaded', () => { `; tbody.appendChild(tr); }); - tbody.addEventListener('click', e => { - if (e.target.classList.contains('delete-item-btn')) { + tbody.addEventListener("click", (e) => { + if (e.target.classList.contains("delete-item-btn")) { const id = e.target.dataset.id; - fetch('/api/admin/items/' + id, { method: 'DELETE' }) - .then(() => e.target.closest('tr').remove()); + fetch("/api/admin/items/" + id, { method: "DELETE" }).then(() => + e.target.closest("tr").remove() + ); } }); }); - fetch('/api/admin/transactions') - .then(res => res.json()) - .then(list => { - const tbody = document.getElementById('transactionTableBody'); - tbody.innerHTML = ''; - list.forEach(tx => { - const tr = document.createElement('tr'); + fetch("/api/admin/transactions") + .then((res) => res.json()) + .then((list) => { + const tbody = document.getElementById("transactionTableBody"); + tbody.innerHTML = ""; + list.forEach((tx) => { + const tr = document.createElement("tr"); tr.innerHTML = ` - ${tx.id} - ${tx.username} - ${tx.itemName} - ${tx.date} + ${tx._id} + ${tx.donorid} + ${tx.itemId} + ${tx.timestamp} ${tx.amount}`; tbody.appendChild(tr); }); }); - fetch('/api/admin/reports') - .then(res => res.json()) - .then(reports => { - const tbody = document.getElementById('reportTableBody'); - tbody.innerHTML = ''; - reports.forEach(r => { - const tr = document.createElement('tr'); + fetch("/api/admin/reports") + .then((res) => res.json()) + .then((reports) => { + const tbody = document.getElementById("reportTableBody"); + tbody.innerHTML = ""; + reports.forEach((r) => { + const tr = document.createElement("tr"); tr.innerHTML = ` - ${r.id} - ${r.username} - ${r.issue} - ${r.reportDate} + ${r._id} + ${r.reportedBy} + ${r.description} + ${r.createdAt} `; tbody.appendChild(tr); }); - tbody.addEventListener('click', e => { - if (e.target.classList.contains('resolve-report-btn')) { + tbody.addEventListener("click", (e) => { + if (e.target.classList.contains("resolve-report-btn")) { const id = e.target.dataset.id; - fetch('/api/admin/reports/' + id + '/resolve', { method: 'POST' }) - .then(() => e.target.closest('tr').remove()); + fetch("/api/admin/reports/" + id + "/resolve", { + method: "POST", + }).then(() => e.target.closest("tr").remove()); } }); }); diff --git a/frontend/js/script.js b/frontend/js/script.js index 5405193..1b4ddc4 100644 --- a/frontend/js/script.js +++ b/frontend/js/script.js @@ -1,125 +1,92 @@ document.addEventListener("DOMContentLoaded", function () { - - const items = document.querySelectorAll('.item'); - - items.forEach(item => { - item.addEventListener('click', function () { - items.forEach(i => i.classList.remove('focused')); - item.classList.add('focused'); - item.scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - }); + const items = document.querySelectorAll(".item"); + + items.forEach((item) => { + item.addEventListener("click", function () { + items.forEach((i) => i.classList.remove("focused")); + item.classList.add("focused"); + item.scrollIntoView({ + behavior: "smooth", + block: "center", + inline: "center", + }); }); - - document.addEventListener('click', function (e) { - const isItem = e.target.closest('.item'); - if (!isItem) { - document.querySelectorAll('.item.focused').forEach(el => el.classList.remove('focused')); - } - }); - - - + }); + + document.addEventListener("click", function (e) { + const isItem = e.target.closest(".item"); + if (!isItem) { + document + .querySelectorAll(".item.focused") + .forEach((el) => el.classList.remove("focused")); + } + }); }); - // //Asiya -// const loginBtn = document.getElementById('login'); -// const profileDropdown = document.getElementById('profile-dropdown'); -// const profileBtn = document.getElementById('profile-btn'); -// const logoutBtn = document.getElementById('logout-btn'); - -// // State -// let isLoggedIn = false; -// let isDropdownOpen = false; - -// document.addEventListener('DOMContentLoaded', function() { -// setupEventListeners(); -// }); - -// function setupEventListeners() { -// loginBtn.addEventListener('click', function(e) { -// e.preventDefault(); -// handleLogin(); -// }); - -// profileBtn.addEventListener('click', function(e) { -// e.preventDefault(); -// toggleDropdown(); -// }); - -// logoutBtn.addEventListener('click', function(e) { -// e.preventDefault(); -// handleLogout(); -// }); - -// document.addEventListener('click', handleOutsideClick); - -// profileDropdown.addEventListener('click', function(e) { -// e.stopPropagation(); -// }); - -// const dropdownItems = document.querySelectorAll('.dropdown-item:not(.logout)'); -// dropdownItems.forEach(item => { -// item.addEventListener('click', function(e) { -// e.preventDefault(); -// closeDropdown(); - -// }); -// }); -// } +document.addEventListener('DOMContentLoaded', function() { + const profileDropdown = document.getElementById('profile-dropdown'); + const profileBtn = document.getElementById('profile-btn'); + const logoutBtn = document.getElementById('logout-btn'); -// function handleLogin() { -// isLoggedIn = true; -// updateUIState(); -// } + if (!profileDropdown) return; // user is not logged in, no dropdown -// function handleLogout() { -// isLoggedIn = false; -// isDropdownOpen = false; -// updateUIState(); -// } + let isDropdownOpen = false; -// function toggleDropdown() { -// isDropdownOpen = !isDropdownOpen; -// updateDropdownState(); -// } - -// function closeDropdown() { -// isDropdownOpen = false; -// updateDropdownState(); -// } + // Toggle dropdown + profileBtn.addEventListener('click', function(e) { + e.preventDefault(); + isDropdownOpen = !isDropdownOpen; + updateDropdownState(); + }); -// function handleOutsideClick(e) { -// if (isDropdownOpen && !profileDropdown.contains(e.target)) { -// closeDropdown(); -// } -// } + // Stop propagation when clicking inside dropdown + profileDropdown.addEventListener('click', function(e) { + e.stopPropagation(); + }); -// function updateUIState() { -// if (isLoggedIn) { -// loginBtn.style.display = 'none'; -// profileDropdown.style.display = 'inline-block'; -// } else { -// profileDropdown.style.display = 'none'; -// loginBtn.style.display = 'inline-block'; -// } -// } + // Close dropdown when clicking outside + document.addEventListener('click', function(e) { + if (isDropdownOpen && !profileDropdown.contains(e.target)) { + closeDropdown(); + } + }); -// function updateDropdownState() { -// if (isDropdownOpen) { -// profileDropdown.classList.add('active'); -// } else { -// profileDropdown.classList.remove('active'); -// } -// } + // Close dropdown with Escape key + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape' && isDropdownOpen) { + closeDropdown(); + } + }); -// document.addEventListener('keydown', function(e) { -// if (e.key === 'Escape' && isDropdownOpen) { -// closeDropdown(); -// } -// }); + // Dropdown item clicks (except logout) + const dropdownItems = document.querySelectorAll('.dropdown-item:not(.logout)'); + dropdownItems.forEach(item => { + item.addEventListener('click', function(e) { + e.preventDefault(); + closeDropdown(); + window.location.href = item.getAttribute('href'); + }); + }); + // Logout button redirects + if (logoutBtn) { + logoutBtn.addEventListener('click', function(e) { + e.preventDefault(); + window.location.href = '/logout'; + }); + } + + function closeDropdown() { + isDropdownOpen = false; + updateDropdownState(); + } + + function updateDropdownState() { + if (isDropdownOpen) { + profileDropdown.classList.add('active'); + } else { + profileDropdown.classList.remove('active'); + } + } +}); diff --git a/frontend/views/admin.ejs b/frontend/views/admin.ejs index 02a7d28..44453d2 100644 --- a/frontend/views/admin.ejs +++ b/frontend/views/admin.ejs @@ -1,116 +1,116 @@ + - - - Admin Dashboard | reGive - - - + + + Admin Dashboard | reGive + + + - - -
-

Admin Dashboard

+ + <%- include('partials/navbar') %> -
-

Statistics

-
-
-
0
-
Total Users
-
-
-
0
-
Total Items
-
-
-
0
-
Total Transactions
-
-
-
0
-
Reports Received
-
-
-
+
+

Admin Dashboard

-
-

User Management

-
- - - - - - - -
IDNameEmailActions
-
-
+
+

Statistics

+
+
+
0
+
Total Users
+
+
+
0
+
Total Items
+
+
+
0
+
Total Transactions
+
+
+
0
+
Reports Received
+
+
+
-
-

Item Management

-
- - - - - - - -
IDNameCategoryStatusActions
-
-
+
+

User Management

+
+ + + + + + + + + + +
IDNameEmailActions
+
+
-
-

Transaction Management

-
- - - - - - - -
IDUserItemDateAmount
-
-
+
+

Item Management

+
+ + + + + + + + + + + +
IDNameCategoryStatusActions
+
+
-
-

Report Management

-
- - - - - - - -
IDUserIssueDate ReportedActions
-
-
-
+
+

Transaction Management

+
+ + + + + + + + + + + +
IDUserItemDateAmount
+
+
- + + <%- include('partials/footer') %> - + + \ No newline at end of file diff --git a/frontend/views/catalog.ejs b/frontend/views/catalog.ejs new file mode 100644 index 0000000..140c3a9 --- /dev/null +++ b/frontend/views/catalog.ejs @@ -0,0 +1,130 @@ + + + + + + + + + Catalog | reGive + + + + + + <%- include('partials/navbar') %> + +
+

Available Items

+ +
+ +
+ Item Image +
+
Winter Jacket
+
Warm and cozy jacket in good condition.
+
Donated by: Alex
+
+
+
+
+ + <%- include('partials/footer') %> + + + \ No newline at end of file diff --git a/frontend/views/category.ejs b/frontend/views/category.ejs index b3bfdbd..5fab50f 100644 --- a/frontend/views/category.ejs +++ b/frontend/views/category.ejs @@ -1,98 +1,15 @@ + - - + + Select Category | reGive + + - + <%- include('partials/navbar') %>

What would you like to donate?

Choose a category to get started with your donation

-
- -
Books & Study Materials
-
Textbooks, notebooks, reference materials, and other educational resources
+
+ Textbooks, notebooks, reference materials, and other educational + resources +
5 subcategories
-
Stationery
-
Pens, pencils, geometry tools, drawing materials, and office supplies
+
+ Pens, pencils, geometry tools, drawing materials, and office + supplies +
10 subcategories
-
Electronics
-
Calculators, kettles, chargers, and other electronic devices
+
+ Calculators, kettles, chargers, and other electronic devices +
6 subcategories
-
Clothing
-
Uniforms, lab coats, casual wear, and accessories
+
+ Uniforms, lab coats, casual wear, and accessories +
4 subcategories
-
Kitchen Items
-
Plates,Glass,Spoon,knife,jars and others
+
+ Plates,Glass,Spoon,knife,jars and others +
6 subcategories
-
Sports & Recreation
-
Sports equipment, games, fitness gear, and recreational items
+
+ Sports equipment, games, fitness gear, and recreational items +
6 subcategories
-

Select Book Type

@@ -447,7 +362,9 @@
Record Books
-
Handwritten Notebooks(Lecture Notes)
+
+ Handwritten Notebooks(Lecture Notes) +
Fresh Notebooks
@@ -460,9 +377,8 @@
-
-

Select Stationery Type

+

Select Stationery Type

Geometry Box
@@ -496,7 +412,6 @@
-

🔌 Select Electronics Type

@@ -520,7 +435,6 @@
-

Select Clothing Type

@@ -538,32 +452,29 @@
- -
-

Select Kitchen Item Type

-
-
-
Plates
-
-
-
Glass
-
-
-
Spoon
-
-
-
Knife
-
-
-
Jars
-
-
-
Other Kitchen Items
-
-
-
- - +
+

Select Kitchen Item Type

+
+
+
Plates
+
+
+
Glass
+
+
+
Spoon
+
+
+
Knife
+
+
+
Jars
+
+
+
Other Kitchen Items
+
+
+

Select Sports Type

@@ -587,133 +498,150 @@
-

Your Selection

Category:

-

Subcategory:

+

+ Subcategory: +

-
- - + +
+ \ No newline at end of file diff --git a/frontend/views/donate.ejs b/frontend/views/donate.ejs index a49c37e..b2e3a05 100644 --- a/frontend/views/donate.ejs +++ b/frontend/views/donate.ejs @@ -2,98 +2,15 @@ - - + + Donate a Resource | reGive + + + + + <%- include('partials/navbar') %> + +
+
+

Complete Your Profile

+
+ + + + + +
+
+
+ + <%- include('partials/footer') %> + + diff --git a/frontend/views/itemListing.ejs b/frontend/views/itemListing.ejs new file mode 100644 index 0000000..2cb85cf --- /dev/null +++ b/frontend/views/itemListing.ejs @@ -0,0 +1,148 @@ + + + + + + Search Results | reGive + + + + + +
+ +
+ +
+ <% if (items.length===0) { %> +

No results found.

+ <% } else { %> + + <% } %> +
+ + + + + \ No newline at end of file diff --git a/frontend/views/partials/footer.ejs b/frontend/views/partials/footer.ejs new file mode 100644 index 0000000..86e03f0 --- /dev/null +++ b/frontend/views/partials/footer.ejs @@ -0,0 +1,29 @@ + + + \ No newline at end of file diff --git a/frontend/views/partials/navbar.ejs b/frontend/views/partials/navbar.ejs new file mode 100644 index 0000000..ef692d2 --- /dev/null +++ b/frontend/views/partials/navbar.ejs @@ -0,0 +1,36 @@ + + + + diff --git a/frontend/views/reg.ejs b/frontend/views/reg.ejs deleted file mode 100644 index e2aad9e..0000000 --- a/frontend/views/reg.ejs +++ /dev/null @@ -1,134 +0,0 @@ - - - - - - Register | reGive - - - - - - - - - - - -
-

Create an Account

-
- - - - - - -

- -
-
- - - - - diff --git a/frontend/views/user-profile.ejs b/frontend/views/user-profile.ejs index 339f559..353577e 100644 --- a/frontend/views/user-profile.ejs +++ b/frontend/views/user-profile.ejs @@ -1,227 +1,250 @@ - - - + + + + User Profile | reGive - + - - - - + -
-
-

User Profile

-
- Profile Picture -
- -
-
Name: Harry P
-
Email: 230000@tkmce.ac.in
-
Phone: +91-9876543210
-
Location: Kollam, Kerala
-
Joined Date: January 2025
- - -
+ + <%- include('partials/navbar') %> - - - -
-
Name: Harry P
-
Email: 230000@tkmce.ac.in
-
Phone: +91-9876543210
-
Location: Kollam, Kerala
-
Joined Date: January 2025
- - +
+
+

User Profile

+
+ Profile Picture +
+
+
+ Name: Harry P +
+
+ Email: + 230000@tkmce.ac.in +
+
+ Phone: + +91-9876543210 +
+
+ Location: + Kollam, Kerala +
+
Joined Date: January 2025
+ + +
+ + + + +
+
+ Name: Harry P +
+
+ Email: + 230000@tkmce.ac.in +
+
+ Phone: + +91-9876543210 +
+
+ Location: + Kollam, Kerala +
+
Joined Date: January 2025
+ + +
+ + + +
- - - -
-
-
-
-

Rewards & Stats

-
-
Total Reward Points: 120
-
Items Donated: 12
-
-
-
-

Active Listings

-
-
- Item Image -
-
Name: Stationary Set
-
Category: Stationary
-
Condition: New
-
Date Posted: 01 Aug 2025
- - +
+
+

Rewards & Stats

+
+
Total Reward Points: 120
+
Items Donated: 12
-
-
- Item Image -
-
Name: Backpack
-
Category: Bags
-
Condition: Used
-
Date Posted: 15 Jul 2025
- - + +
+

Active Listings

+
+
+ Item Image +
+
Name: Stationary Set
+
Category: Stationary
+
Condition: New
+
Date Posted: 01 Aug 2025
+ + +
+
+
+ Item Image +
+
Name: Backpack
+
Category: Bags
+
Condition: Used
+
Date Posted: 15 Jul 2025
+ + +
+
-
-
- -
-

Donation History

- - - - - - - - - - - - - - - - - - - - - - - -
ItemCategoryDate DonatedPoints Earned
Stationary SetStationary02 Aug 202510
BackpackBags16 Jul 202515
-
- - - - + +
+

Donation History

+ + + + + + + + + + + + + + + + + + + + + + + +
ItemCategoryDate DonatedPoints Earned
Stationary SetStationary02 Aug 202510
BackpackBags16 Jul 202515
+
- - + <%- include('partials/footer') %> + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6711f28..94c67d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,17 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "cookie-parser": "^1.4.7", "dotenv": "^17.2.2", "ejs": "^3.1.10", "express": "^5.1.0", "express-session": "^1.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.18.1", + "multer": "^2.0.2", "passport": "^0.7.0", - "passport-google-oauth20": "^2.0.0" + "passport-google-oauth20": "^2.0.0", + "path": "^0.12.7" }, "devDependencies": { "nodemon": "^3.1.10" @@ -49,7 +53,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -58,6 +61,25 @@ "node": ">= 0.6" } }, + "node_modules/accepts/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -72,10 +94,17 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT" }, "node_modules/balanced-match": { "version": "1.0.2", @@ -87,6 +116,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -108,7 +138,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "license": "MIT", "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -124,15 +153,53 @@ "node": ">=18" } }, + "node_modules/body-parser/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/body-parser/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -157,11 +224,33 @@ "node": ">=16.20.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -170,7 +259,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -183,7 +271,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -227,11 +314,25 @@ "dev": true, "license": "MIT" }, + "node_modules/concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "engines": [ + "node >= 6.0" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", - "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" }, @@ -243,7 +344,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -257,19 +357,35 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/cookie-signature": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", "engines": { "node": ">=6.6.0" } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -308,7 +424,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -318,16 +433,25 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "license": "Apache-2.0", "dependencies": { "jake": "^10.8.5" }, @@ -342,7 +466,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -351,7 +474,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -360,7 +482,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", "engines": { "node": ">= 0.4" } @@ -369,7 +490,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -380,14 +500,12 @@ "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -396,7 +514,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", - "license": "MIT", "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.0", @@ -438,6 +555,7 @@ "version": "1.18.2", "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz", "integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==", + "license": "MIT", "dependencies": { "cookie": "0.7.2", "cookie-signature": "1.0.7", @@ -455,12 +573,14 @@ "node_modules/express-session/node_modules/cookie-signature": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" }, "node_modules/express-session/node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } @@ -468,33 +588,56 @@ "node_modules/express-session/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dependencies": { - "minimatch": "^5.0.1" + "node_modules/express/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "engines": { + "node": ">= 0.8" } }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/express/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", "dependencies": { - "balanced-match": "^1.0.0" + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/express/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dependencies": { - "brace-expansion": "^2.0.1" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">= 0.6" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" } }, "node_modules/fill-range": { @@ -514,7 +657,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", - "license": "MIT", "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -540,7 +682,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -564,7 +705,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -573,7 +713,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -597,7 +736,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -623,7 +761,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -645,7 +782,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -657,7 +793,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -669,7 +804,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", @@ -685,7 +819,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } @@ -694,7 +827,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -773,13 +905,13 @@ "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, "node_modules/jake": { "version": "10.9.4", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "license": "Apache-2.0", "dependencies": { "async": "^3.2.6", "filelist": "^1.0.4", @@ -792,6 +924,48 @@ "node": ">=10" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jwa": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", + "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/kareem": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", @@ -801,22 +975,62 @@ "node": ">=12.0.0" } }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/memory-pager": { @@ -829,7 +1043,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", "engines": { "node": ">=18" }, @@ -838,37 +1051,55 @@ } }, "node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", - "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", - "license": "MIT", + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dependencies": { - "mime-db": "^1.54.0" + "mime-db": "1.52.0" }, "engines": { "node": ">= 0.6" } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=10" + } + }, + "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/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" } }, "node_modules/mongodb": { @@ -928,9 +1159,9 @@ } }, "node_modules/mongoose": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.1.tgz", - "integrity": "sha512-K0RfrUXXufqNRZZjvAGdyjydB91SnbWxlwFYi5t7zN2DxVWFD3c6puia0/7xfBwZm6RCpYOVdYFlRFpoDWiC+w==", + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.18.2.tgz", + "integrity": "sha512-gA6GFlshOHUdNyw9OQTmMLSGzVOPbcbjaSZ1dvR5iMp668N2UUznTuzgTY6V6Q41VtBc4kmL/qqML1RNgXB5Fg==", "license": "MIT", "dependencies": { "bson": "^6.10.4", @@ -976,11 +1207,28 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multer": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", + "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.6.0", + "concat-stream": "^2.0.0", + "mkdirp": "^0.5.6", + "object-assign": "^4.1.1", + "type-is": "^1.6.18", + "xtend": "^4.0.2" + }, + "engines": { + "node": ">= 10.16.0" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", "engines": { "node": ">= 0.6" } @@ -990,6 +1238,7 @@ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": "^3.5.2", "debug": "^4", @@ -1013,6 +1262,30 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/nodemon/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -1026,13 +1299,22 @@ "node_modules/oauth": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", - "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==" + "integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -1044,7 +1326,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", "dependencies": { "ee-first": "1.1.1" }, @@ -1056,6 +1337,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1064,7 +1346,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", "dependencies": { "wrappy": "1" } @@ -1082,6 +1363,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "license": "MIT", "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -1099,6 +1381,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-2.0.0.tgz", "integrity": "sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==", + "license": "MIT", "dependencies": { "passport-oauth2": "1.x.x" }, @@ -1110,6 +1393,7 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/passport-oauth2/-/passport-oauth2-1.8.0.tgz", "integrity": "sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==", + "license": "MIT", "dependencies": { "base64url": "3.x.x", "oauth": "0.10.x", @@ -1133,13 +1417,23 @@ "node": ">= 0.4.0" } }, - "node_modules/path-to-regexp": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", - "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", "license": "MIT", - "engines": { - "node": ">=16" + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/pause": { @@ -1150,7 +1444,8 @@ "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1165,6 +1460,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1198,7 +1502,6 @@ "version": "6.14.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -1213,6 +1516,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "license": "MIT", "engines": { "node": ">= 0.8" } @@ -1221,24 +1525,51 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "license": "MIT", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", - "iconv-lite": "0.6.3", + "iconv-lite": "0.7.0", "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" } }, "node_modules/readdirp": { @@ -1258,7 +1589,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -1293,14 +1623,12 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1313,7 +1641,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", - "license": "MIT", "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", @@ -1331,11 +1658,29 @@ "node": ">= 18" } }, + "node_modules/send/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/serve-static": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", - "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -1349,14 +1694,12 @@ "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -1375,7 +1718,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -1391,7 +1733,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1409,7 +1750,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -1456,11 +1796,27 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1491,7 +1847,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", "engines": { "node": ">=0.6" } @@ -1519,23 +1874,28 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", - "license": "MIT", + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "license": "MIT", "dependencies": { "random-bytes": "~1.0.0" }, @@ -1546,7 +1906,8 @@ "node_modules/uid2": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.4.tgz", - "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==" + "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", + "license": "MIT" }, "node_modules/undefsafe": { "version": "2.0.5", @@ -1559,15 +1920,36 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", "engines": { "node": ">= 0.8" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", "engines": { "node": ">= 0.4.0" } @@ -1606,8 +1988,16 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } } } } diff --git a/package.json b/package.json index cfc2878..79bc606 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,17 @@ "license": "ISC", "description": "", "dependencies": { + "cookie-parser": "^1.4.7", "dotenv": "^17.2.2", "ejs": "^3.1.10", "express": "^5.1.0", "express-session": "^1.18.2", + "jsonwebtoken": "^9.0.2", "mongoose": "^8.18.1", + "multer": "^2.0.2", "passport": "^0.7.0", - "passport-google-oauth20": "^2.0.0" + "passport-google-oauth20": "^2.0.0", + "path": "^0.12.7" }, "devDependencies": { "nodemon": "^3.1.10"