Files
open-isp/services/soundboxService.js
oxpoe 1c40e6a0e8 feat(keamanan): security watchdog brute-force + soundbox TTS (port upstream)
- securityWatchdogService: deteksi brute force login admin/kasir/teknisi/kolektor & log MikroTik, blokir IP 15 menit, alert suara + WA/Telegram
- soundboxService + voiceTtsService (TTS Google Translate, cache)
- hook login admin/teknisi/kolektor, hook soundbox saat invoice lunas
- route /admin/api/soundbox/poll dan /test
2026-11-05 01:27:33 +07:00

86 lines
3.0 KiB
JavaScript

const db = require('../config/database');
const { logger } = require('../config/logger');
// Pastikan tabel soundbox_events ada
try {
db.exec(`
CREATE TABLE IF NOT EXISTS soundbox_events (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL,
title TEXT NOT NULL,
speech_text TEXT NOT NULL,
amount INTEGER DEFAULT 0,
customer_name TEXT DEFAULT '',
is_read INTEGER DEFAULT 0,
created_at DATETIME DEFAULT (NOW_LOCAL())
);
CREATE INDEX IF NOT EXISTS idx_soundbox_created ON soundbox_events(created_at);
`);
} catch (e) {
logger.error('[Soundbox] Init table error: ' + e.message);
}
function formatRupiahSpoken(n) {
const num = Number(n || 0);
return num.toLocaleString('id-ID') + ' rupiah';
}
/**
* Memasukkan notifikasi pembayaran masuk ke antrean Soundbox
*/
function queueSoundboxEvent({ type = 'payment', title = 'Pembayaran Masuk', speechText = '', amount = 0, customerName = '' }) {
try {
if (!speechText) {
const spokenAmt = formatRupiahSpoken(amount);
if (type === 'voucher') {
speechText = `Pembayaran voucher hotspot ${customerName ? 'paket ' + customerName : ''} sebesar ${spokenAmt} berhasil diterima.`;
} else if (type === 'invoice') {
speechText = `Pembayaran tagihan ${customerName ? 'atas nama ' + customerName : ''} sebesar ${spokenAmt} berhasil lunas.`;
} else if (type === 'donation') {
speechText = `Donasi masuk sebesar ${spokenAmt} ${customerName ? 'dari ' + customerName : ''} berhasil diterima.`;
} else if (type === 'topup') {
speechText = `Top up saldo ${customerName ? 'atas nama ' + customerName : ''} sebesar ${spokenAmt} berhasil masuk.`;
} else {
speechText = `Pembayaran sebesar ${spokenAmt} berhasil diterima.`;
}
}
const res = db.prepare(`
INSERT INTO soundbox_events (type, title, speech_text, amount, customer_name, created_at)
VALUES (?, ?, ?, ?, ?, (NOW_LOCAL()))
`).run(type, title, speechText, Math.floor(amount || 0), customerName);
// Bersihkan entri lama jika > 200 agar database tetap ramping
db.prepare('DELETE FROM soundbox_events WHERE id < (SELECT COALESCE(MAX(id), 0) - 200 FROM soundbox_events)').run();
logger.info(`[Soundbox] Event #${res.lastInsertRowid} (${type}): ${speechText}`);
return res.lastInsertRowid;
} catch (err) {
logger.error('[Soundbox] Queue error: ' + err.message);
return null;
}
}
/**
* Mengambil event terbaru untuk polling browser
*/
function getLatestEvents(afterId = 0, limit = 10) {
try {
const after = Number(afterId || 0);
if (after > 0) {
return db.prepare('SELECT * FROM soundbox_events WHERE id > ? ORDER BY id ASC LIMIT ?').all(after, limit);
} else {
return db.prepare('SELECT * FROM soundbox_events ORDER BY id DESC LIMIT ?').all(limit).reverse();
}
} catch (err) {
logger.error('[Soundbox] Fetch error: ' + err.message);
return [];
}
}
module.exports = {
queueSoundboxEvent,
getLatestEvents,
formatRupiahSpoken
};