Add QR code tracker to admin panel

Each QR code gets a /qr/<slug> redirect that logs scans (IP, user agent,
timestamp) to disk. The admin QR Codes view lets you add, edit, enable/disable,
and delete codes, with per-code scan history inline.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
nmemmert
2026-06-16 09:33:35 -04:00
parent 37fc69680e
commit 48c2f81d00
6 changed files with 308 additions and 2 deletions
+27
View File
@@ -20,6 +20,7 @@ import {
STUDY_COMMENTS_FILE,
STUDY_CERTIFICATES_FILE,
EPISODE_SCRIPTS_FILE,
QR_CODES_FILE,
REPLY_TEMPLATES_FILE,
REPLY_HISTORY_FILE,
PODCAST_CHECKLIST_FILE,
@@ -1216,3 +1217,29 @@ export function sanitizeStudyCommunityPosts(value) {
})
.filter(Boolean)
}
// ── QR Codes ───────────────────────────────────────────────────────────────
export function queueQrCodesWrite() {
state.qrCodesWritePromise = state.qrCodesWritePromise
.then(async () => {
await mkdir(DATA_DIR, { recursive: true })
await writeFile(QR_CODES_FILE, JSON.stringify({ codes: state.qrCodes, scans: state.qrScans }, null, 2), 'utf8')
})
.catch(err => {
console.error('[qr-codes] failed to write:', err)
})
}
export function loadQrCodesFromDisk() {
return readFile(QR_CODES_FILE, 'utf8')
.then(raw => {
const parsed = JSON.parse(raw)
state.qrCodes = Array.isArray(parsed?.codes) ? parsed.codes : []
state.qrScans = Array.isArray(parsed?.scans) ? parsed.scans : []
})
.catch(() => {
state.qrCodes = []
state.qrScans = []
})
}