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) { const schema = process.env.DB_SCHEMA || 'wind_projekt_bwscheddebrock'; await client.query(`SET search_path TO "${schema}", public;`); } // --- 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 query = ` SELECT id, nachname AS "Nachname", vorname AS "Vorname", ort AS "Ort", "Flur" AS "Flur", "FlSt" AS "Flurstueck", "Gema" AS "Gemarkung", "PLZ" AS "PLZ", "Land" AS "Land", "AFlaeche" AS "AFlaeche", status, notiz, 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, ...properties } = row; return { type: "Feature", id: row.id, geometry: JSON.parse(geometry), properties: properties }; }) }; res.json(geojson); } catch (err) { console.error("Owner Fetch 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 query = ` SELECT id, nutzart AS "nutzart", bez AS "bez", 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, ...properties } = row; return { type: "Feature", geometry: JSON.parse(geometry), properties: properties }; }) }; res.json(geojson); } catch (err) { console.error("Usage Error:", err); res.json({ type: "FeatureCollection", features: [] }); } finally { client.release(); } }); // 2.5 Get WEA Data app.get('/api/wea', async (req, res) => { const client = await pool.connect(); try { await setSchema(client); const query = ` SELECT id, "WEA" AS "Name", "GH" AS "GH", ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM wea `; const result = await client.query(query); const geojson = { type: "FeatureCollection", features: result.rows.map(row => { const { geometry, ...properties } = row; return { type: "Feature", geometry: JSON.parse(geometry), properties: properties }; }) }; res.json(geojson); } catch (err) { console.error("WEA Error:", err); res.json({ type: "FeatureCollection", features: [] }); } finally { client.release(); } }); // 2.6 Get POI Data (Netzverknüpfungspunkt) app.get('/api/poi', async (req, res) => { const client = await pool.connect(); try { await setSchema(client); const query = ` SELECT id, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM "netzverknüpfungspunkt" `; const result = await client.query(query); const geojson = { type: "FeatureCollection", features: result.rows.map(row => { const { geometry, ...properties } = row; return { type: "Feature", geometry: JSON.parse(geometry), properties: properties }; }) }; res.json(geojson); } catch (err) { console.error("POI Error:", err); res.json({ type: "FeatureCollection", features: [] }); } finally { client.release(); } }); // 2.7 Get Infrastructure Data app.get('/api/infrastructure', async (req, res) => { const client = await pool.connect(); try { await setSchema(client); const query = ` SELECT id, ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM infrastruktur `; const result = await client.query(query); const geojson = { type: "FeatureCollection", features: result.rows.map(row => { const { geometry, ...properties } = row; return { type: "Feature", geometry: JSON.parse(geometry), properties: properties }; }) }; res.json(geojson); } catch (err) { res.json({ type: "FeatureCollection", features: [] }); } finally { client.release(); } }); // 3. Get Variants (Kabeltrasse) app.get('/api/variants', async (req, res) => { const client = await pool.connect(); try { await setSchema(client); const query = ` SELECT id, name, "Variante", ST_AsGeoJSON(ST_Transform(geom, 4326)) as geometry FROM kabeltrasse ORDER BY id DESC `; const result = await client.query(query); const featureCollection = { type: "FeatureCollection", features: result.rows.map(row => { return { type: "Feature", id: row.id, geometry: JSON.parse(row.geometry || 'null'), properties: { id: row.id, name: row.name || (row.Variante ? `Variante ${row.Variante}` : 'Neue Trasse'), Variante: row.Variante } }; }) }; res.json(featureCollection); } catch (err) { res.json({ type: "FeatureCollection", features: [] }); } 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 varianteValue = properties.Variante || (properties.name ? properties.name.replace('Variante ', '') : 'A'); const geoJsonStr = JSON.stringify(geometry); // Safety Check: DON'T delete/save if geometry is empty or invalid if (!geometry || !geometry.coordinates || geometry.coordinates.length < 2) { console.log(`Aborting save for ${varianteValue}: Geometry is too short or empty.`); return res.json({ success: true, message: 'Skipped empty geometry' }); } try { await client.query('BEGIN'); // Delete existing variant if it exists await client.query('DELETE FROM kabeltrasse WHERE "Variante" = $1 OR name = $2', [varianteValue, routeName]); const insertQuery = ` INSERT INTO kabeltrasse (geom, name, "Variante") VALUES ( ST_Multi(ST_MakeValid(ST_Transform(ST_SetSRID(ST_GeomFromGeoJSON($1), 4326), 25832))), $2, $3 ) RETURNING id `; const insertRes = await client.query(insertQuery, [geoJsonStr, routeName, varianteValue]); await client.query('COMMIT'); res.json({ success: true, id: insertRes.rows[0].id }); } catch (sqlErr) { await client.query('ROLLBACK'); throw sqlErr; } } 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]); res.json({ success: true, id: result.rows[0]?.id }); } catch (err) { console.error("Update Owner Error:", err); res.status(500).json({ error: 'Failed to update owner' }); } finally { client.release(); } }); app.listen(3000, () => { console.log(`Server running at http://localhost:3000`); });