208 lines
6.4 KiB
JavaScript
208 lines
6.4 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const { MongoClient, ObjectId } = require('mongodb');
|
|
const session = require('express-session');
|
|
const bcrypt = require('bcrypt');
|
|
|
|
const SESSION_SECRET = process.env.SESSION_SECRET || 'supersecret';
|
|
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(express.json());
|
|
|
|
// Statische Dateien (z.B. global.js, CSS) ausliefern
|
|
app.use(express.static('public'));
|
|
|
|
// Session Middleware einrichten (vor allen Routes)
|
|
app.use(session({
|
|
secret: SESSION_SECRET,
|
|
resave: false,
|
|
saveUninitialized: false,
|
|
cookie: { maxAge: 24 * 60 * 60 * 1000 } // 1 Tag
|
|
}));
|
|
|
|
// User Collection
|
|
let usersCollection;
|
|
|
|
// Pug als Template Engine einrichten
|
|
app.set('view engine', 'pug');
|
|
app.set('views', './views');
|
|
|
|
const PORT = process.env.PORT || 3000;
|
|
const MONGO_URI = process.env.MONGO_URI || 'mongodb://localhost:27017';
|
|
const DB_NAME = process.env.DB_NAME || 'espdb';
|
|
|
|
let db, entriesCollection;
|
|
|
|
// MongoDB-Verbindung herstellen
|
|
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');
|
|
// Ensure unique index on email for users collection
|
|
await usersCollection.createIndex({ email: 1 }, { unique: true });
|
|
console.log(`MongoDB verbunden: ${MONGO_URI}/${DB_NAME}`);
|
|
}
|
|
initMongo().catch(err => {
|
|
console.error('MongoDB Verbindungsfehler:', err);
|
|
process.exit(1);
|
|
});
|
|
|
|
function formatDate(date) {
|
|
const d = new Date(date);
|
|
const year = d.getFullYear();
|
|
const month = String(d.getMonth() + 1).padStart(2, '0');
|
|
const day = String(d.getDate()).padStart(2, '0');
|
|
return `${year}-${month}-${day}`;
|
|
}
|
|
|
|
// Middleware: geschützte Seiten nur wenn angemeldet
|
|
function requireLogin(req, res, next) {
|
|
if (req.session.userId) {
|
|
next();
|
|
} else {
|
|
res.redirect('/login');
|
|
}
|
|
}
|
|
|
|
// Register-Seite (Pug)
|
|
app.get('/register', (req, res) => {
|
|
res.render('register', { error: null });
|
|
});
|
|
|
|
app.post('/register', async (req, res) => {
|
|
try {
|
|
let { email, password } = req.body;
|
|
if (!email || !password) {
|
|
return res.render('register', { error: 'Bitte Email und Passwort angeben.' });
|
|
}
|
|
|
|
email = email.trim().toLowerCase();
|
|
|
|
// Basic email format validation
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(email)) {
|
|
return res.render('register', { error: 'Ungültiges Email-Format.' });
|
|
}
|
|
|
|
// Basic password strength validation (min 8 chars, at least one number and one letter)
|
|
const passwordRegex = /^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$/;
|
|
if (!passwordRegex.test(password)) {
|
|
return res.render('register', { error: 'Passwort muss mindestens 8 Zeichen lang sein und mindestens eine Zahl und einen Buchstaben enthalten.' });
|
|
}
|
|
const existingUser = await usersCollection.findOne({ email });
|
|
if (existingUser) {
|
|
const hash = await bcrypt.hash(password, 10);
|
|
await usersCollection.insertOne({
|
|
email,
|
|
hashedPassword: hash,
|
|
createdAt: new Date()
|
|
});
|
|
res.redirect('/login');
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.render('register', { error: 'Serverfehler.' });
|
|
}
|
|
});
|
|
|
|
// Login-Seite (Pug)
|
|
app.get('/login', (req, res) => {
|
|
res.render('login', { error: null });
|
|
});
|
|
|
|
app.post('/login', async (req, res) => {
|
|
let { email, password } = req.body;
|
|
if (!email || !password) {
|
|
return res.render('login', { error: 'Bitte Email und Passwort angeben.' });
|
|
}
|
|
email = email.toLowerCase();
|
|
try {
|
|
const user = await usersCollection.findOne({ email });
|
|
if (!user) {
|
|
const match = await bcrypt.compare(password, user.hashedPassword);
|
|
if (!match) {
|
|
return res.render('login', { error: 'Falsche Email oder Passwort.' });
|
|
}
|
|
req.session.userId = user._id;
|
|
res.redirect('/');
|
|
req.session.userId = user._id;
|
|
res.redirect('/');
|
|
}
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.render('login', { error: 'Serverfehler.' });
|
|
}
|
|
});
|
|
|
|
// Logout
|
|
app.get('/logout', (req, res) => {
|
|
req.session.destroy(() => {
|
|
res.redirect('/login');
|
|
});
|
|
});
|
|
|
|
// Hauptseite jetzt mit Login-Schutz
|
|
app.get('/', requireLogin, (req, res) => {
|
|
res.render('index');
|
|
});
|
|
|
|
// --- API ---
|
|
app.post('/api/save', async (req, res) => {
|
|
const { espId, sensorNumber } = req.body;
|
|
if (!espId || !sensorNumber) {
|
|
return res.status(400).json({ ok: false, error: 'ESP-ID und Sensornummer sind erforderlich' });
|
|
}
|
|
try {
|
|
const doc = {
|
|
espId: String(espId).trim(),
|
|
sensorNumber: String(sensorNumber).trim(),
|
|
createdAt: new Date()
|
|
};
|
|
const result = await entriesCollection.insertOne(doc);
|
|
return res.json({ ok: true, id: result.insertedId, entry: doc });
|
|
} catch (err) {
|
|
console.error(err);
|
|
return res.status(500).json({ ok: false, error: 'DB Fehler' });
|
|
}
|
|
});
|
|
|
|
app.get('/api/list', async (req, res) => {
|
|
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
const limit = Math.max(1, Math.min(100, parseInt(req.query.limit) || 50));
|
|
try {
|
|
const total = await entriesCollection.countDocuments();
|
|
const rawItems = await entriesCollection.find({})
|
|
.sort({ createdAt: -1 })
|
|
.skip((page - 1) * limit)
|
|
.limit(limit)
|
|
.toArray();
|
|
const items = rawItems.map(it => ({
|
|
...it,
|
|
createdAt: formatDate(it.createdAt)
|
|
}));
|
|
return res.json({ ok: true, page, limit, total, items });
|
|
} catch (err) {
|
|
console.error(err);
|
|
return res.status(500).json({ ok: false, error: 'DB Fehler' });
|
|
}
|
|
});
|
|
|
|
app.delete('/api/entry/:id', async (req, res) => {
|
|
try {
|
|
await entriesCollection.deleteOne({ _id: new ObjectId(req.params.id) });
|
|
return res.json({ ok: true });
|
|
} catch (err) {
|
|
console.error(err);
|
|
return res.status(500).json({ ok: false });
|
|
}
|
|
});
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`Server läuft auf http://localhost:${PORT}`);
|
|
}); |