Public Access
Visitor sign in kiosk: multi-site, badge printing, WWCC expiry warnings, admin accounts with 2FA
This commit is contained in:
+19
-10
@@ -606,6 +606,7 @@ function shapeFrequent(row, includePin = false) {
|
||||
id: row.id,
|
||||
firstName: row.first_name,
|
||||
lastName: row.last_name,
|
||||
company: row.company,
|
||||
phone: row.phone,
|
||||
email: row.email,
|
||||
checkType: row.check_type,
|
||||
@@ -737,6 +738,7 @@ function validateFrequent(body, { existingPhone = null, existingId = null } = {}
|
||||
return {
|
||||
firstName,
|
||||
lastName,
|
||||
company: clean(body?.company, 80) || null,
|
||||
phone,
|
||||
email: email || null,
|
||||
checkType,
|
||||
@@ -759,13 +761,13 @@ router.post('/frequent', (req, res) => {
|
||||
const info = db
|
||||
.prepare(
|
||||
`INSERT INTO frequent_visitors
|
||||
(first_name, last_name, phone, email, check_type, check_number, check_expiry,
|
||||
(first_name, last_name, company, phone, email, check_type, check_number, check_expiry,
|
||||
default_host_id, site_id, pin_enc, pin_lookup, photo_path, notes, active)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`
|
||||
)
|
||||
.run(
|
||||
v.firstName, v.lastName, v.phone, v.email, v.checkType, v.checkNumber, v.checkExpiry,
|
||||
v.defaultHostId, siteId, encryptPin(pin), pinLookup(pin), photoPath, v.notes
|
||||
v.firstName, v.lastName, v.company, v.phone, v.email, v.checkType, v.checkNumber,
|
||||
v.checkExpiry, v.defaultHostId, siteId, encryptPin(pin), pinLookup(pin), photoPath, v.notes
|
||||
);
|
||||
res.json(shapeFrequent(db.prepare('SELECT * FROM frequent_visitors WHERE id = ?').get(info.lastInsertRowid), true));
|
||||
} catch (err) {
|
||||
@@ -794,11 +796,11 @@ router.patch('/frequent/:id', (req, res) => {
|
||||
}
|
||||
|
||||
db.prepare(
|
||||
`UPDATE frequent_visitors SET first_name = ?, last_name = ?, phone = ?, email = ?,
|
||||
`UPDATE frequent_visitors SET first_name = ?, last_name = ?, company = ?, phone = ?, email = ?,
|
||||
check_type = ?, check_number = ?, check_expiry = ?, default_host_id = ?, site_id = ?,
|
||||
photo_path = ?, notes = ?, active = ?, updated_at = datetime('now') WHERE id = ?`
|
||||
).run(
|
||||
v.firstName, v.lastName, v.phone, v.email, v.checkType, v.checkNumber, v.checkExpiry,
|
||||
v.firstName, v.lastName, v.company, v.phone, v.email, v.checkType, v.checkNumber, v.checkExpiry,
|
||||
v.defaultHostId,
|
||||
scope || (req.body?.siteId !== undefined ? v.siteId : row.site_id),
|
||||
photoPath,
|
||||
@@ -892,6 +894,7 @@ function shapeVisit(v) {
|
||||
visitorType: v.visitor_type,
|
||||
firstName: v.first_name,
|
||||
lastName: v.last_name,
|
||||
company: v.company,
|
||||
phone: v.phone,
|
||||
email: v.email,
|
||||
checkType: v.check_type,
|
||||
@@ -932,8 +935,10 @@ router.get('/visits', (req, res) => {
|
||||
params.push(`${to}T23:59:59.999Z`);
|
||||
}
|
||||
if (q) {
|
||||
where.push('(last_name LIKE ? OR first_name LIKE ? OR host_name LIKE ? OR phone LIKE ? OR email LIKE ?)');
|
||||
params.push(`%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`);
|
||||
where.push(
|
||||
'(last_name LIKE ? OR first_name LIKE ? OR company LIKE ? OR host_name LIKE ? OR phone LIKE ? OR email LIKE ?)'
|
||||
);
|
||||
params.push(`%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`, `%${q}%`);
|
||||
}
|
||||
const sql = `SELECT * FROM visits ${where.length ? 'WHERE ' + where.join(' AND ') : ''} ORDER BY signed_in_at DESC LIMIT 500`;
|
||||
res.json(db.prepare(sql).all(...params).map(shapeVisit));
|
||||
@@ -969,9 +974,9 @@ router.get('/visits.csv', (req, res) => {
|
||||
.all(...params);
|
||||
|
||||
const csv = toCsv([
|
||||
['Visit ID', 'Site', 'Type', 'First name', 'Last name', 'Phone', 'Email', 'Check type', 'Check number', 'Visiting', 'Reason', 'Signed in', 'Signed out', 'Closed by', 'Photo'],
|
||||
['Visit ID', 'Site', 'Type', 'First name', 'Last name', 'Company', 'Phone', 'Email', 'Check type', 'Check number', 'Visiting', 'Reason', 'Signed in', 'Signed out', 'Closed by', 'Photo'],
|
||||
...rows.map((v) => [
|
||||
v.id, v.site_name, v.visitor_type, v.first_name, v.last_name, v.phone, v.email,
|
||||
v.id, v.site_name, v.visitor_type, v.first_name, v.last_name, v.company, v.phone, v.email,
|
||||
v.check_type, v.check_number, v.host_name, v.visit_reason,
|
||||
localStamp(v.signed_in_at), localStamp(v.signed_out_at), v.signed_out_by,
|
||||
v.photo_path ? 'yes' : 'no',
|
||||
@@ -1041,6 +1046,7 @@ router.get('/pass/:id', (req, res) => {
|
||||
border:1px solid var(--rule); }
|
||||
h1 { margin:0; font-size: 21px; letter-spacing:-0.01em; }
|
||||
.site { margin:0 0 14px; font-size:12px; color:var(--muted); }
|
||||
.org { margin: 3px 0 0; font-size: 13px; color: var(--muted); }
|
||||
.pin { margin: 14px 0 4px; font-size: 46px; font-weight: 700; letter-spacing: 0.22em;
|
||||
font-variant-numeric: tabular-nums; color: var(--deep); }
|
||||
.pin-label { margin:0 0 16px; font-size:12px; color:var(--muted); }
|
||||
@@ -1064,6 +1070,7 @@ router.get('/pass/:id', (req, res) => {
|
||||
<div class="card">
|
||||
<p class="site">${esc(site ? site.name : config.siteName)}</p>
|
||||
<h1>${esc(row.first_name)} ${esc(row.last_name)}</h1>
|
||||
${row.company ? `<p class="org">${esc(row.company)}</p>` : ''}
|
||||
<p class="pin">${esc(pin)}</p>
|
||||
<p class="pin-label">Your PIN. Keep this card, it is not sent to you again.</p>
|
||||
<dl>
|
||||
@@ -1105,6 +1112,8 @@ router.get('/status', (req, res) => {
|
||||
lastError: sheets.status.lastError,
|
||||
tab: sheets.tabName(),
|
||||
stale: sheets.isStale(),
|
||||
serviceAccount: sheets.serviceAccountEmail(),
|
||||
spreadsheetId: config.sheets.spreadsheetId || null,
|
||||
lastOnSiteSync: sheets.status.lastOnSiteSync,
|
||||
onSiteCount: sheets.status.onSiteCount,
|
||||
onSiteError: sheets.status.onSiteError,
|
||||
|
||||
Reference in New Issue
Block a user