ws4kp-linhanced/src/hazard-history.mjs

143 lines
3.5 KiB
JavaScript

import { getPool } from './mysql.mjs';
const MAX_HISTORY_ENTRIES = 7;
const toIsoString = (value) => {
if (!value) return null;
const date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? null : date.toISOString();
};
const mapRowToHistoryEntry = (row) => ({
location: row.location_label,
locationKey: row.location_key,
hazardType: row.hazard_type,
source: row.source,
severity: row.severity,
latestHazardId: row.latest_hazard_id,
encounteredAt: toIsoString(row.encountered_at),
lastSeenAt: toIsoString(row.last_seen_at),
ongoing: Boolean(row.ongoing),
});
/**
* Format location label from weather parameters
* @param {string} city - City name
* @param {string} state - State name
* @param {string} country - Country name
* @param {string} countryCode - Country code
* @returns {string} Formatted location label
*/
const formatLocation = (city, state, country, countryCode) => {
const cleanCity = city?.trim() || 'Unknown';
if (countryCode === 'US' || countryCode === 'USA') {
const cleanState = state?.trim();
return cleanState ? `${cleanCity}, ${cleanState}` : cleanCity;
}
const cleanCountry = country?.trim();
return cleanCountry ? `${cleanCity}, ${cleanCountry}` : cleanCity;
};
const getHistory = async () => {
const [rows] = await getPool().query(
`SELECT
location_label,
location_key,
hazard_type,
source,
severity,
latest_hazard_id,
encountered_at,
last_seen_at,
ongoing
FROM hazard_history
ORDER BY last_seen_at DESC
LIMIT ?`,
[MAX_HISTORY_ENTRIES],
);
return rows.map(mapRowToHistoryEntry);
};
const updateHistory = async (payload) => {
const { location, locationKey, hazards = [] } = payload;
const validHazards = hazards.filter((hazard) => hazard?.hazardType && hazard?.source);
const pool = getPool();
const connection = await pool.getConnection();
try {
await connection.beginTransaction();
if (validHazards.length === 0) {
await connection.execute(
`UPDATE hazard_history
SET ongoing = 0,
last_seen_at = UTC_TIMESTAMP()
WHERE location_key = ?
AND ongoing = 1`,
[locationKey],
);
} else {
const keepClauses = validHazards.map(() => '(hazard_type = ? AND source = ?)').join(' OR ');
const keepParams = validHazards.flatMap((hazard) => [hazard.hazardType, hazard.source]);
await connection.execute(
`UPDATE hazard_history
SET ongoing = 0,
last_seen_at = UTC_TIMESTAMP()
WHERE location_key = ?
AND ongoing = 1
AND NOT (${keepClauses})`,
[locationKey, ...keepParams],
);
}
for (const hazard of validHazards) {
await connection.execute(
`INSERT INTO hazard_history (
location_label,
location_key,
hazard_type,
source,
severity,
latest_hazard_id,
encountered_at,
last_seen_at,
ongoing
) VALUES (?, ?, ?, ?, ?, ?, UTC_TIMESTAMP(), UTC_TIMESTAMP(), 1)
ON DUPLICATE KEY UPDATE
location_label = VALUES(location_label),
severity = VALUES(severity),
latest_hazard_id = VALUES(latest_hazard_id),
last_seen_at = UTC_TIMESTAMP(),
ongoing = 1`,
[
location,
locationKey,
hazard.hazardType,
hazard.source,
hazard.severity ?? null,
hazard.id ?? null,
],
);
}
await connection.commit();
} catch (error) {
await connection.rollback();
throw error;
} finally {
connection.release();
}
return getHistory();
};
export {
formatLocation,
getHistory,
MAX_HISTORY_ENTRIES,
updateHistory,
};