feat: Improve auto-scanner robustness and instructions

Provide clearer instructions for users on how to keep the automated scanner running 24/7 by suggesting external scheduling services.
Refactor the server-side auto-scanner logic to use `runTransaction` for atomic updates of `lastAutoScan`, preventing potential race conditions and ensuring greater reliability.
This commit is contained in:
desartstudio95
2026-05-20 07:43:17 -07:00
parent ee12f7d6be
commit c282aea650
2 changed files with 53 additions and 35 deletions
+28 -10
View File
@@ -6,7 +6,7 @@ import * as OneSignal from 'onesignal-node';
// Import Firebase (Server-side compatible since v9 supports Node environments) // Import Firebase (Server-side compatible since v9 supports Node environments)
import { db } from "./src/lib/firebase"; import { db } from "./src/lib/firebase";
import { doc, getDoc, setDoc, addDoc, collection } from "firebase/firestore"; import { doc, getDoc, setDoc, addDoc, collection, runTransaction } from "firebase/firestore";
async function startServer() { async function startServer() {
const app = express(); const app = express();
@@ -133,10 +133,15 @@ _Analisado por Inteligência Institucional (Worker Node)_
} }
}; };
setInterval(async () => { const runAutoScannerJob = async () => {
try { try {
const settingsRef = doc(db, 'settings', 'app'); const settingsRef = doc(db, 'settings', 'app');
const settingsSnap = await getDoc(settingsRef); let botToken = '';
let chatId = '';
let shouldExecute = false;
await runTransaction(db, async (transaction) => {
const settingsSnap = await transaction.get(settingsRef);
if (!settingsSnap.exists()) return; if (!settingsSnap.exists()) return;
const data = settingsSnap.data(); const data = settingsSnap.data();
@@ -148,11 +153,16 @@ _Analisado por Inteligência Institucional (Worker Node)_
// Check if enough time has passed based on interval // Check if enough time has passed based on interval
if (now - lastScan >= intervalMins * 60 * 1000) { if (now - lastScan >= intervalMins * 60 * 1000) {
// Atomic lock: update lastAutoScan inside transaction
transaction.update(settingsRef, { lastAutoScan: now });
shouldExecute = true;
botToken = data.telegramBotToken || '';
chatId = data.telegramChatId || '';
}
});
if (shouldExecute) {
console.log("[Worker] Executing Auto-Scanner..."); console.log("[Worker] Executing Auto-Scanner...");
// Lock to prevent duplicates if multiple instances exist
await setDoc(settingsRef, { lastAutoScan: now }, { merge: true });
const pairs = ['EURUSD', 'GBPUSD', 'BTCUSD', 'XAU/USD', 'US30']; const pairs = ['EURUSD', 'GBPUSD', 'BTCUSD', 'XAU/USD', 'US30'];
const randomPair = pairs[Math.floor(Math.random() * pairs.length)]; const randomPair = pairs[Math.floor(Math.random() * pairs.length)];
@@ -169,15 +179,23 @@ _Analisado por Inteligência Institucional (Worker Node)_
stopLoss: decision === 'BUY' ? '-15 pips' : '+15 pips', stopLoss: decision === 'BUY' ? '-15 pips' : '+15 pips',
}; };
if (data.telegramBotToken && data.telegramChatId) { if (botToken && chatId) {
await sendTelegramAlertServer(signalData, data.telegramBotToken, data.telegramChatId); await sendTelegramAlertServer(signalData, botToken, chatId);
} }
console.log(`[Worker] Sinais gerados e enviados para ${randomPair}`); console.log(`[Worker] Sinais gerados e enviados para ${randomPair}`);
} }
} catch (e) { } catch (e) {
console.error("[Worker] Erro no Auto-Scanner:", e); console.error("[Worker] Erro no Auto-Scanner:", e);
} }
}, 60000); // Check every minute };
setInterval(runAutoScannerJob, 60000); // Check every minute internally
app.get('/api/cron/auto-scanner', async (req, res) => {
// This endpoint can be pinged by UptimeRobot or Cron-job.org to prevent container sleep
await runAutoScannerJob();
res.json({ status: "ok", message: "Scanner check executed", timestamp: new Date().toISOString() });
});
app.listen(PORT, "0.0.0.0", () => { app.listen(PORT, "0.0.0.0", () => {
console.log('Server running on http://localhost:' + PORT); console.log('Server running on http://localhost:' + PORT);
+1 -1
View File
@@ -349,7 +349,7 @@ export const AdminDashboard: React.FC = () => {
</span> </span>
</div> </div>
<p className="text-[10px] text-brand-red font-bold"> <p className="text-[10px] text-brand-red font-bold">
Atenção: Mantenha esta aba aberta para que o scanner automatizado (Robô Funcional no Browser) permaneça operando. Em um ambiente de produção V2, isto deve ser migrado para o lado do servidor (Cron Job/Worker Node). Atenção: A aplicação entra em repouso quando não abas abertas. Para o Robô funcionar 24/7 de forma totalmente automática, utilize um serviço gratuito como o <strong>cron-job.org</strong> ou <strong>UptimeRobot</strong> para visitar a URL <code>{window.location.origin}/api/cron/auto-scanner</code> a cada 1 minuto.
</p> </p>
</div> </div>
)} )}