From b4c7333fa1ab950662a443d85fa07afdff977f33 Mon Sep 17 00:00:00 2001 From: gitea-enwelo-jba Date: Sun, 19 Apr 2026 17:48:32 +0000 Subject: [PATCH] server.js aktualisiert --- server.js | 343 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 339 insertions(+), 4 deletions(-) diff --git a/server.js b/server.js index fb45b92..875a3ae 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,341 @@ -// Schema-Helper +require('dotenv').config(); +const express = require('express'); +const { Pool } = require('pg'); +const cors = require('cors'); + +const app = express(); +const port = process.env.PORT || 80; + +// Database Connection +const pool = new Pool({ + host: process.env.DB_HOST, + port: process.env.DB_PORT, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + idleTimeoutMillis: 30000, + connectionTimeoutMillis: 2000, +}); + +pool.on('error', (err) => { + console.error('Unexpected error on idle client', err); +}); + +// Middleware +app.use(cors()); +app.use(express.json({ limit: '50mb' })); +app.use(express.static(__dirname)); + +// Helper to run the schema isolation command +// This ensures all subsequent queries in this client session use the specified schema async function setSchema(client) { - await client.query(`SET search_path TO wind_projekt_bwscheddebrock, public;`); + const schema = process.env.DB_SCHEMA || 'wind_projekt_bwscheddebrock'; + await client.query(`SET search_path TO "${schema}", public;`); } -// Beispiel-Query: -await client.query('ALTER TABLE wind_projekt_bwscheddebrock."Kabeltrasse" ALTER COLUMN id_0 TYPE BIGINT'); \ No newline at end of file + +// Database Initial Setup / Migration +const initDBData = async () => { + const client = await pool.connect(); + try { + await setSchema(client); + console.log(`Prüfe Datenbank-Schema (Schema: ${process.env.DB_SCHEMA})...`); + + // Ensure id_0 is BIGINT + await client.query('ALTER TABLE "Kabeltrasse" ALTER COLUMN id_0 TYPE BIGINT'); + + // Enforce SRID 25832 and LineString type + try { + await client.query('ALTER TABLE "Kabeltrasse" ALTER COLUMN geom TYPE geometry(LineString, 25832) USING ST_Transform(ST_GeomFromGeoJSON(ST_AsGeoJSON(geom)), 25832)'); + } catch(e) { + try { + await client.query('ALTER TABLE "Kabeltrasse" ALTER COLUMN geom TYPE geometry(LineString, 25832) USING ST_SetSRID(geom, 25832)'); + } catch(e2) { + console.warn("SRID Enforcement skipped:", e2.message); + } + } + + // Auto-clean duplicates + try { + await client.query(` + DELETE FROM "Kabeltrasse" a + USING "Kabeltrasse" b + WHERE a.id_0 < b.id_0 AND a."Variante" = b."Variante" + `); + } catch(e) { console.warn("Cleanup warning:", e.message); } + + // Ensure Variante is unique + try { + await client.query('ALTER TABLE "Kabeltrasse" ADD CONSTRAINT variant_unique UNIQUE ("Variante")'); + } catch(e) { /* ignore if already exists */ } + + console.log("Migration abgeschlossen."); + } catch (err) { + console.error("Migration fehlgeschlagen:", err.message); + } finally { + client.release(); + } +}; +initDBData(); + +// --- API Routes --- +app.get('/api/health', (req, res) => { + res.json({ status: 'OK', schema: process.env.DB_SCHEMA, time: new Date().toISOString() }); +}); + +// 1. Get Owner Data +app.get('/api/owners', async (req, res) => { + const client = await pool.connect(); + try { + await setSchema(client); + const countRes = await client.query('SELECT count(*) FROM "Eigentuemerdaten"'); + console.log(`Datenbank-Check: ${countRes.rows[0].count} Einträge in "Eigentuemerdaten".`); + + const query = ` + SELECT + *, + ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry + FROM "Eigentuemerdaten" + `; + const result = await client.query(query); + + const geojson = { + type: "FeatureCollection", + features: result.rows.map(row => { + const { geometry, geom, ...properties } = row; + const geomObj = typeof geometry === 'string' ? JSON.parse(geometry) : geometry; + return { + type: "Feature", + id: row.id || row.id_0, + geometry: geomObj, + properties: properties + }; + }) + }; + res.json(geojson); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch owner data' }); + } finally { + client.release(); + } +}); + +// 2. Get Usage Data (Nutzung) +app.get('/api/usage', async (req, res) => { + const client = await pool.connect(); + try { + await setSchema(client); + const countRes = await client.query('SELECT count(*) FROM "Nutzung"'); + + const query = ` + SELECT + *, + ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry + FROM "Nutzung" + `; + const result = await client.query(query); + + const geojson = { + type: "FeatureCollection", + features: result.rows.map(row => { + const { geometry, geom, ...properties } = row; + const geomObj = typeof geometry === 'string' ? JSON.parse(geometry) : geometry; + return { + type: "Feature", + geometry: geomObj, + properties: properties + }; + }) + }; + res.json(geojson); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch usage data' }); + } finally { + client.release(); + } +}); + +// 2.5 Get WEA Data +app.get('/api/wea', async (req, res) => { + const client = await pool.connect(); + try { + await setSchema(client); + let result; + try { + result = await client.query(` + SELECT + *, + ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry + FROM "WEA" + `); + } catch (dbErr) { + result = { rows: [], rowCount: 0 }; + } + + const geojson = { + type: "FeatureCollection", + features: result.rows.map(row => { + const { geometry, geom, ...properties } = row; + const geomObj = typeof geometry === 'string' ? JSON.parse(geometry) : geometry; + return { + type: "Feature", + geometry: geomObj, + properties: properties + }; + }) + }; + res.json(geojson); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch WEA data' }); + } finally { + client.release(); + } +}); + +// 2.7 Get Infrastructure Data +app.get('/api/infrastructure', async (req, res) => { + const client = await pool.connect(); + try { + await setSchema(client); + let result; + try { + result = await client.query(` + SELECT + *, + ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry + FROM "Infrastruktur" + `); + } catch (dbErr) { + result = { rows: [], rowCount: 0 }; + } + + const geojson = { + type: "FeatureCollection", + features: result.rows.map(row => { + const { geometry, geom, ...properties } = row; + const geomObj = typeof geometry === 'string' ? JSON.parse(geometry) : (geometry || null); + return { + type: "Feature", + geometry: geomObj, + properties: properties + }; + }) + }; + res.json(geojson); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch infrastructure data' }); + } finally { + client.release(); + } +}); + +// 3. Get Variants (Kabeltrasse) +app.get('/api/variants', async (req, res) => { + const client = await pool.connect(); + try { + await setSchema(client); + const countRes = await client.query('SELECT count(*) FROM "Kabeltrasse"'); + + const query = ` + SELECT + id_0 as id, name, "Variante", + ST_AsGeoJSON(ST_Transform(ST_SetSRID(geom, 25832), 4326)) as geometry + FROM "Kabeltrasse" + ORDER BY id_0 DESC + `; + const result = await client.query(query); + + const featureCollection = { + type: "FeatureCollection", + features: result.rows.map(row => { + const geomObj = JSON.parse(row.geometry || 'null'); + return { + type: "Feature", + id: row.id, + geometry: geomObj, + properties: { + id: row.id, + name: row.name || (row.Variante ? `Variante ${row.Variante}` : 'Neue Trasse'), + Variante: row.Variante + } + }; + }) + }; + res.json(featureCollection); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to fetch variants' }); + } finally { + client.release(); + } +}); + +// 4. Save/Update Variant +app.post('/api/variants', async (req, res) => { + const { geometry, properties } = req.body; + const client = await pool.connect(); + try { + await setSchema(client); + + const routeName = properties.name || 'Neue Trasse'; + const variante = properties.Variante || (properties.name ? properties.name.replace('Variante ', '') : 'A'); + const geoJsonStr = JSON.stringify(geometry); + + const upsertQuery = ` + INSERT INTO "Kabeltrasse" (geom, name, "Variante") + VALUES ( + ST_MakeValid(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 25832)), + $2, + $3 + ) + ON CONFLICT ("Variante") + DO UPDATE SET + geom = EXCLUDED.geom, + name = EXCLUDED.name + RETURNING id_0 + `; + + const upsertRes = await client.query(upsertQuery, [geoJsonStr, routeName, variante]); + const finalId = upsertRes.rows[0].id_0; + + res.json({ success: true, id: finalId }); + } catch (err) { + console.error("SQL-FEHLER (SAVE):", err.message); + res.status(500).json({ error: 'Failed to save variant' }); + } finally { + client.release(); + } +}); + +// 5. Update Owner Note/Status +app.patch('/api/owners/:id', async (req, res) => { + const { id } = req.params; + const { status, notiz } = req.body; + const client = await pool.connect(); + try { + await setSchema(client); + const query = ` + UPDATE "Eigentuemerdaten" + SET status = $1, notiz = $2 + WHERE id = $3 + RETURNING id; + `; + const result = await client.query(query, [status, notiz, id]); + if (result.rowCount === 0) { + return res.status(404).json({ error: 'Owner not found' }); + } + res.json({ success: true, id: result.rows[0].id }); + } catch (err) { + console.error(err); + res.status(500).json({ error: 'Failed to update owner' }); + } finally { + client.release(); + } +}); + +app.listen(port, () => { + console.log(`Server running at http://localhost:${port}`); +});