Files
espid2sensor/server.js
2025-08-13 15:44:29 +00:00

267 lines
8.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import express from 'express';
import session from 'express-session';
import bcrypt from 'bcrypt';
import { MongoClient, ObjectId } from 'mongodb';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 3000;
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017';
const DB_NAME = process.env.DB_NAME || 'espdb';
const SESSION_SECRET = process.env.SESSION_SECRET || 'supersecret';
let db, entriesCollection, usersCollection;
// Middleware
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: { maxAge: 24 * 60 * 60 * 1000 }
}));
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
// MongoDB verbinden
async function initMongo() {
const client = new MongoClient(MONGO_URI);
await client.connect();
db = client.db(DB_NAME);
entriesCollection = db.collection('entries');
usersCollection = db.collection('users');
console.log(`MongoDB verbunden: ${MONGO_URI}/${DB_NAME}`);
}
await initMongo();
// Login-Middleware
function requireLogin(req, res, next) {
if (req.session.userId) return next();
res.redirect('/login');
}
// Auth-Routen
app.get('/api/check-email', async (req, res) => {
const email = (req.query.email || '').toLowerCase().trim();
if (!email) return res.json({ exists: false });
try {
const existingUser = await usersCollection.findOne({ email });
res.json({ exists: !!existingUser });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Fehler bei der E-Mail-Prüfung' });
}
});
app.get('/register', (req, res) => res.render('register', { error: null }));
app.post('/register', async (req, res) => {
const { email, password } = req.body;
if (!email || !password) return res.render('register', { error: 'Bitte Email und Passwort angeben.' });
const existingUser = await usersCollection.findOne({ email: email.toLowerCase() });
if (existingUser) return res.render('register', { error: 'Email schon registriert.' });
const hash = await bcrypt.hash(password, 10);
await usersCollection.insertOne({ email: email.toLowerCase(), passwordHash: hash });
res.redirect('/login');
});
app.get('/login', (req, res) => res.render('login', { error: null }));
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await usersCollection.findOne({ email: email.toLowerCase() });
if (!user) return res.render('login', { error: 'Falsche Email oder Passwort.' });
const match = await bcrypt.compare(password, user.passwordHash);
if (!match) return res.render('login', { error: 'Falsche Email oder Passwort.' });
req.session.userId = user._id;
res.redirect('/');
});
app.get('/logout', (req, res) => {
req.session.destroy(() => res.redirect('/login'));
});
// Hauptseite
app.get('/', requireLogin, (req, res) => res.render('index'));
// API-Routen (Beispiel)
app.post('/api/save', requireLogin, async (req, res) => {
let { espId, sensorNumber, name, description, address } = req.body;
if (!espId || !sensorNumber) {
return res.json({ error: 'ESP-ID und Sensornummer sind Pflichtfelder' });
}
sensorNumber = parseInt(sensorNumber, 10); // als Integer speichern
try {
const doc = {
espId,
sensorNumber,
name: name || '',
description: description || '',
address: address || '',
createdAt: new Date()
};
await entriesCollection.insertOne(doc);
res.json({ success: true });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Fehler beim Speichern' });
}
});
// Neuer Endpoint zum Bearbeiten
app.put('/api/update/:id', requireLogin, async (req, res) => {
const { id } = req.params;
let { espId, sensorNumber, name, description, address } = req.body;
if (!espId || !sensorNumber) {
return res.json({ error: 'ESP-ID und Sensornummer sind Pflichtfelder' });
}
sensorNumber = parseInt(sensorNumber, 10);
try {
await entriesCollection.updateOne(
{ _id: new ObjectId(id) },
{ $set: { espId, sensorNumber, name, description, address } }
);
res.json({ success: true });
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Fehler beim Aktualisieren' });
}
});
app.get('/api/list', requireLogin, async (req, res) => {
const { id } = req.query;
if (id) {
try {
const item = await entriesCollection.findOne({ _id: new ObjectId(id) });
if (item) return res.json([item]);
return res.json([]);
} catch (err) {
console.error(err);
return res.status(500).json({ error: 'Fehler beim Laden' });
}
}
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 10;
const skip = (page - 1) * limit;
try {
const items = await entriesCollection.find({})
.sort({ createdAt: -1 })
.skip(skip)
.limit(limit)
.toArray();
res.json(items);
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Fehler beim Laden' });
}
});
app.delete('/api/delete/:id', requireLogin, async (req, res) => {
await entriesCollection.deleteOne({ _id: new ObjectId(req.params.id) });
res.json({ success: true });
});
// Dummy-Funktion - hier später Logik implementieren
function getAddress(sensorNumber) {
// Beispiel: feste Testwerte oder Datenbank-Logik
const addressMap = {
1001: 'Musterstraße 1, 12345 Musterstadt',
2002: 'Beispielweg 5, 54321 Beispielstadt'
};
return addressMap[sensorNumber] || 'Adresse nicht gefunden';
}
// .env (optional): ADDRESS_SERVICE_URL=https://noise.fuerst-stuttgart.de/srv/getaddress
const ADDRESS_SERVICE_URL = process.env.ADDRESS_SERVICE_URL
|| 'https://noise.fuerst-stuttgart.de/srv/getaddress';
// kleine Fetch-Helferfunktion mit Timeout
async function fetchWithTimeout(url, ms = 5000) {
const ctrl = new AbortController();
const id = setTimeout(() => ctrl.abort(), ms);
try {
const res = await fetch(url, { signal: ctrl.signal, headers: { 'Accept': 'application/json, text/plain;q=0.9, */*;q=0.8' } });
return res;
} finally {
clearTimeout(id);
}
}
const normalizeAddressPayload = (contentType, payload) => {
try {
if (contentType.includes('application/json')) {
const data = payload; // schon geparst
if (typeof data === 'string') return data.trim();
if (data?.address) return String(data.address).trim();
if (data?.addr) return String(data.addr).trim();
if (data?.result?.address) return String(data.result.address).trim();
// Fallback: JSON zu String
return JSON.stringify(data);
} else {
// Text-Antwort
return String(payload).trim();
}
} catch {
return '';
}
};
// /api/address/:sensorNumber holt Adresse als String "Straße, PLZ Stadt"
app.get('/api/address/:sensorNumber', requireLogin, async (req, res) => {
const sensorNumber = parseInt(req.params.sensorNumber, 10);
if (isNaN(sensorNumber)) {
return res.status(400).json({ error: 'Ungültige Sensornummer' });
}
const url = `https://noise.fuerst-stuttgart.de/srv/getaddress?sensorid=${encodeURIComponent(sensorNumber)}`;
try {
const r = await fetch(url, { headers: { 'Accept': 'application/json' } });
if (!r.ok) {
return res.status(502).json({ error: `Adressdienst Fehler (${r.status})` });
}
const data = await r.json();
// Erwartete Struktur: { erg: { address: { street, plz, city } } }
const addrObj = data?.erg?.address || data?.address || {};
const street = addrObj.street ?? '';
const plz = addrObj.plz ?? '';
const city = addrObj.city ?? '';
const rightPart = [plz, city].filter(Boolean).join(' ').trim();
const addressString = [street, rightPart].filter(Boolean).join(', ');
return res.json({
address: addressString, // <- vom Client direkt ins Inputfeld
parts: { street, plz, city } // optional, falls du es später brauchst
});
} catch (err) {
console.error('Address lookup failed:', err);
return res.status(504).json({ error: 'Adressdienst nicht erreichbar' });
}
});
app.listen(PORT, () => console.log(`Server läuft auf http://localhost:${PORT}`));