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, };