fix: add missing email-status GET route, remove old schema validation on email POST
This commit is contained in:
@@ -272,49 +272,62 @@ router.get('/:id/pdf', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─────────────── GET /api/timesheets/:id/email-status ───────────────
|
||||||
|
router.get('/:id/email-status', async (req, res) => {
|
||||||
|
const { id } = req.params;
|
||||||
|
const timesheet = await req.prisma.timesheet.findUnique({ where: { id }, select: { userId: true } });
|
||||||
|
if (!timesheet) return res.status(404).json({ error: 'Not found' });
|
||||||
|
const isAdmin = req.user.role === 'admin' || req.user.role === 'super_admin';
|
||||||
|
if (timesheet.userId !== req.user.id && !isAdmin) return res.status(403).json({ error: 'Access denied' });
|
||||||
|
const limit = checkEmailLimit(id);
|
||||||
|
res.json({ count: limit.count, max: MAX_EMAILS, cooldownMs: limit.cooldownMs, allowed: limit.allowed });
|
||||||
|
});
|
||||||
|
|
||||||
// ─────────────── POST /api/timesheets/:id/email ───────────────
|
// ─────────────── POST /api/timesheets/:id/email ───────────────
|
||||||
router.post('/:id/email', validateBody(emailTimesheetSchema), async (req, res) => {
|
router.post('/:id/email', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { to, subject, message } = req.validated;
|
|
||||||
|
|
||||||
const timesheet = await req.prisma.timesheet.findUnique({
|
const timesheet = await req.prisma.timesheet.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: { user: { select: { id: true, name: true, email: true } } },
|
include: { user: { select: { id: true, name: true, email: true } } },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!timesheet) {
|
if (!timesheet) return res.status(404).json({ error: 'Timesheet not found' });
|
||||||
return res.status(404).json({ error: 'Timesheet not found' });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only owner or admin
|
|
||||||
const isAdmin = req.user.role === 'admin' || req.user.role === 'super_admin';
|
const isAdmin = req.user.role === 'admin' || req.user.role === 'super_admin';
|
||||||
if (timesheet.userId !== req.user.id && !isAdmin) {
|
if (timesheet.userId !== req.user.id && !isAdmin) {
|
||||||
return res.status(403).json({ error: 'Access denied' });
|
return res.status(403).json({ error: 'Access denied' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rate limit check
|
||||||
|
const limit = checkEmailLimit(id);
|
||||||
|
if (!limit.allowed) {
|
||||||
|
const minutesLeft = Math.ceil(limit.cooldownMs / 60000);
|
||||||
|
if (limit.count >= MAX_EMAILS) {
|
||||||
|
return res.status(429).json({ error: `Maximum ${MAX_EMAILS} emails per timesheet reached.`, count: limit.count, max: MAX_EMAILS });
|
||||||
|
}
|
||||||
|
return res.status(429).json({ error: `Please wait ${minutesLeft} minute${minutesLeft !== 1 ? 's' : ''} before sending again.`, cooldownMs: limit.cooldownMs, count: limit.count });
|
||||||
|
}
|
||||||
|
|
||||||
const weekStart = timesheet.weekStart.toISOString().split('T')[0];
|
const weekStart = timesheet.weekStart.toISOString().split('T')[0];
|
||||||
const data = await loadTimesheetData(req.prisma, timesheet.userId, weekStart);
|
const data = await loadTimesheetData(req.prisma, timesheet.userId, weekStart);
|
||||||
|
const pdfBuffer = await generateTimesheetPDF({ userName: data.user.name, weekStart, entries: data.entries, status: timesheet.status });
|
||||||
const pdfBuffer = await generateTimesheetPDF({
|
|
||||||
userName: data.user.name,
|
|
||||||
weekStart,
|
|
||||||
entries: data.entries,
|
|
||||||
status: timesheet.status,
|
|
||||||
});
|
|
||||||
|
|
||||||
const pdfFilename = buildPdfFilename(data.user.name, weekStart);
|
const pdfFilename = buildPdfFilename(data.user.name, weekStart);
|
||||||
|
const adminEmail = process.env.ADMIN_EMAIL || 'Office@CoastalContractingFL.com';
|
||||||
|
|
||||||
await sendTimesheetEmail({
|
await sendTimesheetEmail({
|
||||||
to,
|
to: adminEmail,
|
||||||
subject: subject || `Timesheet – ${data.user.name} – Week of ${weekStart}`,
|
subject: `Timesheet – ${data.user.name} – Week of ${weekStart}`,
|
||||||
message,
|
message: `${data.user.name} has submitted their timesheet for the week of ${weekStart}.`,
|
||||||
pdfBuffer,
|
pdfBuffer,
|
||||||
pdfFilename,
|
pdfFilename,
|
||||||
fromName: data.user.name,
|
fromName: data.user.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ message: `Timesheet emailed to ${to}` });
|
recordEmailSent(id);
|
||||||
|
const newCount = limit.count + 1;
|
||||||
|
res.json({ message: `Timesheet emailed to ${adminEmail}`, count: newCount, max: MAX_EMAILS, remaining: MAX_EMAILS - newCount });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Email timesheet error:', err);
|
console.error('Email timesheet error:', err);
|
||||||
if (err.message && err.message.includes('SMTP')) {
|
if (err.message && err.message.includes('SMTP')) {
|
||||||
|
|||||||
Reference in New Issue
Block a user