| const db = require('../config/db'); |
| const sms = require('../services/sms'); |
| const messages = require('./messages'); |
| const { canSendTenantNotification } = require('./notificationPreferences'); |
|
|
| function formatDateTime(value) { |
| if (!value) return null; |
| const date = new Date(value); |
| if (Number.isNaN(date.getTime())) return null; |
|
|
| const parts = new Intl.DateTimeFormat('en-GB', { |
| timeZone: 'Africa/Dar_es_Salaam', |
| year: 'numeric', |
| month: 'short', |
| day: '2-digit', |
| hour: '2-digit', |
| minute: '2-digit', |
| hour12: false, |
| }).formatToParts(date); |
| const part = type => parts.find(item => item.type === type)?.value || ''; |
|
|
| return `${part('day')} ${part('month')} ${part('year')}, ${part('hour')}:${part('minute')} EAT`; |
| } |
|
|
| async function loadTokenContext(tokenId) { |
| return db.queryOne( |
| `SELECT t.id AS token_id, t.code AS token_code, t.locked_mac, t.expires_at, |
| d.id AS device_id, d.name AS device_name, |
| c.id AS client_id, c.phone AS client_phone, |
| p.phone AS payment_phone |
| FROM access_tokens t |
| JOIN devices d ON d.id = t.device_id |
| JOIN clients c ON c.id = t.client_id |
| LEFT JOIN payments p ON p.id = t.payment_id |
| WHERE t.id = ? |
| LIMIT 1`, |
| [tokenId] |
| ); |
| } |
|
|
| async function alertWifiClient(eventType, tokenId, options = {}) { |
| try { |
| const token = await loadTokenContext(tokenId); |
| if (!token?.client_id) return false; |
| if (!(await canSendTenantNotification(token.client_id, 'purchase_alerts'))) return false; |
|
|
| const mac = options.mac || token.locked_mac || null; |
| const minutes = options.minutes || null; |
| const action = options.action || 'revoked'; |
| const expiresAt = formatDateTime(options.expiresAt || token.expires_at); |
|
|
| let alertKey = null; |
| let module = 'tokens'; |
| let content = null; |
|
|
| if (eventType === 'token_started') { |
| alertKey = `token-started-${token.token_id}-${String(mac || 'unknown').replace(/[^A-Za-z0-9]/g, '')}`; |
| content = messages.tokenStartedAlert(token.token_code, token.device_name, mac, expiresAt); |
| } else if (eventType === 'token_extended') { |
| alertKey = `token-extended-${token.token_id}-${Date.now()}`; |
| content = messages.tokenExtendedAlert(token.token_code, token.device_name, minutes, expiresAt); |
| } else if (eventType === 'token_revoked') { |
| alertKey = `token-revoked-${token.token_id}-${Date.now()}`; |
| content = messages.tokenRevokedAlert(token.token_code, token.device_name, action); |
| } else { |
| return false; |
| } |
|
|
| const result = await db.query( |
| `INSERT IGNORE INTO omada_alerts |
| (client_id, device_id, omada_alert_id, alert_key, module, level, content, device_mac, alert_time) |
| VALUES (?, ?, ?, ?, ?, 'info', ?, ?, NOW())`, |
| [ |
| token.client_id, |
| token.device_id, |
| alertKey, |
| eventType, |
| module, |
| content, |
| mac, |
| ] |
| ); |
|
|
| if (Number(result?.affectedRows || 0) === 0) { |
| return false; |
| } |
|
|
| if (eventType === 'token_started' && token.payment_phone) { |
| const buyerMessage = messages.tokenStartedNotice(token.token_code, token.device_name, expiresAt); |
| sms.sendSMS(token.payment_phone, buyerMessage, { |
| ttl: 60 * 60, |
| priority: sms.priorities.high, |
| }).catch(err => { |
| console.error('[token-alert] buyer SMS failed:', err.message); |
| }); |
| } |
|
|
| return true; |
| } catch (err) { |
| console.error('[token-alert] failed:', err.message, { eventType, tokenId }); |
| return false; |
| } |
| } |
|
|
| module.exports = { |
| alertWifiClient, |
| }; |
|
|