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:last-child td { border-bottom: none; }
.invoice-history-row:hover td { background: #f8f8ff; cursor: default; } .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 express = require('express');
const crypto = require('crypto');
const router = express.Router(); const router = express.Router();
const bcrypt = require('bcryptjs'); const bcrypt = require('bcryptjs');
const db = require('../config/database'); 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; module.exports = router;

View File

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

View File

@ -365,6 +365,79 @@
</div> </div>
</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 --> </div><!-- end karteikarte-grid -->
</form> </form>