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 async function setSchema(client) { await client.query(`SET search_path TO ${process.env.DB_SCHEMA}, public;`); } // Routes app.get('/api/health', (req, res) => { res.json({ status: 'OK', time: new Date().toISOString() }); }); // 1. Get Owner Data app.get('/api/owners', async (req, res) => { console.log("Anfrage erhalten: /api/owners"); const client = await pool.connect(); try { await setSchema(client); // 1. Log actual count in DB const countRes = await client.query('SELECT count(*) FROM bw_scheddebrock."Eigentuemerdaten"'); console.log(`Datenbank-Check: ${countRes.rows[0].count} Eigentümer in bw_scheddebrock."Eigentuemerdaten" gefunden.`); const query = ` SELECT *, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM bw_scheddebrock."Eigentuemerdaten" `; const result = await client.query(query); console.log(`Abfrage erfolgreich: ${result.rowCount} Zeilen für Frontend geladen.`); 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); // 1. Log actual count in DB const countRes = await client.query('SELECT count(*) FROM bw_scheddebrock."Nutzung"'); console.log(`Datenbank-Check: ${countRes.rows[0].count} Nutzungs-Flächen in bw_scheddebrock."Nutzung" gefunden.`); const query = ` SELECT *, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM bw_scheddebrock."Nutzung" `; const result = await client.query(query); console.log(`Abfrage erfolgreich: ${result.rowCount} Zeilen für Frontend geladen.`); 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 (Windturbinen) 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 bw_scheddebrock."WEA" `); } catch (dbErr) { console.log("WEA Tabelle fehlt oder nicht abrufbar."); 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 (UW, etc) 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 bw_scheddebrock."Infrastruktur" `); } catch (dbErr) { console.log("Infrastruktur Tabelle fehlt oder nicht abrufbar."); 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); // Log count const countRes = await client.query('SELECT count(*) FROM bw_scheddebrock."Kabeltrasse"'); console.log(`Datenbank-Check: ${countRes.rows[0].count} Kabeltrassen in bw_scheddebrock."Kabeltrasse" gefunden.`); const query = ` SELECT id_0 as id, name, "Variante", ST_AsGeoJSON(ST_Transform(ST_SetSRID(geom, 25832), 4326)) as geometry FROM bw_scheddebrock."Kabeltrasse" ORDER BY id_0 DESC `; const result = await client.query(query); console.log(`Abfrage erfolgreich: ${result.rowCount} Trassen geladen.`); const variants = result.rows.map(row => { let routes = []; const geomObj = typeof row.geometry === 'string' ? JSON.parse(row.geometry) : row.geometry; if (geomObj && geomObj.coordinates) { if (geomObj.type === 'MultiLineString') { routes = geomObj.coordinates.map(line => line.map(c => ({ lat: c[1], lng: c[0] }))); } else if (geomObj.type === 'LineString') { routes = geomObj.coordinates.map(c => ({ lat: c[1], lng: c[0] })); } } return { id: row.id, name: row.name || row.Variante || 'Trasse', active: false, visible: true, stats: { total: 0, drilling: 0, open: 0, muffen: 0 }, routes: routes }; }); res.json(variants); } 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) => { console.log("Empfangene Daten (Payload):", JSON.stringify(req.body).substring(0, 200) + "..."); const { geometry, properties } = req.body; const client = await pool.connect(); try { await setSchema(client); console.log("Starte PostgreSQL-Query..."); // Clean UPSERT by Database ID. const routeId = req.body.id; const routeName = properties.name || 'Neue Trasse'; const variante = properties.Variante || properties.name || 'A'; const geoJsonStr = JSON.stringify(geometry); // Check if the passed ID is a valid database ID (small int) vs a frontend dummy Date.now() timestamp // Or try to match it initially just in case it's a known database row. const existing = await client.query('SELECT id_0 FROM bw_scheddebrock."Kabeltrasse" WHERE id_0 = $1', [Number(routeId) || 0]); let result; if (existing.rowCount > 0) { // EXACT match found physically in DB, do an UPDATE const updateQuery = ` UPDATE bw_scheddebrock."Kabeltrasse" SET geom = ST_MakeValid(ST_SetSRID(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 25832), 25832)), name = $2, "Variante" = $3 WHERE id_0 = $4 RETURNING id_0 as id; `; result = await client.query(updateQuery, [geoJsonStr, routeName, variante, existing.rows[0].id_0]); console.log(`PostgreSQL-Ergebnis: Zeile ${existing.rows[0].id_0} aktualisiert (Variante: ${routeName})!`); } else { // INSERT new const insertQuery = ` INSERT INTO bw_scheddebrock."Kabeltrasse" (geom, name, "Variante") VALUES (ST_MakeValid(ST_SetSRID(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 25832), 25832)), $2, $3) RETURNING id_0 as id; `; result = await client.query(insertQuery, [geoJsonStr, routeName, variante]); console.log(`PostgreSQL-Ergebnis: Zeile eingefügt! ID: ${result.rows[0].id}`); } res.json({ success: true, id: result.rows[0].id }); } catch (err) { console.error("KRITISCHER SQL-FEHLER:", err.message); res.status(500).json({ error: 'Failed to save variant: ' + err.message }); } 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 bw_scheddebrock."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 in database' }); } res.json({ success: true, id: result.rows[0].id }); } catch (err) { console.error("Owner Update 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}`); });