← back to Jill Website
src/routes/inquiries.ts
156 lines
import { Router, Request, Response } from 'express';
import { InquiryModel, Inquiry, InquiryValidationError } from '../models/Inquiry';
import { emailService, EmailService } from '../services/emailService';
import { requireAuth } from '../middleware/auth';
// Factory function for testing
export function createInquiriesRouter(inquiryModel?: InquiryModel, emailSvc?: EmailService): Router {
const router = Router();
const model = inquiryModel || new InquiryModel();
const emailServiceInstance = emailSvc || emailService;
/**
* POST /api/inquiries
* Create a new guest inquiry
*/
router.post('/inquiries', async (req: Request, res: Response): Promise<void> => {
try {
if (!req.body) {
res.status(400).json({
success: false,
error: 'Validation error',
message: 'Request body is required'
});
return;
}
const inquiryData: Omit<Inquiry, 'id' | 'created_at' | 'updated_at'> = {
name: req.body.name,
email: req.body.email,
phone: req.body.phone,
check_in_date: req.body.check_in_date ? new Date(req.body.check_in_date) : undefined as any,
check_out_date: req.body.check_out_date ? new Date(req.body.check_out_date) : undefined as any,
guest_count: req.body.guest_count ? parseInt(req.body.guest_count, 10) : undefined as any,
message: req.body.message
};
// Create inquiry (validation happens in model)
const newInquiry = await model.create(inquiryData);
// Send confirmation email (non-blocking)
emailServiceInstance.sendConfirmationEmail(newInquiry)
.then(result => {
if (!result.success) {
console.error('Failed to send confirmation email:', result.error);
} else {
console.log('Confirmation email sent successfully:', result.messageId);
}
})
.catch(error => {
console.error('Error sending confirmation email:', error);
});
res.status(201).json({
success: true,
data: {
id: newInquiry.id,
inquiry: newInquiry
},
message: 'Inquiry created successfully'
});
} catch (error) {
// Handle validation errors
if (error instanceof Error && error.message.startsWith('Validation failed:')) {
res.status(400).json({
success: false,
error: 'Validation error',
message: error.message
});
return;
}
// Handle other errors
console.error('Error creating inquiry:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'Failed to create inquiry'
});
}
});
/**
* GET /api/inquiries — admin-only PII data dump (name, email, phone, message
* of every guest inquiry). P0 fix 2026-05-04: was unauth (only the site-wide
* basic-auth gate stood between it and any visitor with the site password).
*/
router.get('/inquiries', requireAuth, async (req: Request, res: Response): Promise<void> => {
try {
const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 100;
const offset = req.query.offset ? parseInt(req.query.offset as string, 10) : 0;
const inquiries = await model.findAll(limit, offset);
res.status(200).json({
success: true,
data: inquiries,
count: inquiries.length
});
} catch (error) {
console.error('Error fetching inquiries:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'Failed to fetch inquiries'
});
}
});
/**
* GET /api/inquiries/:id — admin-only (P0 fix 2026-05-04, same as the list route).
*/
router.get('/inquiries/:id', requireAuth, async (req: Request, res: Response): Promise<void> => {
try {
const idParam = Array.isArray(req.params.id) ? req.params.id[0] : req.params.id;
const id = parseInt(idParam, 10);
// Check if the ID is a valid integer (not NaN, not a decimal)
if (isNaN(id) || idParam !== id.toString()) {
res.status(400).json({
success: false,
error: 'Invalid ID',
message: 'Inquiry ID must be a number'
});
return;
}
const inquiry = await model.findById(id);
if (!inquiry) {
res.status(404).json({
success: false,
error: 'Not found',
message: 'Inquiry not found'
});
return;
}
res.status(200).json({
success: true,
data: inquiry
});
} catch (error) {
console.error('Error fetching inquiry:', error);
res.status(500).json({
success: false,
error: 'Internal server error',
message: 'Failed to fetch inquiry'
});
}
});
return router;
}
export default createInquiriesRouter();