275 lines
10 KiB
JavaScript
275 lines
10 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const { Pool } = require('pg');
|
|
const bodyParser = require('body-parser');
|
|
const cors = require('cors');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
const port = process.env.PORT || 3000;
|
|
|
|
app.use(cors());
|
|
app.use(bodyParser.json());
|
|
|
|
// Serve static files from the root directory
|
|
app.use(express.static(path.join(__dirname, './')));
|
|
|
|
const pool = new Pool({
|
|
host: process.env.DB_HOST,
|
|
port: process.env.DB_PORT,
|
|
database: process.env.DB_NAME,
|
|
user: process.env.DB_USER,
|
|
password: process.env.DB_PASSWORD
|
|
});
|
|
|
|
// In-memory logs for remote debugging
|
|
const serverLogs = [];
|
|
function log(msg) {
|
|
const entry = `[${new Date().toISOString()}] ${msg}`;
|
|
console.log(entry);
|
|
serverLogs.push(entry);
|
|
if (serverLogs.length > 50) serverLogs.shift();
|
|
}
|
|
|
|
// Status-Endpunkt für Diagnose
|
|
app.get('/api/status', async (req, res) => {
|
|
try {
|
|
const result = await pool.query('SELECT NOW()');
|
|
res.json({
|
|
status: 'online',
|
|
database: 'connected',
|
|
time: result.rows[0].now,
|
|
schema: process.env.DB_SCHEMA || 'geodaten'
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ status: 'error', database: 'disconnected', error: err.message });
|
|
}
|
|
});
|
|
|
|
app.get('/api/logs', (req, res) => {
|
|
res.type('text/plain').send(serverLogs.join('\n'));
|
|
});
|
|
|
|
// API to save turbines
|
|
app.post('/api/wea', async (req, res) => {
|
|
const { projekt_id, turbines } = req.body;
|
|
const targetProject = projekt_id || 'BWSamern-Ohne';
|
|
const schema = process.env.DB_SCHEMA || 'geodaten';
|
|
|
|
log(`Empfange Save-Request für Projekt: ${targetProject} (${turbines?.length || 0} WEAs)`);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
// 1. Bestehende WEAs für dieses Projekt löschen
|
|
const delRes = await client.query(
|
|
`DELETE FROM ${schema}.wea_standorte WHERE projekt_id = $1`,
|
|
[targetProject]
|
|
);
|
|
log(`Gelöscht: ${delRes.rowCount} bestehende Anlagen.`);
|
|
|
|
// 2. Neue WEAs einfügen
|
|
if (turbines && turbines.length > 0) {
|
|
for (const t of turbines) {
|
|
log(`Füge WEA ein: Nr=${t.nr}, Typ=${t.type}, Pos=${t.latlng?.lat},${t.latlng?.lng}`);
|
|
await client.query(
|
|
`INSERT INTO ${schema}.wea_standorte
|
|
(projekt_id, wea_nummer, anlagentyp, nabenhoehe, rotordurchmesser, ksf_drehung, variante, geom)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, ST_Transform(ST_SetSRID(ST_MakePoint($8, $9), 4326), 25832))`,
|
|
[targetProject, t.nr, t.type, t.hh, t.rd, t.ksfAngle, t.variant, t.latlng.lng, t.latlng.lat]
|
|
);
|
|
}
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
log(`Erfolgreich gespeichert: ${turbines?.length || 0} WEAs.`);
|
|
res.json({ message: `${turbines?.length || 0} WEAs erfolgreich in Datenbank gespeichert.` });
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
log(`FEHLER beim Speichern: ${err.message}`);
|
|
res.status(500).json({ error: 'Datenbankfehler beim Speichern', details: err.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// API to load turbines
|
|
app.get('/api/wea/:projekt_id', async (req, res) => {
|
|
const { projekt_id } = req.params;
|
|
log(`Lade WEAs für Projekt: ${projekt_id}`);
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT wea_nummer as nr, anlagentyp as type, nabenhoehe as hh, rotordurchmesser as rd, ksf_drehung as ksfAngle, variante as variant,
|
|
ST_X(ST_Transform(geom, 4326)) as lng, ST_Y(ST_Transform(geom, 4326)) as lat
|
|
FROM geodaten.wea_standorte WHERE projekt_id = $1`,
|
|
[projekt_id]
|
|
);
|
|
log(`Geladen: ${result.rowCount} Anlagen.`);
|
|
res.status(200).json(result.rows);
|
|
} catch (e) {
|
|
log(`FEHLER beim Laden: ${e.message}`);
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Hilfsfunktion zur Auflösung von Projekt-ID oder Name
|
|
async function resolveProjectId(client, input) {
|
|
if (!input) return null;
|
|
|
|
// Prüfen, ob input eine valide UUID ist
|
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
if (uuidRegex.test(input)) return input;
|
|
|
|
// Normalisierte Suche (Kleinschreibung, alle Bindestriche und Unterstriche entfernen)
|
|
const normalized = input.toLowerCase().replace(/[-_]/g, '');
|
|
|
|
const res = await client.query(
|
|
'SELECT id FROM geodaten.projekte WHERE LOWER(REPLACE(REPLACE(name, \'-\', \'\'), \'_\', \'\')) = $1',
|
|
[normalized]
|
|
);
|
|
return res.rows.length > 0 ? res.rows[0].id : null;
|
|
}
|
|
|
|
// API zur Flächensicherung von Eigentümern
|
|
app.post('/api/sicherung', async (req, res) => {
|
|
const { nachname, vorname, projekt_id, status } = req.body;
|
|
const targetStatus = status || 'Gesichert';
|
|
const schema = process.env.DB_SCHEMA || 'geodaten';
|
|
|
|
log(`Sicherungs-Request: ${vorname} ${nachname} Status='${targetStatus}' für Projekt ${projekt_id}`);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
|
|
// 1. Projekt-ID auflösen
|
|
const resolvedPid = await resolveProjectId(client, projekt_id);
|
|
if (!resolvedPid) {
|
|
throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
|
|
}
|
|
|
|
// 2. FSKs für den Namen finden (Behandlung von NULL vs leerem String)
|
|
const ownerRes = await client.query(
|
|
`SELECT "FSK" FROM ${schema}.flaecheneigentuemer_alkis
|
|
WHERE ("GNA" = $1 OR ("GNA" IS NULL AND $1 = ''))
|
|
AND ("VNA" = $2 OR ("VNA" IS NULL AND $2 = ''))`,
|
|
[nachname, vorname]
|
|
);
|
|
|
|
const fsks = ownerRes.rows.map(r => r.FSK);
|
|
if (fsks.length === 0) {
|
|
log(`Keine Flurstücke für ${vorname} ${nachname} gefunden.`);
|
|
await client.query('ROLLBACK');
|
|
return res.status(404).json({ error: 'Keine Flurstücke für diesen Namen gefunden.' });
|
|
}
|
|
|
|
log(`Gefunden: ${fsks.length} Flurstücke für ${vorname} ${nachname}. Prüfe Zuweisung...`);
|
|
|
|
// 3. Zuweisung prüfen und Status schreiben
|
|
let securedCount = 0;
|
|
for (const fsk of fsks) {
|
|
const assignmentRes = await client.query(
|
|
`SELECT 1 FROM ${schema}.flaecheneigentuemer_alkis_zuweisung WHERE fsk = $1 AND projekt_id = $2`,
|
|
[fsk, resolvedPid]
|
|
);
|
|
|
|
if (assignmentRes.rowCount > 0) {
|
|
await client.query(
|
|
`INSERT INTO ${schema}.flaecheneigentuemer_status (id, fsk, projekt_id, status, datum)
|
|
VALUES (gen_random_uuid(), $1, $2, $3, NOW())`,
|
|
[fsk, resolvedPid, targetStatus]
|
|
);
|
|
securedCount++;
|
|
}
|
|
}
|
|
|
|
await client.query('COMMIT');
|
|
log(`Erfolgreich: ${securedCount} von ${fsks.length} Flurstücken als '${targetStatus}' markiert.`);
|
|
res.json({
|
|
message: `${securedCount} Flurstücke für ${vorname} ${nachname} erfolgreich als '${targetStatus}' markiert.`,
|
|
count: securedCount,
|
|
total_found: fsks.length
|
|
});
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
log(`FEHLER bei Sicherung: ${err.message}`);
|
|
res.status(500).json({ error: 'Fehler bei der Flächensicherung', details: err.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// API zum Laden der Sicherungsstände
|
|
app.get('/api/sicherung/:projekt_id', async (req, res) => {
|
|
const { projekt_id } = req.params;
|
|
log(`Lade Sicherungsstände für Projekt: ${projekt_id}`);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
const resolvedPid = await resolveProjectId(client, projekt_id);
|
|
if (!resolvedPid) {
|
|
throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
|
|
}
|
|
|
|
const result = await client.query(
|
|
`SELECT vorname, nachname, aktueller_status as status
|
|
FROM geodaten.v_projekt_sicherung
|
|
WHERE projekt_id = $1`,
|
|
[resolvedPid]
|
|
);
|
|
|
|
log(`Geladen: ${result.rowCount} Status-Einträge.`);
|
|
res.json(result.rows);
|
|
} catch (e) {
|
|
log(`FEHLER beim Laden der Stände: ${e.message}`);
|
|
res.status(500).json({ error: e.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// API für Projekt-Statistiken (Fortschrittsanzeige)
|
|
app.get('/api/stats/:projekt_id', async (req, res) => {
|
|
const { projekt_id } = req.params;
|
|
log(`Lade Statistiken für Projekt: ${projekt_id}`);
|
|
|
|
const client = await pool.connect();
|
|
try {
|
|
const resolvedPid = await resolveProjectId(client, projekt_id);
|
|
if (!resolvedPid) {
|
|
throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
|
|
}
|
|
|
|
// Verwende die vom User vorgeschlagene Logik, aber mit resolvedPid
|
|
// Wir gruppieren nach aktueller_status (da dies in der View v_projekt_sicherung so heißt)
|
|
const result = await client.query(
|
|
`SELECT
|
|
aktueller_status as status,
|
|
count(*) as anzahl,
|
|
ROUND(SUM(ST_Area(geom)) / 10000, 2) as hektar
|
|
FROM geodaten.v_projekt_sicherung
|
|
WHERE projekt_id = $1
|
|
GROUP BY aktueller_status`,
|
|
[resolvedPid]
|
|
);
|
|
|
|
log(`Statistiken berechnet: ${result.rows.length} Status-Gruppen.`);
|
|
res.json(result.rows);
|
|
} catch (e) {
|
|
log(`FEHLER beim Laden der Statistiken: ${e.message}`);
|
|
res.status(500).json({ error: e.message });
|
|
} finally {
|
|
client.release();
|
|
}
|
|
});
|
|
|
|
// Catch-all route to serve index.html for any other request (optional, good for SPAs)
|
|
app.get('*', (req, res) => {
|
|
res.sendFile(path.join(__dirname, 'index.html'));
|
|
});
|
|
|
|
app.listen(port, '0.0.0.0', () => {
|
|
console.log(`Server running at http://0.0.0.0:${port}`);
|
|
});
|