|
|
|
|
@@ -1,4 +1,4 @@
|
|
|
|
|
// src/routes/system.routests
|
|
|
|
|
// src/routes/system.routes.ts
|
|
|
|
|
import { Router, Request, Response, NextFunction } from 'express';
|
|
|
|
|
import { exec } from 'child_process';
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
@@ -8,7 +8,7 @@ import { validateRequest } from '../middleware/validation.middleware';
|
|
|
|
|
|
|
|
|
|
const router = Router();
|
|
|
|
|
|
|
|
|
|
// Validation Schemas
|
|
|
|
|
// Helper for consistent required string validation (handles missing/null/empty)
|
|
|
|
|
const requiredString = (message: string) =>
|
|
|
|
|
z.preprocess((val) => val ?? '', z.string().min(1, message));
|
|
|
|
|
|
|
|
|
|
@@ -18,65 +18,62 @@ const geocodeSchema = z.object({
|
|
|
|
|
}),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// An empty schema for routes that do not expect any input, to maintain a consistent validation pattern.
|
|
|
|
|
const emptySchema = z.object({});
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* GET /pm2-status
|
|
|
|
|
* Checks if 'flyer-crawler-api' is online via PM2.
|
|
|
|
|
* 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) => {
|
|
|
|
|
// Using simple exec command
|
|
|
|
|
const cmd = 'pm2 describe flyer-crawler-api';
|
|
|
|
|
|
|
|
|
|
exec(cmd, (error, stdout, stderr) => {
|
|
|
|
|
if (res.headersSent) return;
|
|
|
|
|
|
|
|
|
|
// Handle "process not found" case which might come as an error code OR text
|
|
|
|
|
const output = (stdout || '') + (stderr || '');
|
|
|
|
|
const processNotFound = output.includes("doesn't exist");
|
|
|
|
|
|
|
|
|
|
// The name 'flyer-crawler-api' comes from your ecosystem.config.cjs file.
|
|
|
|
|
exec('pm2 describe flyer-crawler-api', (error, stdout, stderr) => {
|
|
|
|
|
if (error) {
|
|
|
|
|
if (processNotFound) {
|
|
|
|
|
logger.warn('[API /pm2-status] PM2 process not found.');
|
|
|
|
|
// '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({ err: error.message }, '[API /pm2-status] Exec error');
|
|
|
|
|
logger.error(
|
|
|
|
|
{ error: stderr || error.message },
|
|
|
|
|
'[API /pm2-status] Error executing pm2 describe:',
|
|
|
|
|
);
|
|
|
|
|
return next(error);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Treat stderr as error if it contains text (PM2 often outputs warnings here)
|
|
|
|
|
// 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) {
|
|
|
|
|
// Special case: if it's just a warning we might want to ignore, but sticking to defensive rules:
|
|
|
|
|
logger.error({ stderr }, '[API /pm2-status] Stderr output');
|
|
|
|
|
logger.error({ stderr }, '[API /pm2-status] PM2 executed but produced stderr:');
|
|
|
|
|
return next(new Error(`PM2 command produced an error: ${stderr}`));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for online status in the table output
|
|
|
|
|
const isOnline = /│ status\s+│ online\s+│/m.test(stdout || '');
|
|
|
|
|
// 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 /geocode
|
|
|
|
|
* Proxies geocoding requests securely.
|
|
|
|
|
* 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 },
|
|
|
|
|
@@ -86,6 +83,7 @@ router.post(
|
|
|
|
|
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.' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|