diff options
Diffstat (limited to 'download.js')
| -rw-r--r-- | download.js | 81 |
1 files changed, 68 insertions, 13 deletions
diff --git a/download.js b/download.js index 90a7d81..6a1e67e 100644 --- a/download.js +++ b/download.js @@ -1,10 +1,15 @@ -import crypto from "crypto"; -import fs from "fs"; -import path from "path"; +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; const couchUsername = process.env.COUCHDB_USERNAME; const couchPassword = process.env.COUCHDB_PASSWORD; +/** + * Generates the Basic Auth header for CouchDB using environment variables. + * @returns {string} The Basic Auth header string. + * @throws {Error} If CouchDB credentials are not set. + */ function getAuthHeader() { if (!couchUsername || !couchPassword) { throw new Error("CouchDB credentials not set in environment variables"); @@ -13,7 +18,6 @@ function getAuthHeader() { return `Basic ${auth}`; } -// === CONFIG === const baseUrl = "https://kartta.hel.fi/ws/geoserver/avoindata/wfs"; const layers = [ "Aluesarjat_avainluvut_2024", @@ -41,6 +45,10 @@ if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } +/** + * Creates headers for CouchDB requests, including authorization. + * @returns {Headers} The Headers object for fetch requests. + */ function getHeaders() { return new Headers({ // biome-ignore lint/style/useNamingConvention: database @@ -49,7 +57,11 @@ function getHeaders() { }); } -// === COUCHDB HELPERS === +/** + * Creates the CouchDB database if it doesn't exist. + * @returns {Promise<void>} + * @throws {Error} If database creation fails (other than already exists). + */ async function createDatabase() { const url = `${couchUrl}/${dbName}`; const res = await fetch(url, { @@ -58,11 +70,16 @@ async function createDatabase() { }); if (res.ok || res.status === 412) { console.log(`Database ${dbName} ready.`); + return; } else { throw new Error(await res.text()); } } +/** + * Ensures the design documents (views) exist in the database, creating or updating as needed. + * @returns {Promise<void>} + */ async function ensureDesignDocs() { const designDoc = { _id: "_design/layers", @@ -87,6 +104,7 @@ async function ensureDesignDocs() { method: "PUT", }); console.log("Created design document: layers/by_layer"); + return; } else if (res.ok) { const existing = await res.json(); designDoc._rev = existing._rev; @@ -96,10 +114,18 @@ async function ensureDesignDocs() { method: "PUT", }); console.log("Updated design document"); + return; } + // If neither, implicitly return void, but log unexpected status + console.warn(`Unexpected status when ensuring design docs: ${res.status}`); } -// === DOWNLOAD === +/** + * Downloads a GeoJSON layer from the WFS service. + * @param {string} layer - The name of the layer to download. + * @returns {Promise<object>} The parsed GeoJSON object. + * @throws {Error} If the fetch fails. + */ async function downloadLayer(layer) { const url = `${baseUrl}?service=WFS&version=2.0.0&request=GetFeature&typeName=avoindata:${layer}&outputFormat=json&srsname=EPSG:4326`; const res = await fetch(url); @@ -108,13 +134,26 @@ async function downloadLayer(layer) { return response; } +/** + * Saves GeoJSON data to a local file. + * Note: This function is defined but not currently used in the script. It could be called in processLayer if local saving is desired. + * @param {string} layer - The layer name for the file. + * @param {object} data - The GeoJSON data to save. + * @returns {void} + */ function saveToFile(layer, data) { const filePath = path.join(outputDir, `${layer}.geojson`); fs.writeFileSync(filePath, JSON.stringify(data, null, "\t")); console.log(`Saved: ${layer}.geojson`); } -// === UPLOAD METADATA === +/** + * Uploads or updates metadata for a layer in CouchDB. + * @param {string} layer - The layer name. + * @param {number} featureCount - The number of features in the layer. + * @returns {Promise<void>} + * @throws {Error} If the upload fails. + */ async function uploadLayerMetadata(layer, featureCount) { const docId = `layer_metadata:${layer}`; @@ -142,9 +181,15 @@ async function uploadLayerMetadata(layer, featureCount) { }); if (!putRes.ok) throw new Error(await putRes.text()); console.log(`Metadata updated: ${layer} (${featureCount} features)`); + return; } -// === UPLOAD SINGLE FEATURE (with deduplication) === +/** + * Uploads a single feature document to CouchDB, with deduplication check. + * @param {object} doc - The feature document to upload. + * @returns {Promise<boolean>} True if uploaded/updated, false if skipped (no changes). + * @throws {Error} If the upload fails. + */ async function uploadFeature(doc) { const url = `${couchUrl}/${dbName}/${doc._id}`; const getRes = await fetch(url, { headers: getHeaders() }); @@ -165,15 +210,20 @@ async function uploadFeature(doc) { method: "PUT", }); - return putRes.ok; + if (!putRes.ok) throw new Error(await putRes.text()); + return true; // uploaded or updated } -// === PROCESS LAYER === +/** + * Processes a single layer: downloads GeoJSON, uploads features with dedup, and updates metadata. + * @param {string} layer - The layer to process. + * @returns {Promise<{uploaded: number, skipped: number}>} Counts of uploaded and skipped features. + * @throws {Error} If download or uploads fail. + */ async function processLayer(layer) { const geojson = await downloadLayer(layer); if (!geojson || !geojson.features) { - console.warn(`No features in ${layer} ${geojson}`); - process.exit(1); + throw new Error(`No features in ${layer}: ${JSON.stringify(geojson)}`); } let uploaded = 0; @@ -204,9 +254,13 @@ async function processLayer(layer) { await uploadLayerMetadata(layer, geojson.features.length); console.log(`Done: ${layer} | Uploaded: ${uploaded} | Skipped: ${skipped}`); + return { skipped, uploaded }; } -// === MAIN === +/** + * Main entry point: sets up database, processes all layers. + * @returns {Promise<void>} + */ async function main() { await createDatabase(); await ensureDesignDocs(); @@ -218,6 +272,7 @@ async function main() { } console.log("All layers processed."); + return; } if (process.argv[1] === new URL(import.meta.url).pathname) { |
