const Submission = require("../models/submission"); const NewsletterSubscription = require("../models/newsletterSubscription"); const SOURCE_LABELS = { home: "Home", request: "Request", contact: "Contact", partnership: "Partnerships", newsletter: "Newsletter", }; const SOURCE_URLS = { home: "/", request: "/request", contact: "/contact", partnership: "/about/partnerships", newsletter: "Footer", }; function cleanString(value) { return String(value || "").trim(); } function pickFirst(payload, keys) { for (const key of keys) { const value = cleanString(payload?.[key]); if (value) return value; } return ""; } function buildName(payload) { const directName = pickFirst(payload, ["name", "fullName", "full_name"]); if (directName) return directName; const firstName = pickFirst(payload, ["firstName", "first_name"]); const lastName = pickFirst(payload, ["lastName", "last_name"]); const combined = [firstName, lastName].filter(Boolean).join(" ").trim(); if (combined) return combined; return pickFirst(payload, ["organization", "organisation", "company"]); } function normalizePayload(body) { const payload = body && typeof body.payload === "object" && body.payload !== null ? body.payload : body || {}; return Object.entries(payload).reduce((acc, [key, value]) => { if (typeof value === "string") { acc[key] = value.trim(); return acc; } acc[key] = value; return acc; }, {}); } function buildListQuery(query) { const mongoQuery = {}; const source = cleanString(query.source); const status = cleanString(query.status); const search = cleanString(query.search); if (source && Submission.SOURCES.includes(source)) { mongoQuery.source = source; } if (status && Submission.STATUSES.includes(status)) { mongoQuery.status = status; } if (query.startDate || query.endDate) { mongoQuery.createdAt = {}; if (query.startDate) { mongoQuery.createdAt.$gte = new Date(query.startDate); } if (query.endDate) { const end = new Date(query.endDate); end.setHours(23, 59, 59, 999); mongoQuery.createdAt.$lte = end; } } if (search) { const regex = new RegExp(search.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i"); mongoQuery.$or = [ { name: regex }, { email: regex }, { phone: regex }, { "payload.firstName": regex }, { "payload.lastName": regex }, { "payload.organization": regex }, { "payload.partnershipType": regex }, { "payload.message": regex }, ]; } return mongoQuery; } exports.index = async (req, res) => { const sources = ["home", "request", "contact", "partnership", "newsletter"]; const requestedTab = cleanString(req.query.tab); const activeSource = sources.includes(requestedTab) ? requestedTab : sources[0]; res.render("admin/submissions/index", { layout: "layouts/main", title: "Submission Management", subtitle: "Review and manage website form submissions", sources, activeSource, sourceLabels: SOURCE_LABELS, sourceUrls: SOURCE_URLS, statuses: Submission.STATUSES, newsletterStatuses: NewsletterSubscription.STATUSES, currentPath: req.path, user: req.session.user, }); }; exports.create = async (req, res) => { try { const source = cleanString(req.body.source); const payload = normalizePayload(req.body); if (!Submission.SOURCES.includes(source)) { return res.status(400).json({ success: false, error: "Invalid source" }); } const name = buildName(payload); const email = pickFirst(payload, ["email", "email_address"]); const phone = pickFirst(payload, ["phone", "phone_number"]); if (source !== "partnership" && !email) { return res.status(400).json({ success: false, error: "Email is required" }); } const submission = await Submission.create({ source, pageUrl: cleanString(req.body.pageUrl) || SOURCE_URLS[source] || "", name, email, phone, payload, ipAddress: req.ip || req.connection?.remoteAddress || "", userAgent: req.get("User-Agent") || "", }); return res.status(201).json({ success: true, message: "Submission received", data: { id: submission._id, source: submission.source, status: submission.status, }, }); } catch (error) { if (error.name === "ValidationError") { const errors = Object.values(error.errors).map((item) => item.message); return res.status(400).json({ success: false, error: errors.join(", ") }); } console.error("submission.create error:", error); return res .status(500) .json({ success: false, error: "Error submitting form" }); } }; exports.list = async (req, res) => { try { const page = Math.max(parseInt(req.query.page, 10) || 1, 1); const limit = Math.min(Math.max(parseInt(req.query.limit, 10) || 20, 1), 100); const skip = (page - 1) * limit; const query = buildListQuery(req.query); const [items, total] = await Promise.all([ Submission.find(query).sort({ createdAt: -1 }).skip(skip).limit(limit).lean(), Submission.countDocuments(query), ]); return res.json({ success: true, data: items, pagination: { page, limit, total, totalPages: Math.ceil(total / limit) || 1, }, }); } catch (error) { console.error("submission.list error:", error); return res .status(500) .json({ success: false, error: "Error loading submissions" }); } }; exports.detail = async (req, res) => { try { const submission = await Submission.findById(req.params.id).lean(); if (!submission) { return res.status(404).json({ success: false, error: "Submission not found" }); } return res.json({ success: true, data: submission }); } catch (error) { console.error("submission.detail error:", error); return res .status(500) .json({ success: false, error: "Error loading submission" }); } }; exports.update = async (req, res) => { try { const update = {}; const status = cleanString(req.body.status); if (status) { if (!Submission.STATUSES.includes(status)) { return res.status(400).json({ success: false, error: "Invalid status" }); } update.status = status; } if (req.body.internalNote !== undefined) { update.internalNote = cleanString(req.body.internalNote); } const submission = await Submission.findByIdAndUpdate(req.params.id, update, { new: true, runValidators: true, }); if (!submission) { return res.status(404).json({ success: false, error: "Submission not found" }); } return res.json({ success: true, data: submission }); } catch (error) { if (error.name === "ValidationError") { const errors = Object.values(error.errors).map((item) => item.message); return res.status(400).json({ success: false, error: errors.join(", ") }); } console.error("submission.update error:", error); return res .status(500) .json({ success: false, error: "Error updating submission" }); } };