Add npm run init-db to auto-create database and hazard_history table
Some checks failed
build-docker / Build Image (push) Has been cancelled

This commit is contained in:
mrkmntal 2026-08-21 13:34:26 -04:00
commit 3fcf2e43cd
4 changed files with 111 additions and 32 deletions

View file

@ -2,7 +2,7 @@
`ws4kp-linhanced` is a Linux-focused fork of [`netbymatt/ws4kp`](https://github.com/netbymatt/ws4kp) by `markmental`. `ws4kp-linhanced` is a Linux-focused fork of [`netbymatt/ws4kp`](https://github.com/netbymatt/ws4kp) by `markmental`.
Current release: `v0.2.1` (Security Update released on 7/30/2026) Current release: `v0.2.2`
It keeps the stronger, more stable foundation of the original `ws4kp` project while selectively incorporating international weather support and global map ideas from [`mwood77/ws4kp-international`](https://github.com/mwood77/ws4kp-international). The goal is not to become a kitchen-sink weather platform. The goal is a leaner, maintainable, Linux-oriented WeatherStar fork with a clear identity. It keeps the stronger, more stable foundation of the original `ws4kp` project while selectively incorporating international weather support and global map ideas from [`mwood77/ws4kp-international`](https://github.com/mwood77/ws4kp-international). The goal is not to become a kitchen-sink weather platform. The goal is a leaner, maintainable, Linux-oriented WeatherStar fork with a clear identity.
@ -142,31 +142,14 @@ WS4KP_MYSQL_DATABASE=ws4kp_linhanced
If your local MariaDB/MySQL instance is socket-only, set `WS4KP_MYSQL_SOCKET_PATH` and omit `WS4KP_MYSQL_HOST` / `WS4KP_MYSQL_PORT`. If your local MariaDB/MySQL instance is socket-only, set `WS4KP_MYSQL_SOCKET_PATH` and omit `WS4KP_MYSQL_HOST` / `WS4KP_MYSQL_PORT`.
Create the required table: The database and `hazard_history` table are created automatically when you run `npm run start`. You can also run the setup manually at any time:
```sql ```bash
CREATE TABLE hazard_history ( npm run init-db
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
location_label VARCHAR(255) NOT NULL,
location_key VARCHAR(128) NOT NULL,
hazard_type VARCHAR(128) NOT NULL,
source VARCHAR(64) NOT NULL,
severity VARCHAR(64) DEFAULT NULL,
latest_hazard_id VARCHAR(255) DEFAULT NULL,
encountered_at DATETIME NOT NULL,
last_seen_at DATETIME NOT NULL,
ongoing TINYINT(1) NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uq_logical_hazard (location_key, hazard_type, source),
KEY idx_last_seen_at (last_seen_at),
KEY idx_location_key (location_key),
KEY idx_ongoing (ongoing)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
``` ```
This script is idempotent, so it is safe to run repeatedly. Database initialization is skipped when running in static mode (`STATIC=1`).
The database retains full hazard history. The `Hazard List` UI shows only the latest 7 entries and only the most recent row per stored location. Nearby same-label locations are reconciled during updates to reduce duplicate location spam. The database retains full hazard history. The `Hazard List` UI shows only the latest 7 entries and only the most recent row per stored location. Nearby same-label locations are reconciled during updates to reduce duplicate location spam.
In the current browser session, `Hazard List` updates immediately after new hazard history is synced. In the current browser session, `Hazard List` updates immediately after new hazard history is synced.

12
package-lock.json generated
View file

@ -3039,9 +3039,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/fast-uri": { "node_modules/fast-uri": {
"version": "3.1.4", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@ -4828,9 +4828,9 @@
} }
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.3.0", "version": "4.3.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {

View file

@ -1,11 +1,12 @@
{ {
"name": "ws4kp-linhanced", "name": "ws4kp-linhanced",
"version": "0.2.1", "version": "0.2.2",
"description": "WeatherStar 4000+: Linhanced - A Linux-focused fork of the WeatherStar 4000+ project", "description": "WeatherStar 4000+: Linhanced - A Linux-focused fork of the WeatherStar 4000+ project",
"main": "index.mjs", "main": "index.mjs",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "npm run build && node index.mjs", "init-db": "node src/init-db.mjs",
"start": "npm run init-db && npm run build && node index.mjs",
"stop": "pkill -f 'node index.mjs' || echo 'No process found'", "stop": "pkill -f 'node index.mjs' || echo 'No process found'",
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build:travelcities": "node datagenerators/travelcities.mjs", "build:travelcities": "node datagenerators/travelcities.mjs",

95
src/init-db.mjs Normal file
View file

@ -0,0 +1,95 @@
import 'dotenv/config';
import mysql from 'mysql2/promise';
const getDbConfig = () => {
const {
WS4KP_MYSQL_HOST = '127.0.0.1',
WS4KP_MYSQL_PORT = '3306',
WS4KP_MYSQL_USER,
WS4KP_MYSQL_PASSWORD,
WS4KP_MYSQL_DATABASE,
WS4KP_MYSQL_SOCKET_PATH,
} = process.env;
if (!WS4KP_MYSQL_USER || !WS4KP_MYSQL_PASSWORD || !WS4KP_MYSQL_DATABASE) {
throw new Error('Missing MySQL configuration. Set WS4KP_MYSQL_USER, WS4KP_MYSQL_PASSWORD, and WS4KP_MYSQL_DATABASE.');
}
const config = {
user: WS4KP_MYSQL_USER,
password: WS4KP_MYSQL_PASSWORD,
};
if (WS4KP_MYSQL_SOCKET_PATH) {
config.socketPath = WS4KP_MYSQL_SOCKET_PATH;
} else {
config.host = WS4KP_MYSQL_HOST;
config.port = Number(WS4KP_MYSQL_PORT);
}
return { ...config, database: WS4KP_MYSQL_DATABASE };
};
const createDatabase = async (connection, database) => {
await connection.execute(
`CREATE DATABASE IF NOT EXISTS \`${database}\`
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci`,
);
};
const createHazardHistoryTable = async (connection) => {
await connection.execute(`
CREATE TABLE IF NOT EXISTS \`hazard_history\` (
\`id\` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
\`location_label\` VARCHAR(255) NOT NULL,
\`location_key\` VARCHAR(128) NOT NULL,
\`hazard_type\` VARCHAR(128) NOT NULL,
\`source\` VARCHAR(64) NOT NULL,
\`severity\` VARCHAR(64) DEFAULT NULL,
\`latest_hazard_id\` VARCHAR(255) DEFAULT NULL,
\`encountered_at\` DATETIME NOT NULL,
\`last_seen_at\` DATETIME NOT NULL,
\`ongoing\` TINYINT(1) NOT NULL DEFAULT 1,
\`created_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
\`updated_at\` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`uq_logical_hazard\` (\`location_key\`, \`hazard_type\`, \`source\`),
KEY \`idx_last_seen_at\` (\`last_seen_at\`),
KEY \`idx_location_key\` (\`location_key\`),
KEY \`idx_ongoing\` (\`ongoing\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
`);
};
const initDatabase = async () => {
if (process.env.STATIC) {
console.log('Skipping database initialization (STATIC mode).');
return;
}
const { database, ...baseConfig } = getDbConfig();
let connection;
try {
connection = await mysql.createConnection(baseConfig);
await createDatabase(connection, database);
console.log(`Database "${database}" is ready.`);
} finally {
if (connection) await connection.end();
}
try {
connection = await mysql.createConnection({ ...baseConfig, database });
await createHazardHistoryTable(connection);
console.log('Table "hazard_history" is ready.');
} finally {
if (connection) await connection.end();
}
};
initDatabase().catch((error) => {
console.error('Database initialization failed:', error.message);
process.exit(1);
});