NFC hinzugefügt

This commit is contained in:
cay 2026-03-27 14:21:37 +00:00
parent f6b60af78b
commit d5a1536bb5
4 changed files with 142 additions and 3 deletions

View File

@ -1343,3 +1343,36 @@ body:not(.admin-body) > * {
}
.invoice-history-row:last-child td { border-bottom: none; }
.invoice-history-row:hover td { background: #f8f8ff; cursor: default; }
/* ---- NFC / Zugangskarte ---- */
.token-wrap {
display: flex;
align-items: center;
gap: 10px;
background: #f1f0ff;
border: 1.5px solid #c4b5fd;
border-radius: 8px;
padding: 10px 14px;
}
.token-display {
font-family: monospace;
font-size: 0.88rem;
letter-spacing: 1px;
color: #4c1d95;
flex: 1;
word-break: break-all;
}
.token-regen-btn { flex-shrink: 0; }
.nfc-info-box {
margin-top: 14px;
background: #f0fdf4;
border: 1.5px solid #bbf7d0;
border-radius: 8px;
padding: 12px 16px;
font-size: 0.82rem;
color: #166534;
line-height: 1.6;
}
.nfc-info-box strong { display: block; margin-bottom: 6px; }
.nfc-info-box ol { padding-left: 18px; }

View File

@ -1,4 +1,5 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const bcrypt = require('bcryptjs');
const db = require('../config/database');
@ -289,4 +290,32 @@ router.post('/members/:id/pauses/:pauseId/delete', requireAdmin, async (req, res
}
});
// ===== NFC / ZUGANGSKARTE =====
router.post('/members/:id/regenerate-token', requireAdmin, async (req, res) => {
try {
const token = crypto.randomBytes(16).toString('hex');
await db.query(
'UPDATE memberships SET access_token = ? WHERE id = ?',
[token, req.params.id]
);
res.redirect(`/admin/members/${req.params.id}?success=Neuer+Token+generiert`);
} catch (err) {
res.redirect(`/admin/members/${req.params.id}?error=Fehler+beim+Generieren`);
}
});
router.post('/members/:id/update-nfc', requireAdmin, async (req, res) => {
const { nfc_uid } = req.body;
try {
await db.query(
'UPDATE memberships SET nfc_uid = ?, card_issued = 1, card_issued_at = NOW() WHERE id = ?',
[nfc_uid ? nfc_uid.trim().toUpperCase() : null, req.params.id]
);
res.redirect(`/admin/members/${req.params.id}?success=NFC+UID+gespeichert`);
} catch (err) {
res.redirect(`/admin/members/${req.params.id}?error=Fehler+beim+Speichern`);
}
});
module.exports = router;

View File

@ -1,4 +1,5 @@
const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const dns = require('dns').promises;
const db = require('../config/database');
@ -118,20 +119,23 @@ router.post('/submit-membership', async (req, res) => {
return res.json({ success: false, error: 'Ungültiger oder inaktiver Tarif.' });
}
// Zugangstoken generieren
const access_token = crypto.randomBytes(16).toString('hex');
// In DB speichern
await db.query(`
INSERT INTO memberships
(tariff_id, salutation, title, first_name, last_name, birth_date, email, phone,
street, address_addition, zip, city, bank_name, account_holder, iban,
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct, guardian_consent, is_minor)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
sepa_accepted, agb_accepted, datenschutz_accepted, data_correct, guardian_consent, is_minor, access_token)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
tariff_id, salutation, title || '', first_name, last_name, birth_date,
email, phone || '', street, address_addition || '', zip, city,
bank_name || '', account_holder || '', iban || '',
sepa_accepted ? 1 : 0, agb_accepted ? 1 : 0,
datenschutz_accepted ? 1 : 0, data_correct ? 1 : 0,
guardian_consent ? 1 : 0, is_minor
guardian_consent ? 1 : 0, is_minor, access_token
]);
res.json({ success: true });

View File

@ -365,6 +365,79 @@
</div>
</div>
<!-- ===== KARTE 5: Zugangskarte / NFC ===== -->
<div class="karte">
<div class="karte-header">
<span class="karte-icon">🔑</span>
<h3>Zugangskarte / NFC</h3>
<% if (member.card_issued) { %>
<span class="status-badge active" style="margin-left:auto">✅ Ausgegeben</span>
<% } else { %>
<span class="status-badge inactive" style="margin-left:auto">⚠️ Nicht ausgegeben</span>
<% } %>
</div>
<div class="karte-body">
<!-- Access Token -->
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Access Token <small>(auf NFC-Karte schreiben)</small></label>
<div class="token-wrap">
<code class="token-display"><%= member.access_token || '' %></code>
<form method="POST" action="/admin/members/<%= member.id %>/regenerate-token"
onsubmit="return confirm('Token neu generieren? Die alte Karte funktioniert dann nicht mehr!')">
<button type="submit" class="btn btn-sm btn-warning token-regen-btn"
title="Neuen Token generieren (z.B. bei Kartenverlust)">
🔄 Neu
</button>
</form>
</div>
</div>
</div>
<!-- NFC UID -->
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>NFC Karten-UID <small>(vom Lesegerät auslesen)</small></label>
<form method="POST" action="/admin/members/<%= member.id %>/update-nfc"
id="nfcForm" style="display:flex;gap:8px;align-items:flex-start">
<input type="text" name="nfc_uid"
value="<%= member.nfc_uid || '' %>"
placeholder="z.B. A1:B2:C3:D4"
class="karte-input karte-iban"
style="text-transform:uppercase;flex:1">
<button type="submit" class="btn btn-sm btn-primary" style="white-space:nowrap;margin-top:1px">
💾 Speichern
</button>
</form>
</div>
</div>
<% if (member.card_issued_at) { %>
<div class="karte-row">
<div class="karte-field karte-field-full">
<label>Karte ausgegeben am</label>
<input type="text"
value="<%= new Date(member.card_issued_at).toLocaleDateString('de-DE') %>"
disabled class="karte-input karte-readonly">
</div>
</div>
<% } %>
<!-- Info Box -->
<div class="nfc-info-box">
<strong> So wird die Karte eingerichtet:</strong>
<ol>
<li>Access Token mit NFC-Schreiber auf die Karte schreiben</li>
<li>Karten-UID mit dem Lesegerät auslesen und hier eintragen</li>
<li>Beim Zutritt: Lesegerät prüft UID + Token gegen die Datenbank</li>
</ol>
</div>
</div>
</div>
</div><!-- end karteikarte-grid -->
</form>