Files
flyer-crawler.projectium.com/src/routes/system.routes.ts
Torben Sorensen 56f14f6342
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
feat: Enhance API validation and error handling across routes
- Added tests for invalid request bodies in price and recipe routes.
- Improved type safety in request handlers using Zod schemas.
- Introduced a consistent validation pattern for empty request bodies.
- Enhanced error messages for invalid query parameters in stats and user routes.
- Implemented middleware to inject mock logging for tests.
- Created a custom type for validated requests to streamline type inference.
2025-12-14 12:53:10 -08:00

75 lines
3.2 KiB
TypeScript

// src/routes/system.ts
import { Router, Request, Response, NextFunction } from 'express';
import { exec } from 'child_process';
import { z } from 'zod';
import { logger } from '../services/logger.server';
import { geocodingService } from '../services/geocodingService.server';
import { validateRequest } from '../middleware/validation.middleware';
const router = Router();
const geocodeSchema = z.object({
body: z.object({
address: z.string().min(1, 'An address string is required.'),
}),
});
// An empty schema for routes that do not expect any input, to maintain a consistent validation pattern.
const emptySchema = z.object({});
/**
* Checks the status of the 'flyer-crawler-api' process managed by PM2.
* This is intended for development and diagnostic purposes.
*/
router.get('/pm2-status', validateRequest(emptySchema), (req: Request, res: Response, next: NextFunction) => {
// The name 'flyer-crawler-api' comes from your ecosystem.config.cjs file.
exec('pm2 describe flyer-crawler-api', (error, stdout, stderr) => {
if (error) {
// 'pm2 describe' exits with an error if the process is not found.
// We can treat this as a "fail" status for our check.
if (stdout && stdout.includes("doesn't exist")) {
logger.warn('[API /pm2-status] PM2 process "flyer-crawler-api" not found.');
return res.json({ success: false, message: 'Application process is not running under PM2.' });
}
logger.error({ error: stderr || error.message }, '[API /pm2-status] Error executing pm2 describe:');
return next(error);
}
// Check if there was output to stderr, even if the exit code was 0 (success).
// This handles warnings or non-fatal errors that should arguably be treated as failures in this context.
if (stderr && stderr.trim().length > 0) {
logger.error({ stderr }, '[API /pm2-status] PM2 executed but produced stderr:');
return next(new Error(`PM2 command produced an error: ${stderr}`));
}
// If the command succeeds, we can parse stdout to check the status.
const isOnline = /│ status\s+│ online\s+│/m.test(stdout);
const message = isOnline ? 'Application is online and running under PM2.' : 'Application process exists but is not online.';
res.json({ success: isOnline, message });
});
});
/**
* POST /api/system/geocode - Geocodes a given address string.
* This acts as a secure proxy to the Google Maps Geocoding API.
*/
router.post('/geocode', validateRequest(geocodeSchema), async (req: Request, res: Response, next: NextFunction) => {
// Infer type and cast request object as per ADR-003
type GeocodeRequest = z.infer<typeof geocodeSchema>;
const { body: { address } } = req as unknown as GeocodeRequest;
try {
const coordinates = await geocodingService.geocodeAddress(address, req.log);
if (!coordinates) { // This check remains, but now it only fails if BOTH services fail.
return res.status(404).json({ message: 'Could not geocode the provided address.' });
}
res.json(coordinates);
} catch (error) {
next(error);
}
});
export default router;