- 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
86 lines
3.0 KiB
JavaScript
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
|
|
};
|