Files

158 lines
5.8 KiB
JavaScript
Raw Permalink Normal View History

2026-07-01 01:10:13 +05:45
// ============================================================
// adminController.js — Admin-only operations
// ============================================================
// The admin area has a single purpose: review the customer
// inquiries submitted through the public "Contact Us" form.
// (No user/role management — there is only the admin role.)
// ============================================================
const pool = require('../db');
const { log } = require('../utils/logger');
// ------------------------------------------------------------
// GET /api/admin/stats
// Dashboard headline figures + the distinct country list used to
// populate the table's country filter.
// ------------------------------------------------------------
async function getStats(req, res, next) {
try {
const [total, unread, last7, today, countries] = await Promise.all([
pool.query('SELECT COUNT(*) FROM inquiries'),
pool.query('SELECT COUNT(*) FROM inquiries WHERE is_read = false'),
pool.query("SELECT COUNT(*) FROM inquiries WHERE created_at >= NOW() - INTERVAL '7 days'"),
pool.query('SELECT COUNT(*) FROM inquiries WHERE created_at::date = CURRENT_DATE'),
pool.query("SELECT DISTINCT country FROM inquiries WHERE country IS NOT NULL AND country <> '' ORDER BY country ASC"),
]);
return res.json({
totalInquiries: parseInt(total.rows[0].count, 10),
unreadInquiries: parseInt(unread.rows[0].count, 10),
last7Days: parseInt(last7.rows[0].count, 10),
today: parseInt(today.rows[0].count, 10),
countries: countries.rows.map(r => r.country),
});
} catch (err) {
next(err);
}
}
// ------------------------------------------------------------
// GET /api/admin/inquiries?page=&limit=&search=&country=&status=
// Paginated, searchable, filterable list for the dashboard table.
// ------------------------------------------------------------
async function getInquiries(req, res, next) {
try {
const page = Math.max(1, parseInt(req.query.page, 10) || 1);
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit, 10) || 10));
const offset = (page - 1) * limit;
const { search, country, status } = req.query;
const conditions = [];
const params = [];
if (search) {
params.push(`%${search}%`);
const p = `$${params.length}`;
conditions.push(
`(full_name ILIKE ${p} OR email ILIKE ${p} OR company ILIKE ${p} OR job_title ILIKE ${p} OR job_details ILIKE ${p})`
);
}
if (country) {
params.push(country);
conditions.push(`country = $${params.length}`);
}
if (status === 'read') conditions.push('is_read = true');
if (status === 'unread') conditions.push('is_read = false');
const whereClause = conditions.length ? 'WHERE ' + conditions.join(' AND ') : '';
const countResult = await pool.query(`SELECT COUNT(*) FROM inquiries ${whereClause}`, params);
const totalCount = parseInt(countResult.rows[0].count, 10);
const dataResult = await pool.query(
`SELECT id, full_name, email, phone, company, country, job_title, job_details, is_read, created_at
FROM inquiries
${whereClause}
ORDER BY created_at DESC
LIMIT $${params.length + 1} OFFSET $${params.length + 2}`,
[...params, limit, offset]
);
return res.json({
inquiries: dataResult.rows,
pagination: {
page,
limit,
totalCount,
totalPages: Math.max(1, Math.ceil(totalCount / limit)),
},
});
} catch (err) {
next(err);
}
}
// ------------------------------------------------------------
// GET /api/admin/inquiries/:id — full detail of one inquiry
// Viewing an inquiry marks it as read.
// ------------------------------------------------------------
async function getInquiry(req, res, next) {
try {
const { id } = req.params;
const result = await pool.query('SELECT * FROM inquiries WHERE id = $1', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Inquiry not found.' });
}
// Mark as read on open (idempotent).
await pool.query('UPDATE inquiries SET is_read = true WHERE id = $1 AND is_read = false', [id]);
const inquiry = { ...result.rows[0], is_read: true };
return res.json({ inquiry });
} catch (err) {
next(err);
}
}
// ------------------------------------------------------------
// PATCH /api/admin/inquiries/:id/read body: { is_read }
// ------------------------------------------------------------
async function setInquiryRead(req, res, next) {
try {
const { id } = req.params;
const isRead = req.body.is_read !== false; // default true
const result = await pool.query(
'UPDATE inquiries SET is_read = $1 WHERE id = $2 RETURNING id',
[isRead, id]
);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Inquiry not found.' });
}
return res.json({ message: isRead ? 'Marked as read.' : 'Marked as unread.' });
} catch (err) {
next(err);
}
}
// ------------------------------------------------------------
// DELETE /api/admin/inquiries/:id
// ------------------------------------------------------------
async function deleteInquiry(req, res, next) {
try {
const { id } = req.params;
const result = await pool.query('DELETE FROM inquiries WHERE id = $1 RETURNING email', [id]);
if (result.rows.length === 0) {
return res.status(404).json({ message: 'Inquiry not found.' });
}
await log(req.user.id, 'ADMIN_DELETE_INQUIRY', 'success',
`Admin "${req.user.username}" deleted inquiry from ${result.rows[0].email}`, req);
return res.json({ message: 'Inquiry deleted.' });
} catch (err) {
next(err);
}
}
module.exports = {
getStats,
getInquiries,
getInquiry,
setInquiryRead,
deleteInquiry,
};