143 lines
3.5 KiB
JavaScript
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,
|
|
};
|