feat: Add multi-project support with Authentik access headers and project selector
Deploy Standortplaner / deploy (push) Successful in 16s
Details
Deploy Standortplaner / deploy (push) Successful in 16s
Details
This commit is contained in:
parent
ba6ed0f058
commit
f4a590b859
158
app.js
158
app.js
|
|
@ -14,12 +14,13 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
showAuxiliary: true,
|
showAuxiliary: true,
|
||||||
isEditMode: false,
|
isEditMode: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
hasLoadedFromDB: false
|
hasLoadedFromDB: false,
|
||||||
|
currentProjektId: null,
|
||||||
|
allowedProjects: []
|
||||||
};
|
};
|
||||||
|
|
||||||
const getProjektId = () => {
|
const getProjektId = () => {
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
return state.currentProjektId || "BWSamern-Ohne";
|
||||||
return urlParams.get('project') || urlParams.get('projekt') || urlParams.get('projekt_id') || "BWSamern-Ohne";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS_MAP = {
|
const STATUS_MAP = {
|
||||||
|
|
@ -2019,16 +2020,46 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Dynamic Layers and then load data
|
// Dynamic projects loading and switching logic
|
||||||
initDynamicLayers().then(async () => {
|
async function switchProject(projId) {
|
||||||
|
state.currentProjektId = projId;
|
||||||
|
|
||||||
|
// Quietly update the project URL parameter without reloading
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('project', projId);
|
||||||
|
window.history.pushState({}, '', url);
|
||||||
|
|
||||||
|
const statusEl = document.getElementById('statusInfo');
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.innerText = `Projekt "${projId}" wird geladen...`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Clear existing turbines
|
||||||
|
state.turbines.forEach(t => {
|
||||||
try {
|
try {
|
||||||
// Erst Status laden
|
if (t.layers && t.variant && variantLayers[t.variant]) {
|
||||||
|
Object.values(t.layers).forEach(l => variantLayers[t.variant].removeLayer(l));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Fehler beim Entfernen alter WEA-Layer:", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
state.turbines = [];
|
||||||
|
|
||||||
|
// 2. Clear existing owner statuses
|
||||||
|
state.ownerStatuses = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 3. Load dynamic layers for new project
|
||||||
|
await initDynamicLayers();
|
||||||
|
|
||||||
|
// 4. Load owner statuses
|
||||||
await loadOwnerStatusesFromDB();
|
await loadOwnerStatusesFromDB();
|
||||||
|
|
||||||
// Dann WEAs laden
|
// 5. Load turbines
|
||||||
await loadTurbinesFromDB();
|
await loadTurbinesFromDB();
|
||||||
|
|
||||||
// Automatisches Mapping für den Eigentümer-Layer prüfen
|
// 6. Automatisches Mapping für den Eigentümer-Layer prüfen
|
||||||
const ownerLayerName = Object.keys(overlays).find(k => k.toLowerCase().includes('eigentümer'));
|
const ownerLayerName = Object.keys(overlays).find(k => k.toLowerCase().includes('eigentümer'));
|
||||||
if (ownerLayerName && overlays[ownerLayerName]) {
|
if (ownerLayerName && overlays[ownerLayerName]) {
|
||||||
const layer = overlays[ownerLayerName];
|
const layer = overlays[ownerLayerName];
|
||||||
|
|
@ -2047,7 +2078,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto-zoom to project parcels
|
// 7. Auto-zoom to project parcels
|
||||||
if (ownerLayerName && overlays[ownerLayerName]) {
|
if (ownerLayerName && overlays[ownerLayerName]) {
|
||||||
const layer = overlays[ownerLayerName];
|
const layer = overlays[ownerLayerName];
|
||||||
const layers = layer.getLayers();
|
const layers = layer.getLayers();
|
||||||
|
|
@ -2066,13 +2097,114 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
console.log(`Auto-Zoom auf ${matchedLayers.length} Flächen des Projekts ausgeführt.`);
|
console.log(`Auto-Zoom auf ${matchedLayers.length} Flächen des Projekts ausgeführt.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.innerText = `Projekt "${projId}" erfolgreich geladen.`;
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Fehler bei der Initialisierung des Planungstools:", e);
|
console.error("Fehler beim Umschalten des Projekts:", e);
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.innerHTML = `<span style="color: #ff3333;">Fehler beim Laden des Projekts: ${e.message}</span>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize allowed projects and project selector
|
||||||
|
async function initializeProjects() {
|
||||||
|
const statusEl = document.getElementById('statusInfo');
|
||||||
|
try {
|
||||||
|
if (statusEl) statusEl.innerText = "Benutzerdaten werden geprüft...";
|
||||||
|
|
||||||
|
const response = await fetch('/api/user/projekte');
|
||||||
|
if (!response.ok) {
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
throw new Error("Sie haben keine Berechtigung für dieses Planungstool.");
|
||||||
|
}
|
||||||
|
throw new Error(`HTTP Fehler ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
state.allowedProjects = data.projects || [];
|
||||||
|
|
||||||
|
if (state.allowedProjects.length === 0) {
|
||||||
|
throw new Error("Ihnen wurden in Authentik keine Projekte zugewiesen.");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Erlaubte Projekte geladen:`, state.allowedProjects);
|
||||||
|
|
||||||
|
// Determine initial project
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
const requestedProj = urlParams.get('project') || urlParams.get('projekt') || urlParams.get('projekt_id');
|
||||||
|
|
||||||
|
let initialProj = null;
|
||||||
|
if (requestedProj) {
|
||||||
|
// Check if requested project name or ID is in allowed list
|
||||||
|
const matches = state.allowedProjects.find(p =>
|
||||||
|
p.id.toLowerCase() === requestedProj.toLowerCase() ||
|
||||||
|
p.name.toLowerCase() === requestedProj.toLowerCase()
|
||||||
|
);
|
||||||
|
if (matches) {
|
||||||
|
initialProj = matches.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to first project if not found or not specified
|
||||||
|
if (!initialProj) {
|
||||||
|
initialProj = state.allowedProjects[0].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup project selector UI
|
||||||
|
const selectorContainer = document.getElementById('projectSelectorContainer');
|
||||||
|
const selector = document.getElementById('projectSelector');
|
||||||
|
|
||||||
|
if (state.allowedProjects.length > 1) {
|
||||||
|
// Populate dropdown
|
||||||
|
selector.innerHTML = '';
|
||||||
|
state.allowedProjects.forEach(p => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = p.id;
|
||||||
|
opt.textContent = p.name;
|
||||||
|
selector.appendChild(opt);
|
||||||
|
});
|
||||||
|
selector.value = initialProj;
|
||||||
|
|
||||||
|
// Show dropdown container
|
||||||
|
if (selectorContainer) {
|
||||||
|
selectorContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle change event
|
||||||
|
selector.addEventListener('change', async (e) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
await switchProject(e.target.value);
|
||||||
|
state.isLoading = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Hide dropdown container if only one project is allowed
|
||||||
|
if (selectorContainer) {
|
||||||
|
selectorContainer.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perform initial load
|
||||||
|
await switchProject(initialProj);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Initialisierungsfehler:", err);
|
||||||
|
if (statusEl) {
|
||||||
|
statusEl.innerHTML = `<b style="color: #ff3333;">Zugriff verweigert:</b> ${err.message}`;
|
||||||
|
}
|
||||||
|
// Block edit interactions
|
||||||
|
state.isEditMode = false;
|
||||||
|
const btnEdit = document.getElementById('btnToggleEditMode');
|
||||||
|
if (btnEdit) btnEdit.disabled = true;
|
||||||
} finally {
|
} finally {
|
||||||
state.isLoading = false;
|
state.isLoading = false;
|
||||||
console.log("Planungstool initialisiert, isLoading = false");
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
|
// Start loading process
|
||||||
|
initializeProjects();
|
||||||
|
|
||||||
// Project Persistence
|
// Project Persistence
|
||||||
const btnSave = document.getElementById('btnSaveProject');
|
const btnSave = document.getElementById('btnSaveProject');
|
||||||
|
|
@ -2190,7 +2322,6 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const dbTurbines = await response.json();
|
const dbTurbines = await response.json();
|
||||||
console.log(`Datenbank-Response: ${dbTurbines.length} WEAs erhalten.`);
|
console.log(`Datenbank-Response: ${dbTurbines.length} WEAs erhalten.`);
|
||||||
|
|
||||||
if (dbTurbines.length > 0) {
|
|
||||||
// Clear existing turbines safely
|
// Clear existing turbines safely
|
||||||
state.turbines.forEach(t => {
|
state.turbines.forEach(t => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -2203,6 +2334,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||||
});
|
});
|
||||||
state.turbines = [];
|
state.turbines = [];
|
||||||
|
|
||||||
|
if (dbTurbines && dbTurbines.length > 0) {
|
||||||
dbTurbines.forEach(t => {
|
dbTurbines.forEach(t => {
|
||||||
try {
|
try {
|
||||||
const latlng = L.latLng(t.lat, t.lng);
|
const latlng = L.latLng(t.lat, t.lng);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,12 @@
|
||||||
<div class="sidebar-header" style="display: flex; flex-direction: column; align-items: center; gap: 15px;">
|
<div class="sidebar-header" style="display: flex; flex-direction: column; align-items: center; gap: 15px;">
|
||||||
<img src="Logos/20201202-ENWELO-Logo-4c_ohne_Claim.png" alt="ENWELO Logo" style="max-width: 85%; height: auto; display: block;">
|
<img src="Logos/20201202-ENWELO-Logo-4c_ohne_Claim.png" alt="ENWELO Logo" style="max-width: 85%; height: auto; display: block;">
|
||||||
<h1 style="margin: 0; text-align: center;">WindPlaner</h1>
|
<h1 style="margin: 0; text-align: center;">WindPlaner</h1>
|
||||||
|
<div id="projectSelectorContainer" style="width: 100%; display: none; flex-direction: column; gap: 4px;">
|
||||||
|
<label for="projectSelector" class="label-small" style="font-size: 0.75rem; color: var(--text-dim); text-align: left; width: 100%;">Projekt wechseln</label>
|
||||||
|
<select id="projectSelector" class="input-styled" style="width: 100%; font-size: 0.9rem; padding: 6px; border-radius: 4px;">
|
||||||
|
<!-- Wird dynamisch per JS befüllt -->
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
|
|
|
||||||
227
server.js
227
server.js
|
|
@ -35,6 +35,143 @@ function log(msg) {
|
||||||
if (serverLogs.length > 50) serverLogs.shift();
|
if (serverLogs.length > 50) serverLogs.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion zur Ermittlung der erlaubten Projekte aus Authentik-Headern
|
||||||
|
function getAllowedProjects(req) {
|
||||||
|
const isProd = process.env.NODE_ENV === 'production';
|
||||||
|
const username = req.headers['x-authentik-username'];
|
||||||
|
const projectsHeader = req.headers['x-authentik-meta-projekte'];
|
||||||
|
|
||||||
|
if (!username) {
|
||||||
|
// Lokale Entwicklung (Fallback)
|
||||||
|
if (!isProd) {
|
||||||
|
return {
|
||||||
|
username: 'local-dev-user',
|
||||||
|
projects: ['*'], // Sternchen bedeutet Zugriff auf alle Projekte
|
||||||
|
isLocalDev: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// Im Produktivmodus ist ein fehlender Header ein Fehler
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Projekte aus Header parsen (Komma-separiert)
|
||||||
|
let allowedProjects = [];
|
||||||
|
if (projectsHeader) {
|
||||||
|
allowedProjects = projectsHeader.split(',').map(p => p.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
username,
|
||||||
|
projects: allowedProjects,
|
||||||
|
isLocalDev: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hilfsfunktion zur Berechtigungsprüfung eines bestimmten Projekts
|
||||||
|
async function checkProjectAccess(req, requestedProjectIdentifier, client) {
|
||||||
|
const auth = getAllowedProjects(req);
|
||||||
|
if (!auth) return false;
|
||||||
|
|
||||||
|
// Wenn der Benutzer ein Admin/Entwickler mit '*' Wildcard ist, darf er alles sehen
|
||||||
|
if (auth.projects.includes('*')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!requestedProjectIdentifier) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zuerst prüfen, ob die angeforderte ID/Name direkt im Header steht
|
||||||
|
const normalizedRequested = requestedProjectIdentifier.toLowerCase().trim();
|
||||||
|
const hasDirectAccess = auth.projects.some(p => p.toLowerCase().trim() === normalizedRequested);
|
||||||
|
if (hasDirectAccess) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wenn kein direkter Treffer vorliegt, müssen wir prüfen, ob die UUID in der Datenbank aufgelöst
|
||||||
|
// und mit den erlaubten Projektnamen im Header übereinstimmt.
|
||||||
|
try {
|
||||||
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
||||||
|
let projectId = null;
|
||||||
|
let projectName = null;
|
||||||
|
|
||||||
|
if (uuidRegex.test(requestedProjectIdentifier)) {
|
||||||
|
const res = await client.query('SELECT id, name FROM geodaten.projekte WHERE id = $1', [requestedProjectIdentifier]);
|
||||||
|
if (res.rows.length > 0) {
|
||||||
|
projectId = res.rows[0].id;
|
||||||
|
projectName = res.rows[0].name;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const normalized = requestedProjectIdentifier.toLowerCase().replace(/[-_]/g, '');
|
||||||
|
const res = await client.query(
|
||||||
|
'SELECT id, name FROM geodaten.projekte WHERE LOWER(REPLACE(REPLACE(name, \'-\', \'\'), \'_\', \'\')) = $1',
|
||||||
|
[normalized]
|
||||||
|
);
|
||||||
|
if (res.rows.length > 0) {
|
||||||
|
projectId = res.rows[0].id;
|
||||||
|
projectName = res.rows[0].name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!projectId) return false;
|
||||||
|
|
||||||
|
// Prüfen, ob die aufgelöste ID oder der Projektname in der erlaubten Liste steht
|
||||||
|
const match = auth.projects.some(p => {
|
||||||
|
const normalizedP = p.toLowerCase().trim();
|
||||||
|
return normalizedP === projectId.toLowerCase() ||
|
||||||
|
normalizedP === (projectName || '').toLowerCase().trim();
|
||||||
|
});
|
||||||
|
|
||||||
|
return match;
|
||||||
|
} catch (e) {
|
||||||
|
log(`Fehler bei der Berechtigungsprüfung: ${e.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API-Endpunkt zur Rückgabe der erlaubten Projekte des angemeldeten Nutzers
|
||||||
|
app.get('/api/user/projekte', async (req, res) => {
|
||||||
|
const auth = getAllowedProjects(req);
|
||||||
|
if (!auth) {
|
||||||
|
log('Unauthorized request to /api/user/projekte (missing headers)');
|
||||||
|
return res.status(401).json({ error: 'Nicht autorisiert. Authentik-Header fehlen.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
log(`Lade erlaubte Projekte für User: ${auth.username} (Projekte: ${auth.projects.join(', ')})`);
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
let projects = [];
|
||||||
|
|
||||||
|
if (auth.projects.includes('*')) {
|
||||||
|
const result = await client.query('SELECT id, name FROM geodaten.projekte ORDER BY name');
|
||||||
|
projects = result.rows;
|
||||||
|
} else if (auth.projects.length > 0) {
|
||||||
|
const originalIds = auth.projects.filter(p => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(p));
|
||||||
|
const normalizedNames = auth.projects.map(p => p.toLowerCase().replace(/[-_]/g, '').trim());
|
||||||
|
const result = await client.query(
|
||||||
|
`SELECT id, name FROM geodaten.projekte
|
||||||
|
WHERE id::text = ANY($1::text[])
|
||||||
|
OR LOWER(REPLACE(REPLACE(name, '-', ''), '_', '')) = ANY($2::text[])
|
||||||
|
ORDER BY name`,
|
||||||
|
[originalIds, normalizedNames]
|
||||||
|
);
|
||||||
|
projects = result.rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
username: auth.username,
|
||||||
|
projects: projects,
|
||||||
|
isLocalDev: auth.isLocalDev
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
log(`FEHLER beim Laden der erlaubten Projekte: ${err.message}`);
|
||||||
|
res.status(500).json({ error: 'Datenbankfehler beim Laden der Projekte', details: err.message });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Status-Endpunkt für Diagnose
|
// Status-Endpunkt für Diagnose
|
||||||
app.get('/api/status', async (req, res) => {
|
app.get('/api/status', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
@ -64,6 +201,12 @@ app.post('/api/wea', async (req, res) => {
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, targetProject, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Save-Request Projekt: ${targetProject}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung zum Speichern in diesem Projekt.' });
|
||||||
|
}
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
// 1. Bestehende WEAs für dieses Projekt löschen
|
// 1. Bestehende WEAs für dieses Projekt löschen
|
||||||
|
|
@ -102,8 +245,15 @@ app.post('/api/wea', async (req, res) => {
|
||||||
app.get('/api/wea/:projekt_id', async (req, res) => {
|
app.get('/api/wea/:projekt_id', async (req, res) => {
|
||||||
const { projekt_id } = req.params;
|
const { projekt_id } = req.params;
|
||||||
log(`Lade WEAs für Projekt: ${projekt_id}`);
|
log(`Lade WEAs für Projekt: ${projekt_id}`);
|
||||||
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(
|
const hasAccess = await checkProjectAccess(req, projekt_id, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Laden WEA Projekt: ${projekt_id}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await client.query(
|
||||||
`SELECT wea_nummer as nr, hersteller as hersteller, anlagentyp as type, nabenhoehe as hh, rotordurchmesser as rd, ksf_drehung as ksfAngle, variante as variant,
|
`SELECT wea_nummer as nr, hersteller as hersteller, 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
|
ST_X(ST_Transform(geom, 4326)) as lng, ST_Y(ST_Transform(geom, 4326)) as lat
|
||||||
FROM geodaten.wea_standorte WHERE projekt_id = $1`,
|
FROM geodaten.wea_standorte WHERE projekt_id = $1`,
|
||||||
|
|
@ -114,6 +264,8 @@ app.get('/api/wea/:projekt_id', async (req, res) => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`FEHLER beim Laden: ${e.message}`);
|
log(`FEHLER beim Laden: ${e.message}`);
|
||||||
res.status(500).json({ error: e.message });
|
res.status(500).json({ error: e.message });
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -137,14 +289,28 @@ async function resolveProjectId(client, input) {
|
||||||
|
|
||||||
// API zur Flächensicherung von Eigentümern
|
// API zur Flächensicherung von Eigentümern
|
||||||
app.post('/api/sicherung', async (req, res) => {
|
app.post('/api/sicherung', async (req, res) => {
|
||||||
const { nachname, vorname, status, notiz } = req.body;
|
const { nachname, vorname, status, notiz, projekt_id } = req.body;
|
||||||
const targetStatus = status || 'Gesichert';
|
const targetStatus = status || 'Gesichert';
|
||||||
const schema = process.env.DB_SCHEMA || 'geodaten';
|
const schema = process.env.DB_SCHEMA || 'geodaten';
|
||||||
|
|
||||||
log(`Sicherungs-Request: ${vorname} ${nachname} Status='${targetStatus}' Notiz='${notiz}'`);
|
log(`Sicherungs-Request: ${vorname} ${nachname} Status='${targetStatus}' Notiz='${notiz}' für Projekt: ${projekt_id || 'unbekannt'}`);
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
if (projekt_id) {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projekt_id, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Sicherungs-Request in Projekt: ${projekt_id}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung zur Flächensicherung in diesem Projekt.' });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const auth = getAllowedProjects(req);
|
||||||
|
if (!auth || (auth.projects.length === 0 && !auth.projects.includes('*'))) {
|
||||||
|
log('Zugriff verweigert: Keine zugewiesenen Projekte vorhanden');
|
||||||
|
return res.status(403).json({ error: 'Zugriff verweigert. Sie haben keine Projekte zugewiesen.' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await client.query('BEGIN');
|
await client.query('BEGIN');
|
||||||
|
|
||||||
// Suche nach dem Eigentümer in flaecheneigentuemer_alkis und aktualisiere status und notiz
|
// Suche nach dem Eigentümer in flaecheneigentuemer_alkis und aktualisiere status und notiz
|
||||||
|
|
@ -191,11 +357,23 @@ app.get('/api/sicherung/:projekt_id', async (req, res) => {
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
// Wir laden nun direkt aus der ALKIS Tabelle, wie vom User gewünscht
|
const hasAccess = await checkProjectAccess(req, projekt_id, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Laden Sicherungsstände Projekt: ${projekt_id}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedPid = await resolveProjectId(client, projekt_id);
|
||||||
|
if (!resolvedPid) {
|
||||||
|
return res.json([]);
|
||||||
|
}
|
||||||
|
|
||||||
const result = await client.query(
|
const result = await client.query(
|
||||||
`SELECT "VNA" as vorname, "GNA" as nachname, status, notiz
|
`SELECT a."VNA" as vorname, a."GNA" as nachname, a.status, a.notiz
|
||||||
FROM geodaten.flaecheneigentuemer_alkis
|
FROM geodaten.flaecheneigentuemer_alkis a
|
||||||
WHERE status IS NOT NULL OR notiz IS NOT NULL`
|
JOIN geodaten.flaecheneigentuemer_alkis_zuweisung z ON a."FSK" = z.fsk
|
||||||
|
WHERE z.projekt_id = $1 AND (a.status IS NOT NULL OR a.notiz IS NOT NULL)`,
|
||||||
|
[resolvedPid]
|
||||||
);
|
);
|
||||||
|
|
||||||
log(`Geladen: ${result.rowCount} Status-Einträge aus ALKIS.`);
|
log(`Geladen: ${result.rowCount} Status-Einträge aus ALKIS.`);
|
||||||
|
|
@ -214,6 +392,11 @@ app.get('/api/layers/alkis', async (req, res) => {
|
||||||
log(`Lade ALKIS-Layer aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
log(`Lade ALKIS-Layer aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projectParam, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für ALKIS-Layer in Projekt: ${projectParam}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
let query = `
|
let query = `
|
||||||
SELECT jsonb_build_object(
|
SELECT jsonb_build_object(
|
||||||
'type', 'Feature',
|
'type', 'Feature',
|
||||||
|
|
@ -273,6 +456,11 @@ app.get('/api/layers/artennachweis', async (req, res) => {
|
||||||
log(`Lade Artennachweis-Layer aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
log(`Lade Artennachweis-Layer aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projectParam, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Artennachweis-Layer in Projekt: ${projectParam}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
let whereClause = '';
|
let whereClause = '';
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
|
|
@ -320,6 +508,11 @@ app.get('/api/layers/artennachweise_restriktionen', async (req, res) => {
|
||||||
log(`Lade Artennachweise-Restriktionen aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
log(`Lade Artennachweise-Restriktionen aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projectParam, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Restriktionen in Projekt: ${projectParam}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
let whereClause = '';
|
let whereClause = '';
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
|
|
@ -364,6 +557,11 @@ app.get('/api/layers/artennachweise_restriktionen', async (req, res) => {
|
||||||
|
|
||||||
// NEU: API zum Laden der Leitungsverläufe (Freileitung, Wasserstoff etc.)
|
// NEU: API zum Laden der Leitungsverläufe (Freileitung, Wasserstoff etc.)
|
||||||
app.get('/api/layers/leitungsverlaeufe', async (req, res) => {
|
app.get('/api/layers/leitungsverlaeufe', async (req, res) => {
|
||||||
|
const auth = getAllowedProjects(req);
|
||||||
|
if (!auth) {
|
||||||
|
log('Unauthorized request to /api/layers/leitungsverlaeufe');
|
||||||
|
return res.status(401).json({ error: 'Nicht autorisiert. Authentik-Header fehlen.' });
|
||||||
|
}
|
||||||
log("Lade Leitungsverläufe aus Datenbank...");
|
log("Lade Leitungsverläufe aus Datenbank...");
|
||||||
try {
|
try {
|
||||||
const result = await pool.query(
|
const result = await pool.query(
|
||||||
|
|
@ -418,6 +616,11 @@ app.get('/api/layers/gebaeude', async (req, res) => {
|
||||||
log(`Lade Gebäude aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
log(`Lade Gebäude aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projectParam, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Gebäude in Projekt: ${projectParam}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
let whereClause = '';
|
let whereClause = '';
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
|
|
@ -466,6 +669,11 @@ app.get('/api/layers/wohngebeaude', async (req, res) => {
|
||||||
log(`Lade Wohngebäude aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
log(`Lade Wohngebäude aus Datenbank für Projekt: ${projectParam || 'alle'}`);
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projectParam, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Wohngebäude in Projekt: ${projectParam}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
let whereClause = '';
|
let whereClause = '';
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
|
|
@ -515,6 +723,11 @@ app.get('/api/stats/:projekt_id', async (req, res) => {
|
||||||
|
|
||||||
const client = await pool.connect();
|
const client = await pool.connect();
|
||||||
try {
|
try {
|
||||||
|
const hasAccess = await checkProjectAccess(req, projekt_id, client);
|
||||||
|
if (!hasAccess) {
|
||||||
|
log(`Zugriff verweigert für Statistiken in Projekt: ${projekt_id}`);
|
||||||
|
return res.status(403).json({ error: 'Keine Berechtigung für dieses Projekt.' });
|
||||||
|
}
|
||||||
const resolvedPid = await resolveProjectId(client, projekt_id);
|
const resolvedPid = await resolveProjectId(client, projekt_id);
|
||||||
if (!resolvedPid) {
|
if (!resolvedPid) {
|
||||||
throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
|
throw new Error(`Projekt '${projekt_id}' konnte nicht gefunden werden.`);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue