feat: link project to postgres database (geodaten.wea_standorte) and add save button

This commit is contained in:
Johannes Baumeister 2026-04-28 12:17:32 +02:00
parent 5682c076c1
commit 4107a62167
4 changed files with 142 additions and 3 deletions

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
node_modules/
.env
backend/node_modules/
backend/check_db.py
backend/check_db.js
backend/check_db2.js
backend/check_db_admin.js
backend/list_tables.js
backend/check_columns.js
backend/setup_db.py
.vscode/
*.log

38
app.js
View File

@ -1414,9 +1414,45 @@ document.addEventListener('DOMContentLoaded', async () => {
a.download = `WindPlan_Projekt_${new Date().toLocaleDateString()}.json`;
a.click();
URL.revokeObjectURL(url);
document.getElementById('statusInfo').innerText = "Projekt exportiert.";
document.getElementById('statusInfo').innerText = "Projekt lokal exportiert.";
});
const btnSaveDB = document.getElementById('btnSaveDB');
if (btnSaveDB) {
btnSaveDB.addEventListener('click', async () => {
const statusEl = document.getElementById('statusInfo');
statusEl.innerText = "Speichere in Datenbank...";
const project_id = "BWSamern-Ohne"; // Could be dynamic from config
const turbineData = state.turbines.map(t => ({
nr: t.nr,
variant: t.variant,
type: t.type,
rd: t.rd,
hh: t.hh,
latlng: t.layers.marker.getLatLng()
}));
try {
const response = await fetch('http://localhost:3000/api/wea', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ projekt_id, turbines: turbineData })
});
if (response.ok) {
const result = await response.json();
statusEl.innerHTML = `<span style="color: #2ecc71;">${result.message}</span>`;
} else {
throw new Error("Fehler beim Speichern in DB");
}
} catch (err) {
console.error(err);
statusEl.innerHTML = `<span style="color: #ff4444;">Fehler beim DB-Sync: ${err.message}</span>`;
}
});
}
btnLoad.addEventListener('click', () => projectInput.click());
projectInput.addEventListener('change', (e) => {

90
backend/server.js Normal file
View File

@ -0,0 +1,90 @@
require('dotenv').config();
const express = require('express');
const { Pool } = require('pg');
const bodyParser = require('body-parser');
const cors = require('cors');
const app = express();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(bodyParser.json());
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
});
// Test DB Connection
pool.connect()
.then(() => console.log('Connected to PostgreSQL (enwelo_db) successfully'))
.catch(err => console.error('Connection error', err.stack));
// API to save turbines
app.post('/api/wea', async (req, res) => {
const { projekt_id, turbines } = req.body;
const client = await pool.connect();
try {
await client.query('BEGIN');
// Delete existing turbines for this project before saving new state
// (This ensures we always have the latest state on 'Save')
await client.query('DELETE FROM geodaten.wea_standorte WHERE projekt_id = $1', [projekt_id]);
for(let t of turbines) {
// Mapping frontend data to geodaten.wea_standorte columns:
// wea_nummer, hersteller, anlagentyp, nabenhoehe, rotordurchmesser, ksf_drehung, projekt_id, geom
await client.query(
`INSERT INTO geodaten.wea_standorte (
wea_nummer, hersteller, anlagentyp, nabenhoehe, rotordurchmesser, ksf_drehung, projekt_id, geom
) VALUES ($1, $2, $3, $4, $5, $6, $7, ST_SetSRID(ST_MakePoint($8, $9), 4326))`,
[
t.nr,
t.hersteller || '',
t.type,
parseInt(t.hh),
parseInt(t.rd),
parseInt(t.ksfAngle || 0),
projekt_id,
t.latlng.lng,
t.latlng.lat
]
);
}
await client.query('COMMIT');
res.status(200).json({ message: 'Windenergieanlagen erfolgreich in Datenbank gespeichert' });
} catch (e) {
await client.query('ROLLBACK');
console.error('Error saving turbines', e);
res.status(500).json({ error: e.message });
} finally {
client.release();
}
});
// API to load turbines
app.get('/api/wea/:projekt_id', async (req, res) => {
const { projekt_id } = req.params;
try {
const result = await pool.query(
`SELECT wea_nummer as nr, anlagentyp as type, nabenhoehe as hh, rotordurchmesser as rd, ksf_drehung as ksfAngle,
ST_X(geom) as lng, ST_Y(geom) as lat
FROM geodaten.wea_standorte WHERE projekt_id = $1`,
[projekt_id]
);
res.status(200).json(result.rows);
} catch (e) {
console.error('Error loading turbines', e);
res.status(500).json({ error: e.message });
}
});
app.listen(port, () => {
console.log(`Backend server listening at http://localhost:${port}`);
});

View File

@ -120,8 +120,9 @@
<div class="section">
<h2>Projektverwaltung</h2>
<button class="btn-secondary" id="btnSaveProject">Projekt speichern</button>
<button class="btn-secondary" id="btnLoadProject">Projekt laden</button>
<button class="btn-secondary" id="btnSaveProject">Projekt speichern (Lokal)</button>
<button class="btn-primary" id="btnSaveDB">In Datenbank speichern</button>
<button class="btn-secondary" id="btnLoadProject">Projekt laden (Lokal)</button>
<input type="file" id="projectInput" style="display: none;" accept=".json">
</div>